/* ============================================================ ZENITH — Admin (Open Ai Pvt Ltd) - Overview: institute KPIs, papers, classes - Batch & Release (SRS 3.1): Excel/CSV parser → staged "Hidden" records → Saturday-night CRON flips them to "Published", pushing Z-Scores & Island Ranks to student dashboards. ============================================================ */ function AdminOverview() { const { lang } = useApp(); const A = window.ZDATA.admin; const t = (en, si) => tx(lang, en, si); const statusTone = s => ({ Active: "green", Released: "blue", Published: "green", Marking: "amber", Draft: "grey", Archived: "grey" }[s] || "grey"); const [papers, setPapers] = useState(A.papers); const [building, setBuilding] = useState(false); const [viewing, setViewing] = useState(null); // examId currently open in the paper viewer return (
{building && setBuilding(false)} onSave={p => { setPapers(ps => [p, ...ps]); setBuilding(false); }} />} {viewing && setViewing(null)} />}
{fmtInt(A.stats.students)}
{t("Active students", "සක්‍රිය සිසුන්")}
{fmtInt(A.stats.papers)}
{t("Papers published", "ප්‍රකාශිත පත්‍ර")}
{fmtInt(A.stats.pendingMarking)}
{t("Scripts to mark", "ලකුණු කිරීමට")}
{A.stats.avgZ ?? "—"}
{t("Avg. predicted Z", "සාමාන්‍ය පුරෝකථන Z")}

{t("Papers", "ප්‍රශ්න පත්‍ර")}

setBuilding(true)}>{t("New paper", "නව පත්‍රය")}
{papers.map((p, i) => { const isDraft = p.status === "Draft"; const publish = async () => { if (!isDraft || !p.id) return; try { await window.ZAPI.publishPaper(p.id); } catch (e) { console.error("publish failed", e); return; } setPapers(ps => ps.map(x => x === p ? { ...x, status: "Published" } : x)); }; return ( ); })}
{t("Paper", "පත්‍රය")}{t("Sat", "ලියූ")} {t("Marking progress", "ලකුණු ප්‍රගතිය")} {t("Avg / 200", "සාමා. / 200")}{t("Status", "තත්ත්වය")}
{p.title} {p.sat}
{p.marked}/{p.sat}
{p.avg}
{p.status}{isDraft ? " · " + t("publish", "ප්‍රකාශ") : ""} {p.id && ( )}

{t("Classes", "පන්ති")}

{A.classes.map(c => (
Z {c.avgZ}

{c.name}

{c.students} {t("students", "සිසුන්")} · {c.teacher}

))}
); } /* ============================================================ Batch upload + staging + scheduled publish (CRON) ============================================================ */ function AdminBatch() { const { lang } = useApp(); const t = (en, si) => tx(lang, en, si); const c = useCountdown(window.ZDATA.release.releaseHour); const inputRef = useRef(null); // lifecycle: existing staged rows → upload(staged Hidden) → published const [rows, setRows] = useState(window.ZDATA.staging || []); const [fileName, setFileName] = useState(""); const [published, setPublished] = useState(false); const [cyc, setCyc] = useState(window.ZDATA.admin && window.ZDATA.admin.cycleId); // ensure a cycle exists to upload against (a fresh system has none) const ensureCycle = async () => { if (cyc) return cyc; const c = await window.ZAPI.createCycle(); setCyc(c.id); return c.id; }; // sample/demo fallback (no real file) — mirrors any existing staging const ingest = name => { setFileName(name || "z-scores.xlsx"); setRows((window.ZDATA.staging || []).map(r => ({ ...r, state: "Hidden" }))); setPublished(false); }; // real Excel/CSV upload -> backend parse + hidden staging const onFile = async files => { if (!files || !files[0]) return; try { const id = await ensureCycle(); const r = await window.ZAPI.uploadResults(id, files[0], true); setRows(r.staging || []); setFileName(files[0].name); setPublished(false); } catch (e) { console.error("upload failed", e); } }; const runCron = async () => { try { const id = await ensureCycle(); const r = await window.ZAPI.publishNow(id); setRows(r.rows || rows.map(x => ({ ...x, state: "Published" }))); setPublished(true); } catch (e) { console.error("publish failed", e); setRows(rs => rs.map(r => ({ ...r, state: "Published" }))); setPublished(true); } }; const staged = rows.length; return (
{/* Step 1 — Excel/CSV parser */}

{t("Step 1 · Batch result upload", "පියවර 1 · කාණ්ඩ ප්‍රතිඵල උඩුගත")}

.xlsx · .csv

{t("Upload the offline-calculated file mapping System IDs to their Z-Score and Island Rank. The parser stages records in a hidden state.", "පද්ධති හැඳුනුම් ඔවුන්ගේ Z අගය හා දිවයින් ශ්‍රේණියට සම්බන්ධ කරන නොබැඳිව ගණනය කළ ගොනුව උඩුගත කරන්න. විග්‍රහකය වාර්තා සැඟවුණු තත්ත්වයක තබයි.")}

inputRef.current.click()} onDragOver={e => e.preventDefault()} onDrop={e => { e.preventDefault(); onFile(e.dataTransfer.files); }} style={{ border: "2px dashed var(--border-strong)", borderRadius: "var(--r-md)", padding: "32px 20px", textAlign: "center", cursor: "pointer", background: "var(--grey-5)" }}>

