/* ================================================================== Admin → Teachers ------------------------------------------------------------------ The cockpit is the admin panel. Teachers themselves use the mobile app. This screen lets the admin add a teacher, assign them to a set of (class × subject) pairs (controls which tests they can author + grade on mobile), reset passwords, and deactivate (which immediately revokes any live mobile session). ================================================================== */ const { Icon: AdIcon } = window.AppUI; const ROLE_LABEL = { admin: "Admin", hod: "HoD", teacher: "Teacher", coordinator: "Coordinator", boarder: "Boarder", support_staff: "Support Staff" }; const TeachersScreen = () => { const [teachers, setTeachers] = React.useState([]); const [loading, setLoading] = React.useState(true); const [err, setErr] = React.useState(null); const [selectedId, setSelectedId] = React.useState(null); const [addOpen, setAddOpen] = React.useState(false); const [credentialFlash, setCredentialFlash] = React.useState(null); // { username, temp_password } const [search, setSearch] = React.useState(""); const [resets, setResets] = React.useState([]); // pending password-reset requests const [actBusy, setActBusy] = React.useState(null); // id currently being approved/rejected const refresh = React.useCallback(async () => { setLoading(true); setErr(null); try { const [t, pr] = await Promise.all([ window.AppApi.get("/admin/teachers"), window.AppApi.get("/password-resets?status=pending").catch(() => []), ]); setTeachers(t); setResets(pr || []); } catch (e) { setErr(String(e.message || e)); } finally { setLoading(false); } }, []); React.useEffect(() => { refresh(); }, [refresh]); // Self-registered teachers awaiting approval are surfaced in a dedicated card // above the roster (and kept OUT of the main list until activated). const pendingUsers = teachers.filter(t => t.status === "pending"); const activeUsers = teachers.filter(t => t.status !== "pending"); const approveTeacher = async (id) => { setActBusy(id); try { await window.AppApi.post(`/admin/teachers/${id}/approve`, {}); await refresh(); } catch (e) { setErr(String(e.message || e)); } finally { setActBusy(null); } }; const rejectTeacher = async (id, name) => { if (!window.confirm(`Reject and delete the registration for ${name || "this teacher"}?`)) return; setActBusy(id); try { await window.AppApi.post(`/admin/teachers/${id}/reject`, {}); if (selectedId === id) setSelectedId(null); await refresh(); } catch (e) { setErr(String(e.message || e)); } finally { setActBusy(null); } }; const decideReset = async (id, kind) => { setActBusy(id); try { await window.AppApi.post(`/password-resets/${id}/${kind}`, {}); await refresh(); } catch (e) { setErr(String(e.message || e)); } finally { setActBusy(null); } }; const filtered = activeUsers.filter(t => { if (!search) return true; const q = search.toLowerCase(); return (t.name || "").toLowerCase().includes(q) || (t.email || "").toLowerCase().includes(q) || (t.username || "").toLowerCase().includes(q); }); const selected = selectedId ? teachers.find(t => t.id === selectedId) : null; return (
Each teacher logs in to the mobile app and only sees tests for the (class × subject) pairs you assign here. Deactivating a teacher revokes their open sessions immediately.
{credentialFlash && (