/* ==================================================================
Admin → Topics
------------------------------------------------------------------
Curate the topic catalog teachers pick from when authoring a test.
Topics are nested under chapters: pick class → subject → chapter
on the left, then manage that chapter's topic list on the right.
No FK from tests → topics today (tests store the topic name as
TEXT), so deletes are unconditional.
IIFE wrap — see the matching note in screen-admin-chapters.jsx.
babel-standalone transpiles `const` to `var` in the global scope,
so without this wrapper helpers like `ErrBanner` collide with the
identically-named locals in chapters + taxonomy.
================================================================== */
(() => {
const { Icon: TIcon } = window.AppUI;
const TopicsScreen = () => {
const [subjects, setSubjects] = React.useState([]);
const [subjectsErr, setSubjectsErr] = React.useState(null);
const [classLevels, setClassLevels] = React.useState([]);
const [classErr, setClassErr] = React.useState(null);
// Three-step picker: class → subject → chapter. Each narrows the next.
const [pickedClass, setPickedClass] = React.useState("");
const [pickedSubjectId, setPickedSubjectId] = React.useState("");
const [chapters, setChapters] = React.useState([]);
const [chaptersErr, setChaptersErr] = React.useState(null);
const [chaptersLoading, setChaptersLoading] = React.useState(false);
const [pickedChapterId, setPickedChapterId] = React.useState("");
// Bootstrap — subjects + distinct class levels.
React.useEffect(() => {
setSubjectsErr(null);
window.AppApi.get("/admin/subjects").then(setSubjects)
.catch((e) => setSubjectsErr(String(e.message || e)));
}, []);
React.useEffect(() => {
setClassErr(null);
window.AppApi.get("/admin/class-sections")
.then((sections) => {
const uniq = Array.from(new Set((sections || []).map((s) => s.class_level).filter(Boolean))).sort();
setClassLevels(uniq);
})
.catch((e) => setClassErr(String(e.message || e)));
}, []);
// Whenever the (class × subject) pair becomes resolvable, fetch the chapter
// list it contains. Clears the previously-picked chapter if it no longer
// appears in the new list so the right panel doesn't show stale topics.
React.useEffect(() => {
if (!pickedClass || !pickedSubjectId) { setChapters([]); setPickedChapterId(""); return; }
setChaptersLoading(true); setChaptersErr(null);
const q = new URLSearchParams({ subject_id: pickedSubjectId, class_level: pickedClass });
window.AppApi.get(`/admin/chapters?${q.toString()}`)
.then((rows) => {
setChapters(rows);
if (pickedChapterId && !rows.some((r) => r.id === pickedChapterId)) setPickedChapterId("");
})
.catch((e) => setChaptersErr(String(e.message || e)))
.finally(() => setChaptersLoading(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pickedClass, pickedSubjectId]);
return (
Admin · Catalog
Topics
Topics are the finer-grained tag under each chapter — what a teacher reaches for when "Chapter 4: Stoichiometry" is too broad. Pick a class, subject and chapter on the left to manage that chapter's topics on the right.