// Status labels, pills, job names and due dates. Copy from the old build where it had one. import type { PacketCard, PacketRead, PacketStatus, ProjectStatus, StageCounts } from "../api/types"; export const STATUS_LABEL: Record = { draft: "Ready to release", warehouse: "With warehouse", ready: "Ready to assign", assigned: "Sent to station", in_progress: "Working", review: "Supervisor review", qc: "Q.C. inspection", complete: "Routed", filed: "Filed in data book", }; export function statusLabel(s: string): string { return (STATUS_LABEL as Record)[s] ?? s; } /** The pill class for a status: "pill st-". */ export function pillClass(s: string): string { return `pill st-${s}`; } export const SHOP: readonly PacketStatus[] = ["ready", "assigned", "in_progress", "review"]; export const IN_SHOP_OR_QC: readonly PacketStatus[] = [...SHOP, "qc"]; /** "L999001 · WELD-PART". */ export function jobName(p: Pick | Pick): string { return p.op ? `${p.unit.number} · ${p.op}` : p.unit.number; } export const PROJECT_STATUS_LABEL: Record = { open: "Open", hold: "On hold", closed: "Closed" }; /** The old projPill colours: open green, on hold pink, closed grey. */ export function projectPillClass(s: ProjectStatus): string { return `pill ${s === "open" ? "st-in_progress" : s === "hold" ? "st-review" : "st-filed"}`; } /** A flag text that names a hard problem shows in pink, the rest in amber. */ export function flagClass(flag: string): string { return /couldn't|not in|mismatch|No approved|Not released/i.test(flag) ? "due over" : "due soon"; } const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; /** The old dueInfo(): "Due Oct 20 '26 · 4 d left", with a class for colour. */ export function dueInfo(due: string | null | undefined, closed = false, today: Date = new Date()): { txt: string; cls: string } { if (!due) return { txt: "No due date", cls: "due muted" }; const d = new Date(`${due.slice(0, 10)}T12:00:00`); if (Number.isNaN(d.getTime())) return { txt: `Due ${due}`, cls: "due" }; const noon = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 12); const days = Math.round((d.getTime() - noon.getTime()) / 86_400_000); const lab = `${MONTHS[d.getMonth()] ?? ""} ${d.getDate()} '${String(d.getFullYear()).slice(2)}`; if (closed) return { txt: `Due ${lab}`, cls: "due muted" }; if (days < 0) return { txt: `Due ${lab} · ${-days} d overdue`, cls: "due over" }; if (days <= 7) return { txt: `Due ${lab} · ${days === 0 ? "today" : `${days} d left`}`, cls: "due soon" }; return { txt: `Due ${lab} · ${days} d left`, cls: "due" }; } /** The open change orders on a card, as a count. */ export function openCoCount(p: Pick): number { return Array.isArray(p.open_cos) ? p.open_cos.length : Number(p.open_cos) || 0; } /** Sum of the work orders' target hours. */ export function targetHours(p: Pick): number { return p.work_orders.reduce((a, w) => a + (Number(w.target_hrs) || 0), 0); } /** "001" from "1", "7A" stays "7A". The server does the same. */ export function normLot(v: string): string { const t = v.trim().toUpperCase(); return /^\d{1,3}$/.test(t) ? t.padStart(3, "0") : t; } export const RX_LOT = /^[A-Z0-9]{1,3}$/; export const RX_PROJECT = /^[A-Z0-9][A-Z0-9-]{0,19}$/; /** Lots counting up from a numeric start: ("008", 3) -> 008, 009, 010. Null when the start is not digits. */ export function lotSequence(start: string, count: number): string[] | null { const s = start.trim(); if (!/^\d{1,3}$/.test(s) || !Number.isInteger(count) || count < 1) return null; const b = Number(s); if (b + count - 1 > 999) return null; return Array.from({ length: count }, (_, i) => String(b + i).padStart(3, "0")); } /** The next free numeric lot after the ones in use. */ export function nextLot(lots: readonly string[]): string { const nums = lots.filter((l) => /^\d+$/.test(l)).map(Number); const n = nums.length ? Math.max(...nums) + 1 : 1; return String(Math.min(n, 999)).padStart(3, "0"); } /** Packet counts with every stage present. */ export function stageCounts(c: Partial | undefined | null): StageCounts { return { draft: 0, warehouse: 0, shop: 0, qc: 0, complete: 0, filed: 0, total: 0, ...(c ?? {}) }; } /** Counts of a list of cards, the same way. */ export function countCards(list: readonly Pick[]): StageCounts { const c = stageCounts(null); for (const p of list) { c.total++; if (p.status === "draft") c.draft++; else if (p.status === "warehouse") c.warehouse++; else if ((SHOP as readonly string[]).includes(p.status)) c.shop++; else if (p.status === "qc") c.qc++; else if (p.status === "complete") c.complete++; else if (p.status === "filed") c.filed++; } return c; }