/* ================================================================== Doubts — Student↔Teacher per-question threads ------------------------------------------------------------------ Left column: list of doubts the calling user can see (test author + HoD/admin for the owning school). Tabs filter by status. Right column: selected thread — original question, what the student picked, the message timeline, and a reply composer (text + image). The bell in shell.jsx surfaces 'doubt_raised' / 'doubt_message' notifications; clicking one navigates here with the doubt preselected (via window.APP.DOUBTS_CONTEXT seeded by app.jsx). ================================================================== */ const { Icon: DbIcon } = window.AppUI; const STATUS_LABEL = { open: "Open", answered: "Replied", resolved: "Resolved" }; const STATUS_CLS = { open: "amber", answered: "blue", resolved: "green" }; const relTimeD = (iso) => { if (!iso) return ""; const ms = Date.now() - new Date(iso).getTime(); const s = Math.floor(ms / 1000); if (s < 60) return `${s}s ago`; if (s < 3600) return `${Math.floor(s/60)}m ago`; if (s < 86400) return `${Math.floor(s/3600)}h ago`; return `${Math.floor(s/86400)}d ago`; }; const DoubtsScreen = () => { const [items, setItems] = React.useState([]); const [counts, setCounts] = React.useState({ open: 0, answered: 0, resolved: 0 }); const [tab, setTab] = React.useState("open"); const [selectedId, setSelectedId] = React.useState(() => window.APP?.DOUBTS_CONTEXT?.doubtId || null); const [search, setSearch] = React.useState(""); const [loading, setLoading] = React.useState(true); const [err, setErr] = React.useState(null); const refresh = React.useCallback(async () => { setLoading(true); setErr(null); try { const [list, c] = await Promise.all([ window.AppApi.get(`/teacher/doubts${tab === "all" ? "" : `?status=${tab}`}`), window.AppApi.get("/teacher/doubts/counts"), ]); setItems(list || []); setCounts(c || { open: 0, answered: 0, resolved: 0 }); } catch (e) { setErr(String(e.message || e)); } finally { setLoading(false); } }, [tab]); React.useEffect(() => { refresh(); }, [refresh]); // Clear the cross-screen context once we've consumed it. The selectedId // is now state-owned so the user can navigate freely from here. React.useEffect(() => { if (window.APP?.DOUBTS_CONTEXT) { // If the context's doubt belongs to a different status tab, switch. const ctx = window.APP.DOUBTS_CONTEXT; if (ctx.tab && ctx.tab !== tab) setTab(ctx.tab); delete window.APP.DOUBTS_CONTEXT; } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const filtered = items.filter((d) => { if (!search) return true; const q = search.toLowerCase(); return ( (d.studentName || "").toLowerCase().includes(q) || (d.studentRoll || "").toLowerCase().includes(q) || (d.testTitle || "").toLowerCase().includes(q) || (d.testDisplayId || "").toLowerCase().includes(q) || (d.qTextPreview || "").toLowerCase().includes(q) ); }); const selected = filtered.find((d) => d.id === selectedId) || items.find((d) => d.id === selectedId) || null; return (
Threads opened by students from the report screen. You see threads on tests you authored; HoDs and admins see the whole school.
{/* Tabs */}