"use client"; import { useMemo } from "react"; import { useParams } from "next/navigation"; import type { inferRouterOutputs } from "@trpc/server"; import { Calendar } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; import { api } from "@/lib/trpc"; import type { AppRouter } from "@/server/root"; import { cn } from "@/lib/utils"; type ListObject = inferRouterOutputs["objects"]["list"]["objects"][number]; type PlannerTask = ListObject & { dueDate?: unknown; properties?: unknown; }; function readDueDateString(task: PlannerTask): string | null { if (typeof task.dueDate === "string" && task.dueDate.trim()) { return task.dueDate.trim(); } const props = task.properties; if (props && typeof props === "object" && props !== null) { const v = (props as Record).dueDate; if (typeof v === "string" && v.trim()) return v.trim(); } const content = task.content; if (content && typeof content === "object" && content !== null) { const v = (content as Record).dueDate; if (typeof v === "string" && v.trim()) return v.trim(); } return null; } function toLocalDateKey(iso: string): string | null { const d = new Date(iso); if (Number.isNaN(d.getTime())) return null; const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } function formatGroupHeading(dateKey: string): string { const [y, mo, da] = dateKey.split("-").map(Number); const date = new Date(y, mo - 1, da); return date.toLocaleDateString(undefined, { weekday: "long", month: "long", day: "numeric", year: "numeric", }); } function formatWeekRangeLabel(dateKey: string): string { const [y, mo, da] = dateKey.split("-").map(Number); const start = new Date(y, mo - 1, da); const day = start.getDay(); const diff = start.getDate() - day + (day === 0 ? -6 : 1); const weekStart = new Date(start); weekStart.setDate(diff); const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); const opts: Intl.DateTimeFormatOptions = { month: "short", day: "numeric" }; const a = weekStart.toLocaleDateString(undefined, opts); const b = weekEnd.toLocaleDateString(undefined, { ...opts, year: weekEnd.getFullYear() !== weekStart.getFullYear() ? "numeric" : undefined, }); return `Week of ${a} – ${b}`; } const NO_DATE_KEY = "__no_date__"; function statusLabel(status: string | null | undefined): string { switch (status) { case "in_progress": return "In progress"; case "done": return "Done"; case "closed": return "Closed"; case "open": default: return "Open"; } } function StatusBadge({ status }: { status: string | null | undefined }) { const s = status ?? "open"; if (s === "in_progress") { return ( {statusLabel(s)} ); } if (s === "done") { return ( {statusLabel(s)} ); } return ( {statusLabel(s)} ); } export default function PlannerPage() { const params = useParams(); const workspaceSlug = typeof params?.workspaceSlug === "string" ? params.workspaceSlug : undefined; const listQuery = api.objects.list.useQuery( { workspace: workspaceSlug!, type: "task", limit: 200 }, { enabled: Boolean(workspaceSlug) }, ); const grouped = useMemo(() => { const rows = (listQuery.data?.objects ?? []) as PlannerTask[]; const map = new Map(); for (const task of rows) { const raw = readDueDateString(task); const key = raw ? toLocalDateKey(raw) : null; const groupKey = key ?? NO_DATE_KEY; const list = map.get(groupKey) ?? []; list.push(task); map.set(groupKey, list); } const keys = [...map.keys()].sort((a, b) => { if (a === NO_DATE_KEY) return 1; if (b === NO_DATE_KEY) return -1; return a.localeCompare(b); }); for (const k of keys) { const list = map.get(k)!; list.sort((a, b) => a.title.localeCompare(b.title)); } return { keys, map }; }, [listQuery.data?.objects]); const taskCount = listQuery.data?.objects?.length ?? 0; const isEmpty = Boolean(workspaceSlug) && !listQuery.isLoading && taskCount === 0; if (!workspaceSlug) { return (

No workspace selected.

); } return (

Planner

Calendar view coming soon
{listQuery.isLoading ? (

Loading tasks…

) : isEmpty ? (
No tasks in this workspace yet. Create tasks in a space to see them here.
) : (
{grouped.keys.map((key) => { const tasks = grouped.map.get(key)!; const isNoDate = key === NO_DATE_KEY; return (

{isNoDate ? "No date" : formatGroupHeading(key)}

{!isNoDate ? (

{formatWeekRangeLabel(key)}

) : null}
    {tasks.map((task) => (
  • {task.title || "Untitled"}
  • ))}
); })}
)}
); }