"use client"; import { useMemo } from "react"; import { Empty } from "@/components/ui/primitives"; import { cn } from "@/lib/utils"; import { dateOf } from "@/lib/databases/values"; import { useDb } from "../hooks"; import { OPTION_CLASS } from "../colors"; const DAY = 864e5; const day0 = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate()); export function TimelineView() { const ctx = useDb(); const sp = ctx.props.find((p) => p.id === ctx.view.config.startProperty); const ep = ctx.props.find((p) => p.id === ctx.view.config.endProperty) ?? sp; const scale = ctx.view.config.scale ?? "week"; const px = scale === "week" ? 28 : 8; const st = ctx.props.find((p) => p.type === "status" || p.type === "select"); const data = useMemo(() => { if (!sp || !ep) return null; const items = ctx.rows.map((r) => { const s = dateOf(r.values[sp.id]); const e = dateOf(r.values[ep.id]) ?? s; if (!s) return null; const a = day0(s), b = day0(e && e >= s ? e : s); return { r, a, b }; }).filter(Boolean) as { r: (typeof ctx.rows)[number]; a: Date; b: Date }[]; const today = day0(new Date()); const min = new Date(Math.min(today.getTime(), ...items.map((i) => i.a.getTime())) - 7 * DAY); const max = new Date(Math.max(today.getTime(), ...items.map((i) => i.b.getTime())) + 14 * DAY); const days = Math.round((max.getTime() - min.getTime()) / DAY) + 1; const cols: { label: string; x: number; w: number }[] = []; for (let d = new Date(min); d <= max; ) { const start = new Date(d); if (scale === "week") { d.setDate(d.getDate() + 1); } else { d = new Date(d.getFullYear(), d.getMonth() + 1, 1); } const end = d > max ? new Date(max.getTime() + DAY) : d; const x = ((start.getTime() - min.getTime()) / DAY) * px; cols.push({ label: scale === "week" ? start.toLocaleDateString("en-CA", { day: "numeric", month: start.getDate() === 1 ? "short" : undefined }) : start.toLocaleDateString("en-CA", { month: "short", year: "numeric" }), x, w: Math.round((end.getTime() - start.getTime()) / DAY) * px }); } return { items, min, days, cols, todayX: ((today.getTime() - min.getTime()) / DAY) * px, undated: ctx.rows.filter((r) => !dateOf(r.values[sp.id])) }; }, [ctx.rows, sp, ep, scale, px]); if (!sp || !data) return ; const width = data.days * px; return (
Scale{(["week", "month"] as const).map((s) => )}
{data.items.map((i) => )}
{data.cols.map((c, i) =>
{c.label}
)}
{data.items.map((i) => { const x = ((i.a.getTime() - data.min.getTime()) / DAY) * px; const w = Math.max(px, (((i.b.getTime() - i.a.getTime()) / DAY) + 1) * px); const o = st ? (st.config.options ?? []).find((op) => op.id === i.r.values[st.id]) : null; return (
); })}
{data.undated.length > 0 &&
{data.undated.length} row{data.undated.length > 1 ? "s" : ""} without a start date: {data.undated.slice(0, 5).map((r) => )}{data.undated.length > 5 && "…"}
}
); }