Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions artifacts/sunsama-web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<title>Sunsama</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
29 changes: 29 additions & 0 deletions artifacts/sunsama-web/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@workspace/sunsama-web",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"devDependencies": {
"@radix-ui/react-popover": "^1.1.15",
"@tailwindcss/vite": "catalog:",
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"@replit/vite-plugin-runtime-error-modal": "catalog:",
"date-fns": "^3.6.0",
"lucide-react": "catalog:",
"react": "catalog:",
"react-day-picker": "^9.11.1",
"react-dom": "catalog:",
"tailwind-merge": "catalog:",
"tailwindcss": "catalog:",
"vite": "catalog:"
}
}
5 changes: 5 additions & 0 deletions artifacts/sunsama-web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { HomePage } from "./pages/home/Homepage";

export default function App() {
return <HomePage />;
}
15 changes: 15 additions & 0 deletions artifacts/sunsama-web/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import "tailwindcss";

*,
*::before,
*::after {
box-sizing: border-box;
}

html,
body {
height: 100%;
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
}
10 changes: 10 additions & 0 deletions artifacts/sunsama-web/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
85 changes: 85 additions & 0 deletions artifacts/sunsama-web/src/pages/home/BacklogPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import "./homepage.css";
import { useState } from "react";
import type { Project } from "./types";
import { X, Search, RotateCcw } from "lucide-react";

interface Props {
archivedProjects: Project[];
onRestore: (projectId: string) => void;
onClose: () => void;
}

export default function BacklogPanel({
archivedProjects,
onRestore,
onClose,
}: Props) {
const [search, setSearch] = useState("");
const [filterTag, setFilterTag] = useState("all");

const filtered = archivedProjects.filter((p) => {
const matchesSearch =
!search || p.title.toLowerCase().includes(search.toLowerCase());
const matchesTag = filterTag === "all" || p.tag === filterTag;
return matchesSearch && matchesTag;
});

return (
<aside className="backlog-panel">
<div className="backlog-header">
<span className="backlog-title">Search</span>
<button className="backlog-close" onClick={onClose} aria-label="Close">
<X size={16} />
</button>
</div>

<div className="backlog-search">
<Search size={14} className="backlog-search-icon" />
<input
className="backlog-search-input"
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>

<div className="backlog-filters">
<button
className={`backlog-filter-btn ${filterTag === "all" ? "active" : ""}`}
onClick={() => setFilterTag("all")}
>
Filter
</button>
<span className="backlog-filter-label">Date: Anytime</span>
<span className="backlog-filter-label">
Channel: {filterTag === "all" ? "all" : filterTag}
</span>
</div>

<div className="backlog-list">
{filtered.length === 0 && (
<div className="backlog-empty">No archived tasks</div>
)}
{filtered.map((p) => (
<div key={p._id} className="backlog-item">
<div className="backlog-item-header">
<span className="backlog-item-title">{p.title}</span>
<span className="backlog-item-date">{p.date}</span>
</div>
<div className="backlog-item-meta">
<span className="backlog-archived-badge">Archived</span>
<span className="tag">{p.tag ?? "# work"}</span>
<button
className="backlog-restore-btn"
onClick={() => onRestore(p._id)}
aria-label="Restore task"
>
<RotateCcw size={14} />
</button>
</div>
</div>
))}
</div>
</aside>
);
}
34 changes: 34 additions & 0 deletions artifacts/sunsama-web/src/pages/home/BoardHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import "./homepage.css";
import {
Calendar,
SlidersHorizontal,
LayoutGrid,
CalendarDays,
} from "lucide-react";

export default function BoardHeader() {
return (
<header className="board-header">
<div className="left-controls">
<button className="btn small">
<Calendar size={14} />
Today
</button>
<button className="btn small ghost">
<SlidersHorizontal size={14} />
Filter
</button>
</div>
<div className="right-controls">
<button className="btn small">
<LayoutGrid size={14} />
Board
</button>
<button className="btn small ghost">
<CalendarDays size={14} />
Calendars
</button>
</div>
</header>
);
}
100 changes: 100 additions & 0 deletions artifacts/sunsama-web/src/pages/home/CalendarPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import "./homepage.css";
import { useState, useEffect, useMemo } from "react";
import {
Calendar,
BarChart2,
Settings,
ClipboardList,
Moon,
Search,
Zap,
Plus,
} from "lucide-react";

const HOURS = Array.from({ length: 12 }, (_, i) => i + 12);
const HOUR_HEIGHT = 56;

