/** The week desk — a calendar whose events are records, moved in place. */ import { useState } from "react"; import { Text } from "@lotics/ui/text"; import { colors, type ColorName } from "@lotics/ui/colors"; import { ActionMenu } from "@lotics/ui/action_menu"; import { Status } from "@lotics/ui/status"; import { Box } from "@lotics/ui/box"; import { Button } from "@lotics/ui/button"; import { Card, CardHeader, CardHeaderTitle, CardHeaderMeta } from "@lotics/ui/card"; import { Divider } from "@lotics/ui/divider"; import { Drawer, DrawerScrollArea } from "@lotics/ui/drawer"; import { ListItem } from "@lotics/ui/list_item"; import { ScrollArea } from "@lotics/ui/scroll_area"; import { Stack } from "@lotics/ui/stack"; import { CalendarView, addDays, isSameDay, startOfWeek, viewTitle, type CalendarEvent, } from "@lotics/ui/calendar"; // ───────────────────────────────────────────────────────────────────────────── // Template, Calendar — the delivery/schedule desk. One real // CalendarView (week grid) over the current week's deliveries / reconciliations / // customer visits, then a "Today" agenda card listing today's three slots. // // Grammar: zinc-50 canvas, header band (title + week label + Today), // calendar card at a real height, agenda card of pressable rows. Every slot // is a door: press (grid event or agenda row) opens the sequenced workspace // Drawer; agenda rows also carry the ⋯ quick-actions menu. Event colors carry // meaning: blue = delivery, emerald = reconciliation, amber = customer. // ───────────────────────────────────────────────────────────────────────────── interface AgendaMeta { diaDiem: string; trangThai: string; mau: ColorName; } // Mock data anchored to the real current week so the page is evergreen. const NOW = new Date(); const TODAY = addDays(NOW, 0); // start of today const WEEK_START = startOfWeek(TODAY, 1); const TODAY_IDX = Math.round((TODAY.getTime() - WEEK_START.getTime()) / 86_400_000); // Four weekday slots for the rest of the week — never colliding with today, // so the agenda below always shows exactly today's three slots. const SLOTS = [0, 1, 2, 3, 4, 5].filter((d) => d !== TODAY_IDX).slice(0, 4); function evt( id: string, day: Date, h: number, m: number, durMin: number, title: string, data: AgendaMeta, ): CalendarEvent { const start = new Date(day.getFullYear(), day.getMonth(), day.getDate(), h, m); // The chip's colour IS the row's colour — `data.mau` is the one statement of // it, so the two surfaces cannot drift apart. return { id, title, start, end: new Date(start.getTime() + durMin * 60_000), color: data.mau, data }; } const EVENTS: CalendarEvent[] = [ // Today — the dispatch desk's three slots. evt("hn-1", TODAY, 8, 0, 90, "Deliver APEX PLASTICS — VTD063518", { diaDiem: "Eastport industrial zone", trangThai: "Truck loaded", mau: "blue", }), evt("hn-2", TODAY, 10, 30, 60, "Reconcile delivery notes — HANDAN", { diaDiem: "Head office", trangThai: "Prepared", mau: "emerald", }), evt("hn-3", TODAY, 15, 0, 60, "Customer sample review — VITTORIA", { diaDiem: "Factory sample room", trangThai: "Awaiting confirmation", mau: "amber", }), // The rest of the week. evt("t-1", addDays(WEEK_START, SLOTS[0]), 8, 0, 90, "Deliver KOMASPEC — blanks 675×325", { diaDiem: "Northgate industrial park", trangThai: "Truck loaded", mau: "blue", }), evt("t-2", addDays(WEEK_START, SLOTS[1]), 14, 0, 60, "Reconcile delivery notes — NEWTECONS", { diaDiem: "Head office", trangThai: "Prepared", mau: "emerald", }), evt("t-3", addDays(WEEK_START, SLOTS[2]), 10, 0, 60, "Kick off the VITTORIA order", { diaDiem: "Online meeting", trangThai: "Awaiting confirmation", mau: "amber", }), evt("t-4", addDays(WEEK_START, SLOTS[3]), 8, 30, 90, "Deliver BRIGHTCELL BATTERIES — TS1250", { diaDiem: "Brightcell plant, Eastport", trangThai: "Truck loaded", mau: "blue", }), // All-week banner: an internal task spanning two mid-week days. { id: "kk-1", title: "Q2 stock count", start: addDays(WEEK_START, 2), end: addDays(WEEK_START, 4), allDay: true, color: "zinc", }, ]; const p2 = (n: number) => String(n).padStart(2, "0"); const fmtTime = (d: Date) => `${p2(d.getHours())}:${p2(d.getMinutes())}`; const fmtDate = (d: Date) => `${p2(d.getDate())}/${p2(d.getMonth() + 1)}`; // The drawer sequences over the week's timed slots in chronological order — // the dispatcher's triage rhythm: open one, ◀ ▶ / ←→ through the rest. The // all-day banner opens the same drawer but sits outside the sequence. const SEQUENCE = EVENTS.filter((e) => !e.allDay).sort( (a, b) => a.start.getTime() - b.start.getTime(), ); // Quick operations for a schedule slot — the ⋯ door on each agenda row // (press = workspace drawer; ⋯ = these). Destructive last. const SLOT_ACTIONS = [ { key: "sua", label: "Edit slot", icon: "pencil" as const, onPress: () => {} }, { key: "nhac", label: "Remind owner", icon: "bell" as const, onPress: () => {} }, { key: "huy", label: "Cancel slot", icon: "trash" as const, danger: true, onPress: () => {} }, ]; // Compact slot workspace — identity + key facts + the two real actions. function SlotWorkspace({ e }: { e: CalendarEvent }) { const thoiGian = e.allDay ? `${fmtDate(e.start)} – ${e.end ? fmtDate(e.end) : ""} (all day)` : `${fmtDate(e.start)}, ${fmtTime(e.start)} – ${e.end ? fmtTime(e.end) : ""}`; const rows: [string, React.ReactNode][] = [ ["Time", {thoiGian}], ...(e.data ? ([ ["Location", {e.data.diaDiem}], ["Status", ], ] as [string, React.ReactNode][]) : []), ]; return ( <> {rows.map(([label, value], i) => ( {i > 0 ? : null} {label} {value} ))}