{t("Drop your .xlsx / .csv here, or click to browse", "ඔබගේ .xlsx / .csv මෙහි දමන්න, හෝ ක්ලික් කරන්න")}

{t("Columns: System ID · Z-Score · Island Rank · District Rank", "තීරු: පද්ධති හැඳුනුම · Z අගය · දිවයින් ශ්‍රේණිය · දිස්ත්‍රික් ශ්‍රේණිය")}

onFile(e.target.files)} />
{fileName && (
{fileName} · {t("parsed", "විග්‍රහ කළා")} {staged} {t("records into staging", "වාර්තා staging වෙත")}
)}
{/* Step 2 — staging table */} {staged > 0 && (

{t("Step 2 · Staged records", "පියවර 2 · staged වාර්තා")}

{published ? t("Now visible on student dashboards.", "දැන් සිසු උපකරණ පුවරු මත දෘශ්‍යමානයි.") : t("Invisible to students until release.", "නිකුත් වන තුරු සිසුන්ට නොපෙනේ.")}

{published ? t("Published", "නිකුත් කළා") : t("Hidden", "සැඟවුණු")}
{rows.map(r => ( ))}
{t("System ID", "පද්ධති හැඳුනුම")} {t("Z-Score", "Z අගය")} {t("Island Rank", "දිවයින් ශ්‍රේණිය")} {t("District Rank", "දිස්ත්‍රික් ශ්‍රේණිය")} {t("State", "තත්ත්වය")}
{r.sysId} {r.z.toFixed(2)} #{r.islandRank.toLocaleString()} #{r.districtRank} {r.state === "Published" ? t("Published", "නිකුත්") : t("Hidden", "සැඟවුණු")}
)} {/* Step 3 — scheduled publish CRON */}

{t("Step 3 · Scheduled publishing protocol", "පියවර 3 · සැලසුම් කළ ප්‍රකාශන ක්‍රමවේදය")}

{t("Saturday-night automated release", "සෙනසුරාදා රාත්‍රී ස්වයංක්‍රීය නිකුතුව")}

{t("A server-side CRON job (node-cron / Celery) runs every Saturday at 9:00 PM, flipping all staged records from Hidden to Published and pushing them to student dashboards at once.", "සේවාදායක CRON කාර්යයක් (node-cron / Celery) සෑම සෙනසුරාදාම රාත්‍රී 9.00ට ක්‍රියාත්මක වී, සියලු staged වාර්තා Hidden සිට Published වෙත මාරු කර එකවර සිසු උපකරණ පුවරු වෙත යවයි.")}

{t("Next scheduled run", "ඊළඟ සැලසුම් කළ ක්‍රියාව")}
{[[c.d, t("d", "දි")], [c.h, t("h", "පැ")], [c.m, t("m", "මි")], [c.s, t("s", "ත")]].map((u, i) => (
{String(u[0]).padStart(2, "0")}
{u[1]}
))}
{t("For demos you can trigger the job manually.", "ආදර්ශන සඳහා ඔබට කාර්යය අතින් ක්‍රියාත්මක කළ හැක.")} {published ? {t("Released to all students", "සියලු සිසුන්ට නිකුත් කළා")} : {t("Run scheduled publish now", "දැන් ප්‍රකාශනය ක්‍රියාත්මක කරන්න")}}
); } window.AdminOverview = AdminOverview; window.AdminBatch = AdminBatch;