import { useCallback, useEffect, useState } from "react"; import { api } from "../lib/api"; interface Item { id: number; position: number; title: string; zendesk_tickets: string; jira_key: string; jira_status: string; body: string; links: string[]; state: string; remove_at_end_of_shift: boolean; first_raised_on: string | null; carried_from_id: number | null; } interface RunpodRow { machine_id: string; name: string; colour: string; zendesk_ticket: string; jira_key: string; jira_status: string; gpu_reserved: number; gpu_total: number; last_error: string; next_steps: string; hours_unlisted: number | null; unlist_count: number; } interface Doc { id: number; title: string; shift: string; shift_date: string; handing_to: string; status: string; team_members: string; significant_issues_checked: boolean; hubspot_checked: boolean; total_open_tickets: number | null; member_checks: { name: string; hs_checked: boolean; jira_checked: boolean }[]; other_comments: string; item_count: number; items: Item[]; } const STATES: Record = { pending_infra: "Pending Infra", pending_customer: "Pending customer", pending_runpod: "Pending RunPod", pending_rma: "Pending RMA", pending_cx: "Pending CX", in_progress: "In progress", monitoring: "Monitoring", no_further_engagement: "No further engagement", done: "Done", }; const COLOURS: Record = { red: { dot: "#f2545b", text: "Blocked from relisting — recurring issue" }, purple: { dot: "#a97bf0", text: "Pending RunPod" }, yellow: { dot: "#e0a336", text: "Pending Infrastructure / DC team" }, blue: { dot: "#59a0f5", text: "Pending removal from the RunPod platform" }, green: { dot: "#35c46a", text: "Stress testing >24h — GPUs may look reserved" }, white: { dot: "#8a93a5", text: "Actionable by CX" }, }; const blankItem = (): Partial => ({ title: "", zendesk_tickets: "", jira_key: "", jira_status: "", body: "", links: [], state: "in_progress", remove_at_end_of_shift: false, }); export default function HandoverPage() { const [doc, setDoc] = useState(null); const [runpod, setRunpod] = useState([]); const [editing, setEditing] = useState | null>(null); const [saved, setSaved] = useState(""); const [busy, setBusy] = useState(false); const load = useCallback(async () => { const r = await api.get<{ handover: Doc | null; runpod: RunpodRow[] }>("/api/handover/current"); setDoc(r.handover); setRunpod(r.runpod ?? []); }, []); useEffect(() => { void load(); }, [load]); const flash = (m: string) => { setSaved(m); setTimeout(() => setSaved(""), 2200); }; const saveDoc = async (patch: Partial) => { if (!doc) return; const next = await api.post(`/api/handover/${doc.id}`, { ...doc, ...patch }); setDoc(next); flash("Saved"); }; const saveItem = async () => { if (!doc || !editing) return; setBusy(true); try { const path = editing.id ? `/api/handover/${doc.id}/items/${editing.id}` : `/api/handover/${doc.id}/items`; setDoc(await api.post(path, editing)); setEditing(null); flash("Item saved"); } finally { setBusy(false); } }; const handOver = async () => { if (!doc) return; if (!confirm("Close this shift and start the next one? Live items carry forward; " + "anything done or flagged 'remove at end of shift' is left behind.")) return; const r = await api.post<{ next: Doc; carried: number }>(`/api/handover/${doc.id}/hand-over`); setDoc(r.next); flash(`Handed over — ${r.carried} items carried forward`); }; if (!doc) { return (

No handover yet

); } return (

{doc.title}

{doc.shift} → {doc.handing_to} {doc.status.replace("_", " ")} {saved && {saved}}

Shift

saveDoc({ team_members: e.target.value })} /> saveDoc({ total_open_tickets: Number(e.target.value) })} />

Key updates ({doc.items.length})

{doc.items.map((it) => (
{it.title || "(untitled)"} {it.carried_from_id && carried over} {it.remove_at_end_of_shift && remove at end of shift} {STATES[it.state] ?? it.state}
{it.zendesk_tickets && Zendesk {it.zendesk_tickets}} {it.jira_key && {it.jira_key} {it.jira_status}}
{it.body &&
{it.body}
} {(it.links ?? []).map((l) => ( ))}
))} {editing && (
setEditing({ ...editing, title: e.target.value })} />
setEditing({ ...editing, zendesk_tickets: e.target.value })} />
setEditing({ ...editing, jira_key: e.target.value })} />
setEditing({ ...editing, jira_status: e.target.value })} />