"use client"; import { useState } from "react"; import { ChevronLeft, ChevronRight, Plus } from "lucide-react"; import { Empty } from "@/components/ui/primitives"; import { cn } from "@/lib/utils"; import type { RowOut } from "@/lib/databases/types"; import { dateOf } from "@/lib/databases/values"; import { useDb } from "../hooks"; import { CellDisplay } from "../cells/Cell"; const iso = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const DOW = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; export function CalendarView() { const ctx = useDb(); const dp = ctx.props.find((p) => p.id === ctx.view.config.dateProperty); const [cur, setCur] = useState(() => { const d = new Date(); d.setDate(1); d.setHours(0, 0, 0, 0); return d; }); if (!dp) return ; const byDay = new Map(); for (const r of ctx.rows) { const d = dateOf(r.values[dp.id]); if (!d) continue; const k = iso(d); byDay.set(k, [...(byDay.get(k) ?? []), r]); } const start = new Date(cur); start.setDate(1 - ((cur.getDay() + 6) % 7)); const days: Date[] = []; for (let i = 0; i < 42; i++) { const d = new Date(start); d.setDate(start.getDate() + i); days.push(d); } const today = iso(new Date()); const month = cur.getMonth(); const label = cur.toLocaleDateString("en-CA", { month: "long", year: "numeric" }); const nav = (n: number) => setCur((c) => { const d = new Date(c); d.setMonth(d.getMonth() + n); return d; }); const add = (d: Date) => ctx.addRow({ [dp.id]: dp.type === "date" ? iso(d) : undefined }); const canAdd = dp.type === "date"; const undated = ctx.rows.filter((r) => !dateOf(r.values[dp.id])); const header = {label} nav(-1)} className="grid size-8 place-items-center rounded-md hover:bg-bg-sunk"> { const d = new Date(); d.setDate(1); d.setHours(0, 0, 0, 0); setCur(d); }} className="h-8 rounded-md px-2 text-[12px] hover:bg-bg-sunk">Today nav(1)} className="grid size-8 place-items-center rounded-md hover:bg-bg-sunk">; if (ctx.isMobile) { const inMonth = days.filter((d) => d.getMonth() === month && byDay.has(iso(d))); return {header}{inMonth.length === 0 && Nothing this month.}{inMonth.map((d) => {d.toLocaleDateString("en-CA", { weekday: "short", day: "numeric", month: "short" })}{byDay.get(iso(d))!.map((r) => )})}{canAdd && add(new Date())} className="mt-3 flex h-9 items-center gap-1 rounded-md px-2 text-[13px] text-fg-muted hover:bg-bg-sunk"> New today}; } return ( {header} {DOW.map((d) => {d})} {days.map((d) => { const k = iso(d); const evs = byDay.get(k) ?? []; return ( canAdd && add(d)} className={cn("group/day relative min-h-[6rem] border-b border-r border-line p-1", d.getMonth() !== month && "bg-bg-sunk/40 text-fg-faint")}> {d.getDate()}{canAdd && add(d)} className="ml-auto hidden size-5 place-items-center rounded text-fg-faint hover:bg-bg-sunk hover:text-fg group-hover/day:grid">} {evs.slice(0, 4).map((r) => )}{evs.length > 4 && +{evs.length - 4} more} ); })} {undated.length > 0 && {undated.length} row{undated.length > 1 ? "s" : ""} without a date (visible in the table view).} ); } function Chip({ row, big }: { row: RowOut; big?: boolean }) { const ctx = useDb(); const st = ctx.props.find((p) => p.type === "status" || p.type === "select"); return ctx.open(row.id)} className={cn("flex w-full items-center gap-1 truncate rounded border border-line bg-bg-elev text-left hover:bg-bg-sunk", big ? "h-10 px-2 text-[13px]" : "h-5 px-1 text-[11px]")}>{row.icon ? `${row.icon} ` : ""}{row.title || "Untitled"}{big && st && }; }