/** The attendance desk — presence per person per day, corrected in place. */ import { useState } from "react"; import { Text } from "@lotics/ui/text"; import { colors, solid } from "@lotics/ui/colors"; import { ActionMenu } from "@lotics/ui/action_menu"; import { Avatar } from "@lotics/ui/avatar"; import { Status } from "@lotics/ui/status"; import { Box } from "@lotics/ui/box"; import { Button } from "@lotics/ui/button"; import { Card } from "@lotics/ui/card"; import { Divider } from "@lotics/ui/divider"; import { Drawer, DrawerScrollArea } from "@lotics/ui/drawer"; import { ScrollArea } from "@lotics/ui/scroll_area"; import { Stack } from "@lotics/ui/stack"; import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table"; import { cycleSort, sortBy, type SortState } from "@lotics/ui/sort_header"; import { MetricStrip } from "@lotics/ui/metric_strip"; import { Select } from "@lotics/ui/select"; import { StateMatrix, type StateMatrixState } from "@lotics/ui/state_matrix"; import { Tabs } from "@lotics/ui/tabs"; // ───────────────────────────────────────────────────────────────────────────── // Template, Attendance. The daily attendance desk: who is in, who // is late, who is out, plus the weekly per-person grid. Emerald = presence; // badge colors carry the status meaning (amber late / red absent / blue leave). // // Grammar: zinc-50 canvas, header + month Select, MetricStrip (four counts), // Tabs Today / This week. "Today" = one banded roster Card — each row // press-opens the employee workspace Drawer (◀ ▶ sequencing) and carries a // ⋯ ActionMenu with the day's quick fixes. "This week" = one StateMatrix — // a person per row, the weekdays named once across the top, a glyph per day; // a cell opens the same workspace Drawer. // ───────────────────────────────────────────────────────────────────────────── type TrangThai = "co_mat" | "di_muon" | "vang" | "nghi_phep"; type NgayCong = "du" | "muon" | "vang" | "phep"; interface NhanVien { id: string; ten: string; chucVu: string; vao?: string; ra?: string; trangThai: TrangThai; /** Minutes late — only for di_muon rows; feeds the badge label. */ muonPhut?: number; /** Hours worked today — only once the person has checked out. */ tongGio?: string; /** T2 → T6 of the current week. */ tuan: NgayCong[]; tongTuan: string; } const NHAN_VIEN: NhanVien[] = [ { id: "NV-01", ten: "Daniel Reed", chucVu: "Plant supervisor", vao: "07:52", ra: "17:05", trangThai: "co_mat", tongGio: "9,2h", tuan: ["du", "du", "du", "du", "du"], tongTuan: "42,1h" }, { id: "NV-02", ten: "Tessa Tran", chucVu: "Accountant", vao: "07:58", trangThai: "co_mat", tuan: ["du", "du", "muon", "du", "du"], tongTuan: "40,6h" }, { id: "NV-03", ten: "Martin Pike", chucVu: "Sales rep", vao: "08:12", trangThai: "di_muon", muonPhut: 12, tuan: ["muon", "du", "du", "muon", "muon"], tongTuan: "39,8h" }, { id: "NV-04", ten: "Helen Lee", chucVu: "Warehouse keeper", vao: "07:45", ra: "17:02", trangThai: "co_mat", tongGio: "9,3h", tuan: ["du", "du", "du", "du", "du"], tongTuan: "41,9h" }, { id: "NV-05", ten: "Nolan Hayes", chucVu: "Delivery driver", trangThai: "vang", tuan: ["du", "vang", "du", "du", "vang"], tongTuan: "24,3h" }, { id: "NV-06", ten: "Maria Vu", chucVu: "HR & admin", trangThai: "nghi_phep", tuan: ["du", "du", "phep", "phep", "phep"], tongTuan: "16,4h" }, { id: "NV-07", ten: "Bruno Dale", chucVu: "Machine operator", vao: "07:49", trangThai: "co_mat", tuan: ["du", "du", "du", "du", "du"], tongTuan: "42,0h" }, { id: "NV-08", ten: "Nina Burke", chucVu: "Sales rep", vao: "08:27", trangThai: "di_muon", muonPhut: 27, tuan: ["du", "muon", "du", "du", "muon"], tongTuan: "40,2h" }, { id: "NV-09", ten: "Theo Nash", chucVu: "Maintenance", vao: "07:56", ra: "16:45", trangThai: "co_mat", tongGio: "8,8h", tuan: ["du", "du", "du", "du", "du"], tongTuan: "41,7h" }, ]; const TRANG_THAI: Record = { co_mat: { label: "Present", color: "emerald" }, di_muon: { label: "Late", color: "amber" }, vang: { label: "Absent", color: "red" }, nghi_phep: { label: "On leave", color: "blue" }, }; const NGAY_CONG: (StateMatrixState & { key: NgayCong })[] = [ { key: "du", label: "Full day", color: "emerald" }, { key: "muon", label: "Late", color: "amber" }, { key: "vang", label: "Absent", color: "red", shape: "hollow" }, { key: "phep", label: "On leave", color: "blue", shape: "dot" }, ]; const ngayCong = (k: NgayCong) => NGAY_CONG.find((n) => n.key === k) ?? NGAY_CONG[0]; const THANG = [ { label: "April 2026", value: "2026-04" }, { label: "May 2026", value: "2026-05" }, { label: "June 2026", value: "2026-06" }, ]; const TAB = [ { label: "Today", value: "hom_nay" }, { label: "This week", value: "tuan_nay" }, ]; const NGAY_TUAN = ["T2", "T3", "T4", "T5", "T6"]; const NGAY_TUAN_DAY = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]; const COLUMNS: TableColumn[] = [ { key: "ten", label: "Employee", flex: 1, sortable: true, lines: 2 }, { key: "vao", label: "In – out", width: 112, align: "right", sortable: true }, { key: "trangThai", label: "Status", width: 124, sortable: true }, { key: "tongGio", label: "Hours", width: 72, align: "right", sortable: true }, ]; function badgeLabel(nv: NhanVien): string { const tt = TRANG_THAI[nv.trangThai]; return nv.trangThai === "di_muon" && nv.muonPhut !== undefined ? `${tt.label} ${nv.muonPhut}ph` : tt.label; } /** One weekday marker in the drawer's day list — the same glyph the week grid draws. */ function DayCell({ ngay }: { ngay: NgayCong }) { const s = ngayCong(ngay); const hollow = s.shape === "hollow"; const dot = s.shape === "dot"; return ( ); } /** The day's quick fixes for one person — the ⋯ door; the row press is the workspace door. */ function HomNayMenu({ nv }: { nv: NhanVien }) { return ( {} }, { key: "nghi_phep", label: "Record leave", icon: "calendar-off", onPress: () => {} }, { key: "nhac", label: "Remind to clock in", icon: "bell", disabled: nv.vao !== undefined, onPress: () => {} }, { key: "danh_dau_vang", label: "Mark absent", icon: "ban", danger: true, disabled: nv.trangThai === "vang", onPress: () => {} }, ]} /> ); } function HomNayRow({ nv, selected, onPress }: { nv: NhanVien; selected: boolean; onPress: () => void }) { const tt = TRANG_THAI[nv.trangThai]; return ( } > {nv.ten} {nv.chucVu} {`${nv.vao ?? "—"} – ${nv.ra ?? "—"}`} {nv.tongGio ?? "—"} ); } function KVRow({ label, value }: { label: string; value: string }) { return ( {label} {value} ); } // The drawer body — compact: identity + today's facts + the week at a glance. // Keyed by nv.id from the caller so it resets when ◀ ▶ steps. function NhanVienWorkspace({ nv }: { nv: NhanVien }) { const tt = TRANG_THAI[nv.trangThai]; return ( <> {nv.chucVu} This week {nv.tongTuan} {nv.tuan.map((ngay, i) => ( {NGAY_TUAN[i]} {ngayCong(ngay).label} ))} {/* pinned footer — the workspace's one primary fix action */} Missed punches get fixed here — every change is audited.