/* ============================================================ ZENITH — Admin: Physical script workflow Paper II scripts are marked ON PAPER. The admin prints a copy of each student's uploaded answer sheet, hand-delivers it to a teacher (suggested from the digital claim), collects it back, and keys the marks in here. Each script is tracked through: not printed → printed → out for delivery → with teacher → collected → marks entered ============================================================ */ const PHYS_ORDER = ["not_printed", "printed", "out_for_delivery", "with_teacher", "collected", "marks_entered"]; function physStageMeta(t) { return { not_printed: { label: t("Not printed", "මුද්‍රණය නැත"), tone: "grey" }, printed: { label: t("Printed", "මුද්‍රණය කළා"), tone: "blue" }, out_for_delivery: { label: t("Out for delivery", "බෙදාහැරීමට"), tone: "amber" }, with_teacher: { label: t("With teacher", "ගුරු සතුව"), tone: "amber" }, collected: { label: t("Collected", "එකතු කළා"), tone: "blue" }, marks_entered: { label: t("Marks entered", "ලකුණු ඇතුළත්"), tone: "green" }, }; } function physSince(r) { return { printed: r.printedAt, out_for_delivery: r.dispatchedAt, with_teacher: r.handedAt, collected: r.collectedAt, marks_entered: r.enteredAt }[r.stage] || null; } function physAgo(iso) { if (!iso) return ""; const h = Math.round((Date.now() - new Date(iso).getTime()) / 3.6e6); if (h < 1) return "· now"; if (h < 24) return "· " + h + "h"; return "· " + Math.round(h / 24) + "d"; } function AdminPhysical() { const { lang } = useApp(); const t = (en, si) => tx(lang, en, si); const D = window.ZDATA.admin || {}; const rows = D.physical || []; const teachers = D.teachers || []; const [, force] = useState(0); const [entry, setEntry] = useState(null); // row being mark-entered const [dispatchFor, setDispatchFor] = useState(null); // row awaiting a dispatch (teacher + note) const [q, setQ] = useState(""); const [stage, setStage] = useState("all"); const [busy, setBusy] = useState(null); const refresh = async () => { await window.ZDATA_load("admin"); force(x => x + 1); }; if (entry) return { setEntry(null); await refresh(); }} />; const meta = physStageMeta(t); const print = async r => { setBusy(r.attemptId); try { await window.ZAPI.downloadScriptPDF(r.attemptId, r.sysId); await refresh(); } catch (e) { console.error("print failed", e); } finally { setBusy(null); } }; const advance = async (r, target) => { setBusy(r.attemptId); try { await window.ZAPI.advancePhysical(r.attemptId, target); await refresh(); } catch (e) { console.error("advance failed", e); } finally { setBusy(null); } }; const doDispatch = async (r, teacherAccountId, note) => { setBusy(r.attemptId); try { await window.ZAPI.advancePhysical(r.attemptId, "out_for_delivery", teacherAccountId, note); setDispatchFor(null); await refresh(); } catch (e) { console.error("dispatch failed", e); } finally { setBusy(null); } }; const returnUnmarked = async r => { const note = window.prompt(t("Why is this script coming back unmarked? (returns it to 'printed')", "මෙම පත්‍රය ලකුණු නොකර ආපසු එන්නේ ඇයි? ('මුද්‍රණය' තත්ත්වයට ආපසු යයි)")); if (!note || !note.trim()) return; setBusy(r.attemptId); try { await window.ZAPI.returnPhysical(r.attemptId, note.trim()); await refresh(); } catch (e) { console.error("return failed", e); } finally { setBusy(null); } }; const counts = {}; PHYS_ORDER.forEach(s => counts[s] = 0); rows.forEach(r => { counts[r.stage] = (counts[r.stage] || 0) + 1; }); const needle = q.trim().toLowerCase(); const filtered = rows.filter(r => (stage === "all" || r.stage === stage) && (!needle || r.sysId.toLowerCase().includes(needle) || (r.paper || "").toLowerCase().includes(needle) || (r.teacherName || "").toLowerCase().includes(needle) || (r.suggestedTeacherName || "").toLowerCase().includes(needle)) ); // per-row primary action const nextAction = r => { if (r.stage === "not_printed") return print(r)}>{t("Print", "මුද්‍රණය")}; if (r.stage === "printed") return setDispatchFor(r)}>{t("Dispatch", "බෙදාහැරීම")}; if (r.stage === "out_for_delivery") return advance(r, "with_teacher")}>{t("Handed over", "භාර දුන්නා")}; if (r.stage === "with_teacher") return advance(r, "collected")}>{t("Collected", "එකතු කළා")}; if (r.stage === "collected") return setEntry(r)}>{t("Enter marks", "ලකුණු ඇතුළත්")}; return {t("Done", "අවසන්")}; }; return (
{/* stage pipeline KPIs */}
{PHYS_ORDER.map(s => (
))}

{t("Physical script tracking", "භෞතික පත්‍ර ලුහුබැඳීම")}

{t("Print each script, hand it to the suggested teacher, collect it back, then key the marks in. Marks are credited to the teacher who marked on paper.", "සෑම පත්‍රයක්ම මුද්‍රණය කර, යෝජිත ගුරුවරයාට භාර දී, ආපසු එකතු කර, ලකුණු ඇතුළත් කරන්න. ලකුණු, කඩදාසියේ ලකුණු කළ ගුරුවරයාට හිමිවේ.")}

{rows.length === 0 ? (

{t("No active scripts to track yet.", "ලුහුබැඳීමට ක්‍රියාකාරී පත්‍ර නැත.")}

) : (
setQ(e.target.value)} placeholder={t("Search ID, paper, or teacher…", "හැඳුනුම, පත්‍රය, හෝ ගුරු සොයන්න…")} style={{ width: "100%", border: "1.5px solid var(--border)", borderRadius: "var(--r-sm)", padding: "9px 12px 9px 34px", fontSize: 13.5, outline: "none", fontFamily: "var(--font)" }} />
{[["all", t("All", "සියල්ල")]].concat(PHYS_ORDER.map(s => [s, meta[s].label])).map(f => ( ))}
{filtered.map(r => ( ))}
{t("System ID", "පද්ධති හැඳුනුම")} {t("Paper", "පත්‍රය")} {t("Deliver to", "භාර දෙන්නේ")} {t("Stage", "අදියර")} {t("Progress", "ප්‍රගතිය")} {t("Action", "ක්‍රියාව")}
{r.sysId} {r.paper} {r.notes &&
{t("Note:", "සටහන:")} {r.notes}
}
{r.teacherName ?
{r.teacherName}
: r.suggestedTeacherName ? {t("Suggest:", "යෝජනා:")} {r.suggestedTeacherName} : }
{meta[r.stage].label} {physSince(r) && {physAgo(physSince(r))}}
{r.marked}/{r.total || 6}
{nextAction(r)} {(r.stage === "out_for_delivery" || r.stage === "with_teacher") && ( )} {r.stage !== "not_printed" && r.stage !== "marks_entered" && ( )}
)}
{dispatchFor && setDispatchFor(null)} onConfirm={(tid, note) => doDispatch(dispatchFor, tid, note)} />}
); } /* Choose which teacher to hand the printed copy to (defaults to the digital-claim suggestion). */ function DispatchModal({ script, teachers, busy, onCancel, onConfirm }) { const { lang } = useApp(); const t = (en, si) => tx(lang, en, si); // Default to whoever is already assigned to mark this script (the digital claim holder, or the // physical delivery teacher if one is already set). Dispatching commits this teacher as the marker. const assignedId = script.suggestedTeacherAccountId || script.teacherAccountId || ""; const assignedName = script.suggestedTeacherName || script.teacherName || ""; const [teacherId, setTeacherId] = useState(assignedId); const [note, setNote] = useState(""); const differs = teacherId && assignedId && teacherId !== assignedId; const selStyle = { width: "100%", border: "1.5px solid var(--border)", borderRadius: "var(--r-sm)", padding: "10px 12px", fontSize: 14, outline: "none", fontFamily: "var(--font)", background: "#fff" }; return (
e.stopPropagation()} style={{ width: 480, maxWidth: "92vw", display: "flex", flexDirection: "column", gap: 14 }}>

{t("Dispatch for marking", "ලකුණු කිරීමට බෙදාහරින්න")}

{t("Hand the printed copy of", "මුද්‍රිත පිටපත")} {script.sysId} {t("to:", "භාර දෙන්න:")}

{assignedName ? {t("Assigned to", "පවරා ඇත්තේ")} {assignedName} {t("— dispatch keeps them, or pick someone else to reassign.", "— බෙදාහැරීමෙන් ඔවුන්ම තබා ගනී, නැතහොත් වෙනත් අයෙකු තෝරා නැවත පවරන්න.")} : t("No one is assigned yet — the teacher you pick becomes the marker from now on.", "තවම කිසිවෙකු පවරා නැත — ඔබ තෝරන ගුරුවරයා මෙතැන් සිට ලකුණුකරු වේ.")}

{differs && (
{t("This teacher differs from the current marker — any digital marks already entered will be cleared so they start fresh.", "මෙම ගුරුවරයා වත්මන් ලකුණුකරුගෙන් වෙනස් — දැනටමත් ඇතුළත් ඩිජිටල් ලකුණු ඉවත් වේ.")}
)}