"use client"; /** * CalendarView — month grid for CALENDAR views. * * The date source is view.config: the view's *group* property when it is a * date (calendar views group by date), else config.datePropertyId, else the * first date property. Each day shows record dots; clicking a day opens a * side list of that day's records; record click → panel. Prev/next/today * navigation; records are matched client-side over the loaded pages (the * query engine remains server-side). */ import { useMemo, useState } from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { DatabaseProperty, DatabaseView, RecordDto, UseDatabaseRecordsResult, valueToText } from "@/hooks/use-database"; import { formatDateValue } from "@/components/database/cell-renderer"; export interface CalendarProps { databaseId: string; view: DatabaseView; properties: DatabaseProperty[]; records: UseDatabaseRecordsResult; onOpenRecord: (record: RecordDto) => void; } /** UTC date → "YYYY-MM-DD". */ function dayKey(d: Date): string { return d.toISOString().slice(0, 10); } function recordDay(record: RecordDto, dateProp: DatabaseProperty): string | null { const start = record.properties[dateProp.id]?.start; return start ? start.slice(0, 10) : null; } export function CalendarView({ view, properties, records, onOpenRecord }: CalendarProps) { const { data, isLoading } = records; const [cursor, setCursor] = useState(() => { const now = new Date(); return new Date(Date.UTC(now.getFullYear(), now.getMonth(), 1)); }); const [selectedDay, setSelectedDay] = useState(null); const dateProp = useMemo(() => { const groupProp = view.group?.[0] ? properties.find((p) => p.id === view.group[0].propertyId) : undefined; if (groupProp && groupProp.type === "date") return groupProp; if (view.config.datePropertyId) { const p = properties.find((x) => x.id === view.config.datePropertyId); if (p) return p; } return properties.find((p) => p.type === "date"); }, [view.group, view.config.datePropertyId, properties]); const titleProp = properties.find((p) => p.isTitle); // ---- month grid ------------------------------------------------------------ const days = useMemo(() => { const year = cursor.getUTCFullYear(); const month = cursor.getUTCMonth(); const firstWeekday = new Date(Date.UTC(year, month, 1)).getUTCDay(); // 0=Sun const daysInMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); const cells: (Date | null)[] = []; for (let i = 0; i < firstWeekday; i++) cells.push(null); for (let d = 1; d <= daysInMonth; d++) cells.push(new Date(Date.UTC(year, month, d))); while (cells.length % 7 !== 0) cells.push(null); return cells; }, [cursor]); // ---- records per day (over loaded pages) ------------------------------------ const byDay = useMemo(() => { const map = new Map(); if (!dateProp) return map; for (const r of data.records) { const k = recordDay(r, dateProp); if (!k) continue; const arr = map.get(k) ?? []; arr.push(r); map.set(k, arr); } return map; }, [data.records, dateProp]); const todayKey = dayKey(new Date()); const monthLabel = cursor.toLocaleDateString("en-US", { timeZone: "UTC", month: "long", year: "numeric" }); const move = (delta: number) => setCursor((c) => new Date(Date.UTC(c.getUTCFullYear(), c.getUTCMonth() + delta, 1))); const selectedRecords = selectedDay ? byDay.get(selectedDay) ?? [] : []; if (!dateProp) { return (
This calendar needs a date property. Set the view's group to a date property (or add one).
); } return (
{/* header */}

{monthLabel}

{/* grid */}
{["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((d) => (
{d}
))}
{days.map((d, i) => { if (!d) return
; const k = dayKey(d); const dayRecords = byDay.get(k) ?? []; return ( ); })}
{/* side list for selected day */} {selectedDay && ( )}
); }