"use client"; import { useCallback, useEffect, useState } from "react"; import { usePathname } from "next/navigation"; import { toast } from "sonner"; import { AlertTriangle, CalendarClock, ChevronLeft, ChevronRight, Plus } from "lucide-react"; import { cn } from "@/lib/utils"; import { formatWeekdayDate } from "@/lib/format"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useScheduledPanel } from "@/components/scheduled/scheduled-panel-provider"; import { ScheduledTodoItem } from "@/components/scheduled/scheduled-todo-item"; import { ScheduledTodoDialog } from "@/components/scheduled/scheduled-todo-dialog"; import { getScheduledBoard, toggleScheduledOccurrence } from "@/lib/actions/scheduled-todos"; import type { ScheduledBoardDTO, ScheduledOccurrenceDTO } from "@/types/scheduled-todo"; /** `layout.tsx` is shared by Home and every Project board, so the panel * derives its own scope from the URL rather than a prop threaded down from * a page it doesn't render (see the plan notes on why this fetches * client-side instead of taking initial data as a prop like KanbanBoard). */ function projectIdFromPathname(pathname: string): string | null { const match = /^\/projects\/([^/]+)/.exec(pathname); return match ? match[1] : null; } function matchesOccurrence(o: ScheduledOccurrenceDTO, scheduledTodoId: string, occurrenceDate: string) { return o.scheduledTodoId === scheduledTodoId && o.occurrenceDate === occurrenceDate; } function withToggledOccurrence( board: ScheduledBoardDTO, scheduledTodoId: string, occurrenceDate: string, completed: boolean ): ScheduledBoardDTO { const flip = (o: ScheduledOccurrenceDTO) => matchesOccurrence(o, scheduledTodoId, occurrenceDate) ? { ...o, completed } : o; return { // Overdue only ever lists incomplete occurrences -- completing one // removes it rather than leaving a checked-off item in the flashy list. overdue: completed ? board.overdue.filter((o) => !matchesOccurrence(o, scheduledTodoId, occurrenceDate)) : board.overdue.map(flip), today: { ...board.today, occurrences: board.today.occurrences.map(flip) }, upcoming: board.upcoming.map((day) => ({ ...day, occurrences: day.occurrences.map(flip) })), }; } export function ScheduledPanel() { const { collapsed, toggle } = useScheduledPanel(); const pathname = usePathname(); const projectId = projectIdFromPathname(pathname); const [board, setBoard] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [editingId, setEditingId] = useState(null); const refresh = useCallback(() => { getScheduledBoard(projectId) .then(setBoard) .catch(() => setBoard(null)); }, [projectId]); useEffect(() => { setBoard(null); refresh(); }, [refresh]); async function handleToggle(occurrence: ScheduledOccurrenceDTO, completed: boolean) { setBoard((prev) => prev ? withToggledOccurrence(prev, occurrence.scheduledTodoId, occurrence.occurrenceDate, completed) : prev ); try { await toggleScheduledOccurrence(occurrence.scheduledTodoId, occurrence.occurrenceDate, completed); } catch { toast.error("Couldn't update scheduled to-do. Try again."); refresh(); } } function handleEdit(scheduledTodoId: string) { setEditingId(scheduledTodoId); setDialogOpen(true); } function handleAdd() { setEditingId(null); setDialogOpen(true); } const overdueCount = board?.overdue.length ?? 0; return ( <> ); } function CollapsedRail({ overdueCount, onAdd, onExpand, }: { overdueCount: number; onAdd: () => void; onExpand: () => void; }) { return ( <>
{overdueCount > 0 && ( {overdueCount > 9 ? "9+" : overdueCount} )} } /> {overdueCount > 0 ? `${overdueCount} overdue` : "Scheduled to-dos"}
} /> Add Scheduled To-Do
); } function ExpandedPanel({ board, onToggleOccurrence, onEditOccurrence, onAdd, onCollapse, }: { board: ScheduledBoardDTO | null; onToggleOccurrence: (occurrence: ScheduledOccurrenceDTO, completed: boolean) => void; onEditOccurrence: (scheduledTodoId: string) => void; onAdd: () => void; onCollapse: () => void; }) { return ( <>
Scheduled
{!board ? (

Loading…

) : (
{board.overdue.length > 0 && (
Overdue ({board.overdue.length})
    {board.overdue.map((o) => ( onToggleOccurrence(o, completed)} onEdit={() => onEditOccurrence(o.scheduledTodoId)} /> ))}
)}

Today — {formatWeekdayDate(board.today.date)}

{board.today.occurrences.length > 0 ? (
    {board.today.occurrences.map((o) => ( onToggleOccurrence(o, completed)} onEdit={() => onEditOccurrence(o.scheduledTodoId)} /> ))}
) : (

Nothing scheduled today.

)}
{board.upcoming.map((day, index) => ( // Fades a little more for each day further from today, so // today reads as the loudest thing in the column.

{formatWeekdayDate(day.date)}

{day.occurrences.length > 0 ? (
    {day.occurrences.map((o) => ( onToggleOccurrence(o, completed)} onEdit={() => onEditOccurrence(o.scheduledTodoId)} /> ))}
) : (

Nothing scheduled.

)}
))}
)}
); }