function currentMinuteOffset(): number {
const now = new Date();
const h = now.getHours();
const m = now.getMinutes();
const minutesSinceNoon = (h - 12) * 60 + m;
return (minutesSinceNoon / 60) * HOUR_HEIGHT;
}

interface Props {
onOpenBacklog: () => void;
}

export default function CalendarPane({ onOpenBacklog }: Props) {
const now = new Date();
const dayAbbr = now
.toLocaleDateString("en-US", { weekday: "short" })
.toUpperCase();
const dayNum = now.getDate();

const [timeOffset, setTimeOffset] = useState(currentMinuteOffset);

useEffect(() => {
const id = setInterval(() => setTimeOffset(currentMinuteOffset()), 60_000);
return () => clearInterval(id);
}, []);

const showTimeLine = useMemo(() => {
const h = now.getHours();
return h >= 12 && h < 24;
}, [now]);

return (
<aside className="calendar-pane">
<div className="calendar-top">
<div className="cal-header-row">
<div className="cal-zoom-controls">
<button className="cal-zoom-btn">+</button>
<span className="cal-zoom-label">1x</span>
<button className="cal-zoom-btn">&minus;</button>
</div>
<div className="month">
{dayAbbr}
<br />
{dayNum}
</div>
</div>
</div>

<div className="calendar-hours">
<div className="hours-grid-labeled">
{HOURS.map((h) => (
<div key={h} className="hour-row" style={{ height: HOUR_HEIGHT }}>
<span className="hour-label">
{h}:00
</span>
<div className="hour-line" />
</div>
))}
{showTimeLine && (
<div
className="current-time-line"
style={{ top: timeOffset }}
/>
)}
</div>
</div>

<div className="cal-icon-toolbar">
<Calendar size={18} />
<BarChart2 size={18} />
<Settings size={18} />
<button className="toolbar-btn" onClick={onOpenBacklog} aria-label="Open backlog">
<ClipboardList size={18} />
</button>
<Moon size={18} />
<Search size={18} />
<Zap size={18} />
<Plus size={18} />
</div>

<div className="chat-btn">💬</div>
</aside>
);
}
49 changes: 49 additions & 0 deletions artifacts/sunsama-web/src/pages/home/CalendarSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from "react";
import * as Popover from "@radix-ui/react-popover";
import { CalendarIcon } from "lucide-react";
import { DayPicker } from "react-day-picker";
import { format, isToday } from "date-fns";
import "react-day-picker/style.css";

interface Props {
value?: string;
onSelect?: (dateString: string) => void;
}

export default function CalendarSelect({ value, onSelect }: Props) {
const initial = value ? new Date(value) : new Date();
const [date, setDate] = useState<Date>(initial);
const [open, setOpen] = useState(false);

function handleSelect(d: Date | undefined) {
if (!d) return;
setDate(d);
const iso = format(d, "yyyy-MM-dd");
if (onSelect) onSelect(iso);
setOpen(false);
}

return (
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger asChild>
<button className="calendar-trigger" aria-label="Select date">
<CalendarIcon className="calendar-icon" />
{isToday(date) ? "Today" : format(date, "EEEE, MMM d")}
</button>
</Popover.Trigger>

<Popover.Content sideOffset={6} className="calendar-popover">
<div className="calendar-popover-header">
Schedule exact start date
</div>
<DayPicker
mode="single"
required
selected={date}
onSelect={handleSelect}
weekStartsOn={1}
/>
</Popover.Content>
</Popover.Root>
);
}
42 changes: 42 additions & 0 deletions artifacts/sunsama-web/src/pages/home/DayColumn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { DayItem, Project } from "./types";
import "./homepage.css";
import TaskCard from "./TaskCard";

interface Props {
day: DayItem;
projects: Project[];
openEditor: (dayId: string) => void;
pendingToggles: Record<string, boolean>;
onToggleSubtask: (projectId: string, subtaskTitle: string) => void;
onArchiveTask: (projectId: string) => void;
}

export default function DayColumn({
day,
projects,
openEditor,
pendingToggles,
onToggleSubtask,
onArchiveTask,
}: Props) {
return (
<div className="day-col">
<div className="day-head">
<div className="day-name">{day.name}</div>
<div className="day-date">{day.date}</div>
</div>
<button className="add-task" onClick={() => openEditor(day.id)}>
+ Add task
</button>
{projects.map((p) => (
<TaskCard
key={p._id}
project={p}
pendingToggles={pendingToggles}
onToggleSubtask={onToggleSubtask}
onArchiveTask={onArchiveTask}
/>
))}
</div>
);
}
Loading