/* ================================================================== App root — orchestrates screens + tweaks ------------------------------------------------------------------ The cockpit is a single column: the pill top bar (brand + section pills + sub-nav) sits above a full-width content area. Section → screen mapping lives in shell.jsx's NAV_SECTIONS, which the top bar and this router both read, so adding a screen is one edit there plus a case below. ================================================================== */ const KTopbar = window.AppUI.CockpitTopbar; const KRadialNav = window.AppUI.CockpitRadialNav; const KIcon = window.AppUI.Icon; // `accent: ""` means "use --accent from theme.css". Picking a swatch in the // Tweaks panel overrides it at runtime for this browser only. const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "", "density": "comfortable" }/*EDITMODE-END*/; // Landing screen. `?screen=` is how the installed app's home-screen shortcuts // (manifest.webmanifest) open straight into Staff Attendance / Finance // instead of always dropping the user on the dashboard. Unknown values fall // through to the dashboard rather than rendering an empty router default. const KNOWN_SCREENS = new Set([ "dashboard", "academics", "staff-attendance", "student-attendance", "holidays", "parents-overview", "assessment-overview", "finance", "library", "online-tests", "evaluate", "report", "answer-key", "doubts", "ai-review", "notifications", "teachers", "students", "parents", "announcements", "personnel", "taxonomy", "chapters", "topics", "exams", "parent-complaints", "sheet-builder", ]); const initialScreen = () => { try { const want = new URLSearchParams(window.location.search).get("screen"); if (want && KNOWN_SCREENS.has(want)) return want; } catch { /* no URL access — fall through */ } return "dashboard"; }; const App = () => { const [screen, setRawScreen] = useState(initialScreen); const [pickerOpen, setPickerOpen] = useState(false); const [evalTest, setEvalTest] = useState(null); const [authoringMeta, setAuthoringMeta] = useState(null); const [authoringInitial, setAuthoringInitial] = useState(null); const [pendingSaveHandler, setPendingSaveHandler] = useState(null); const [tweaks, setTweak] = (typeof useTweaks === "function") ? useTweaks(TWEAK_DEFAULTS) : [TWEAK_DEFAULTS, () => {}]; // Intercept screen change: opening "evaluate" without a chosen test pops the picker first. // If the library/upload-sheets flow set window.APP.EVAL_CONTEXT first, honour it directly. const setScreen = (next) => { if (next === "evaluate") { const ctx = window.APP.EVAL_CONTEXT; if (ctx?.test) { setEvalTest(ctx.test); setRawScreen("evaluate"); return; } if (!evalTest) { setPickerOpen(true); return; } } setRawScreen(next); // Landing on a new screen should start at the top of it — without this a // jump from the foot of a long list opens the next screen mid-page. window.scrollTo({ top: 0, behavior: "instant" }); }; useEffect(() => { if (tweaks.accent) document.documentElement.style.setProperty('--accent', tweaks.accent); else document.documentElement.style.removeProperty('--accent'); const density = tweaks.density; document.body.style.fontSize = density === "compact" ? "12px" : density === "spacious" ? "14px" : "13px"; }, [tweaks]); // Cross-component navigation hook. Used by the notifications panel (in // shell.jsx) to jump to the submissions view for a specific test without // threading setScreen through every component. useEffect(() => { window.APP = window.APP || {}; window.APP.navigate = (target, opts = {}) => { if (target === "evaluate" && opts.testDisplayId) { const test = (window.APP.TESTS || []).find(t => t.id === opts.testDisplayId); if (test) { window.APP.EVAL_CONTEXT = { test }; setEvalTest(test); setRawScreen("evaluate"); return; } setPickerOpen(true); return; } if (target === "doubts" && opts.doubtId) { // Preselect a specific thread when arriving from a notification. window.APP.DOUBTS_CONTEXT = { doubtId: opts.doubtId, tab: opts.tab || "open" }; } setScreen(target); }; return () => { if (window.APP) delete window.APP.navigate; }; }, [evalTest]); const Screen = () => { switch(screen) { /* ---- Cockpit overview screens ---- */ case "dashboard": return typeof window.CockpitDashboard !== "undefined" ? : ; case "academics": return typeof window.CockpitAcademics !== "undefined" ? : ; case "staff-attendance": return typeof window.CockpitStaffAttendance !== "undefined" ? : ; case "holidays": return typeof window.CockpitHolidays !== "undefined" ? : ; case "student-attendance": return typeof window.CockpitStudentAttendance !== "undefined" ? : ; case "parents-overview": return typeof window.CockpitParents !== "undefined" ? : ; case "assessment-overview": return typeof window.CockpitAssessment !== "undefined" ? : ; case "finance": return typeof window.CockpitFinance !== "undefined" ? : ; /* ---- Existing deep screens ---- */ case "library": return { setAuthoringMeta(meta); setPendingSaveHandler(() => saveHandler); setAuthoringInitial(initial || null); setRawScreen("manual"); }}/>; case "manual": return { if (pendingSaveHandler) await pendingSaveHandler(t); setAuthoringMeta(null); setAuthoringInitial(null); setRawScreen("library"); }} onCancel={() => { setAuthoringMeta(null); setAuthoringInitial(null); setRawScreen("library"); }}/>; case "report": return ; case "evaluate": return { setEvalTest(null); setPickerOpen(true); }} setScreen={setScreen}/>; case "answer-key": return typeof window.AnswerKeyAuthoring !== "undefined" ? : ; case "online-tests": return typeof window.OnlineTestsScreen !== "undefined" ? : ; case "teachers": return typeof window.TeachersScreen !== "undefined" ? : ; case "taxonomy": return typeof window.TaxonomyScreen !== "undefined" ? : ; case "chapters": return typeof window.ChaptersScreen !== "undefined" ? : ; case "topics": return typeof window.TopicsScreen !== "undefined" ? : ; case "students": return typeof window.StudentsScreen !== "undefined" ? : ; case "parents": return typeof window.AdminParentsScreen !== "undefined" ? : ; case "announcements": return typeof window.AdminAnnouncementsScreen !== "undefined" ? : ; case "doubts": return typeof window.DoubtsScreen !== "undefined" ? : ; case "ai-review": return typeof window.AiReviewScreen !== "undefined" ? : ; case "notifications": return typeof window.NotificationsScreen !== "undefined" ? : ; case "exams": return typeof window.ExamsScreen !== "undefined" ? : ; case "parent-complaints": return typeof window.ParentComplaintsScreen !== "undefined" ? : ; case "personnel": return typeof window.PersonnelScreen !== "undefined" ? : ; case "sheet-builder": return typeof window.SheetBuilderScreen !== "undefined" ? : ; default: return ; } }; return ( <>
{typeof window.TestPickerModal !== "undefined" && ( setPickerOpen(false)} onPick={(t) => { setEvalTest(t); setPickerOpen(false); setRawScreen("evaluate"); }}/> )}
{/* Hidden above 760px by CSS — see .cx-radialnav. */}
{/* Lives outside the router: a "question reported" notification can fire from any screen, and the modal listens on a window event. */} {typeof window.AppUI?.EditQuestionModal !== "undefined" && } {typeof TweaksPanel !== "undefined" && ( setTweak("accent", v)}/> setTweak("density", v)}/> )} ); }; const Placeholder = ({ name, desc }) => (
{name}{desc || "Coming soon."}
); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(
Loading…
, ); // Take down the inline boot screen from index.html. Called after the first // real render — either the app or the login form — so the user never sees a // blank page between "scripts finished" and "something is on screen". // // The rAF matters: root.render() only SCHEDULES work, so removing the overlay // on the same tick reveals an empty #root for a frame. Waiting one frame lets // React commit first. The timeout is a backstop — if bootstrap throws in a way // that never reaches a render, the boot screen must still come down rather // than trapping the user behind it forever. let bootDismissed = false; function dismissBoot() { if (bootDismissed) return; bootDismissed = true; const el = document.getElementById("cx-boot"); if (!el) return; requestAnimationFrame(() => { el.classList.add("gone"); setTimeout(() => el.remove(), 320); }); } setTimeout(dismissBoot, 15000); async function renderRoot() { try { await window.bootstrapApp(); } catch (e) { console.error("[bootstrap] failed:", e); } if (window.APP.NEEDS_LOGIN) { root.render(); } else { root.render(); } dismissBoot(); } renderRoot();