"use client"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { useActiveSchedule } from "@/hooks/use-channels"; import type { ChannelResponse, ScheduledSlotResponse } from "@/lib/types"; import { BLOCK_COLORS } from "./block-timeline"; // Stable color per block_id within a schedule function makeColorMap(slots: ScheduledSlotResponse[]): Map { const seen = new Map(); slots.forEach((slot) => { if (!seen.has(slot.block_id)) { seen.set(slot.block_id, BLOCK_COLORS[seen.size % BLOCK_COLORS.length]); } }); return seen; } interface DayRowProps { label: string; dayStart: Date; slots: ScheduledSlotResponse[]; colorMap: Map; } function DayRow({ label, dayStart, slots, colorMap }: DayRowProps) { const DAY_MS = 24 * 60 * 60 * 1000; const dayEnd = new Date(dayStart.getTime() + DAY_MS); // Only include slots that overlap this day const daySlots = slots.filter((s) => { const start = new Date(s.start_at); const end = new Date(s.end_at); return start < dayEnd && end > dayStart; }); return (

{label}

{/* Hour grid */} {Array.from({ length: 25 }, (_, i) => (
))} {daySlots.map((slot) => { const slotStart = new Date(slot.start_at); const slotEnd = new Date(slot.end_at); // Clamp to this day const clampedStart = Math.max(slotStart.getTime(), dayStart.getTime()); const clampedEnd = Math.min(slotEnd.getTime(), dayEnd.getTime()); const leftPct = ((clampedStart - dayStart.getTime()) / DAY_MS) * 100; const widthPct = ((clampedEnd - clampedStart) / DAY_MS) * 100; const color = colorMap.get(slot.block_id) ?? "#6b7280"; const startTime = slotStart.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }); const endTime = slotEnd.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }); return (
{slot.item.title}
); })}
{/* Hour labels */}
{[0, 6, 12, 18, 24].map((h) => ( {h.toString().padStart(2, "0")}:00 ))}
); } interface ScheduleSheetProps { channel: ChannelResponse | null; open: boolean; onOpenChange: (open: boolean) => void; } export function ScheduleSheet({ channel, open, onOpenChange }: ScheduleSheetProps) { const { data: schedule, isLoading, error } = useActiveSchedule(channel?.id ?? ""); const colorMap = schedule ? makeColorMap(schedule.slots) : new Map(); // Build day rows from valid_from to valid_until const days: { label: string; dayStart: Date }[] = []; if (schedule) { const start = new Date(schedule.valid_from); // Start at midnight of the first day const dayStart = new Date(start); dayStart.setHours(0, 0, 0, 0); const end = new Date(schedule.valid_until); const cursor = new Date(dayStart); while (cursor < end) { days.push({ label: cursor.toLocaleDateString(undefined, { weekday: "short", month: "short", day: "numeric" }), dayStart: new Date(cursor), }); cursor.setDate(cursor.getDate() + 1); } } const fmt = (iso: string) => new Date(iso).toLocaleString(undefined, { weekday: "short", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", hour12: false, }); return ( Schedule — {channel?.name}
{isLoading && (
)} {error && (
No active schedule. Generate one from the dashboard.
)} {schedule && ( <> {/* Meta */}
Generation #{schedule.generation} From {fmt(schedule.valid_from)} Until {fmt(schedule.valid_until)} {schedule.slots.length} slots
{/* Timeline per day */}
{days.map(({ label, dayStart }) => ( ))}
{/* Slot list */}

Slots

{schedule.slots.map((slot) => { const color = colorMap.get(slot.block_id) ?? "#6b7280"; const start = new Date(slot.start_at).toLocaleString(undefined, { weekday: "short", hour: "2-digit", minute: "2-digit", hour12: false, }); const durationMins = Math.round( (new Date(slot.end_at).getTime() - new Date(slot.start_at).getTime()) / 60000 ); return (

{slot.item.title}

{start} · {durationMins}m

); })}
)}
); }