import { Fragment, useCallback, useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { ApiHttpError, apiFetch } from '../../api/http' import { TeacherShell } from './TeacherShell' import { useTeacherResource } from './useTeacherResource' const CUSTOM_UNIT = 'Custom' export type CurriculumRow = { unit_number?: number | null unit_title?: string | null chapter_name?: string | null } export type ClassProgressSubmitFormData = { subject_sections?: Record status_options?: Record sunday_options?: string[] curriculum?: Record classes?: Array<{ class_section_id?: number class_section_name?: string class_id?: number | null class_name?: string | null }> defaults?: { class_id?: number | null class_section_id?: number | null school_year?: string | null semester?: string | null week_start?: string | null } } type UnitRow = { unitNumber: number | 'custom' | null chapter: string } function unwrapSubmitForm(raw: unknown): ClassProgressSubmitFormData | null { if (raw == null || typeof raw !== 'object') return null const o = raw as Record const inner = o.data if (inner != null && typeof inner === 'object' && !Array.isArray(inner)) { return inner as ClassProgressSubmitFormData } return raw as ClassProgressSubmitFormData } function formatWeekLabel(iso: string): string { const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso) if (!m) return iso return `${m[2]}/${m[3]}/${m[1]}` } function unitChoices(curriculum: CurriculumRow[]): Array<{ value: number; label: string }> { const seen = new Map() for (const r of curriculum) { const n = Number(r.unit_number ?? 0) if (!Number.isFinite(n) || n <= 0) continue if (!seen.has(n)) { const t = String(r.unit_title ?? '').trim() seen.set(n, t || `Unit ${n}`) } } return [...seen.entries()] .sort((a, b) => a[0] - b[0]) .map(([value, label]) => ({ value, label })) } function chaptersForUnit(curriculum: CurriculumRow[], unitNumber: number): string[] { const out: string[] = [] const seen = new Set() for (const r of curriculum) { if (Number(r.unit_number ?? 0) !== unitNumber) continue const ch = String(r.chapter_name ?? '').trim() if (ch && !seen.has(ch)) { seen.add(ch) out.push(ch) } } return out } function unitLabelForPayload(curriculum: CurriculumRow[], unitNumber: number): string { for (const r of curriculum) { if (Number(r.unit_number ?? 0) === unitNumber) { const t = String(r.unit_title ?? '').trim() if (t) return t } } return `Unit ${unitNumber}` } function islamicSelectionValid(rows: UnitRow[]): boolean { for (const r of rows) { const ch = r.chapter.trim() if (r.unitNumber === 'custom' && ch !== '') return true if (typeof r.unitNumber === 'number' && r.unitNumber > 0 && ch !== '') return true if (typeof r.unitNumber === 'number' && r.unitNumber > 0 && ch === '') continue } return false } function appendArray(fd: FormData, key: string, values: string[]) { values.forEach((v, i) => { fd.append(`${key}[${i}]`, v) }) } /** `teacher/class_progress_submit.php` — weekly progress form; POST `/api/v1/teacher/class-progress-submit`. */ export function TeacherClassProgressSubmitPage() { const { data: raw, loading, error, reload } = useTeacherResource('/class-progress-submit') const data = useMemo(() => unwrapSubmitForm(raw), [raw]) const subjectSections = data?.subject_sections ?? {} const slugs = useMemo(() => Object.keys(subjectSections), [subjectSections]) const sundayOptions = data?.sunday_options ?? [] const classes = data?.classes ?? [] const defaults = data?.defaults ?? {} const curriculumIslamic = data?.curriculum?.islamic ?? [] const curriculumQuran = data?.curriculum?.quran ?? [] const unitPickList = useMemo(() => unitChoices(curriculumIslamic), [curriculumIslamic]) const [weekStart, setWeekStart] = useState('') const [classSectionId, setClassSectionId] = useState('') const [covered, setCovered] = useState>({}) const [homework, setHomework] = useState>({}) const [islamicRows, setIslamicRows] = useState([{ unitNumber: null, chapter: '' }]) const [quranRows, setQuranRows] = useState([{ unitNumber: null, chapter: '' }]) const [attachmentIslamic, setAttachmentIslamic] = useState([]) const [attachmentQuran, setAttachmentQuran] = useState([]) const [confirmOverwrite, setConfirmOverwrite] = useState(false) const [submitting, setSubmitting] = useState(false) const [submitError, setSubmitError] = useState(null) const [submitOk, setSubmitOk] = useState(null) useEffect(() => { if (!data) return const ws = defaults.week_start ?? sundayOptions[0] ?? '' setWeekStart(ws) const sid = Number(defaults.class_section_id ?? 0) setClassSectionId(sid > 0 ? sid : '') setCovered({}) setHomework({}) setIslamicRows([{ unitNumber: null, chapter: '' }]) setQuranRows([{ unitNumber: null, chapter: '' }]) setAttachmentIslamic([]) setAttachmentQuran([]) setConfirmOverwrite(false) setSubmitError(null) setSubmitOk(null) }, [data, defaults.class_section_id, defaults.week_start, sundayOptions]) useEffect(() => { if (classSectionId === '' && classes.length === 1) { const only = Number(classes[0].class_section_id ?? 0) if (only > 0) setClassSectionId(only) } }, [classes, classSectionId]) const canSubmit = useMemo(() => { if (!weekStart) return false if (!islamicSelectionValid(islamicRows)) return false const covIslamic = (covered.islamic ?? '').trim() if (covIslamic === '') return false return true }, [weekStart, islamicRows, covered]) const updateIslamicRow = useCallback((idx: number, patch: Partial) => { setIslamicRows((prev) => prev.map((r, i) => (i === idx ? { ...r, ...patch } : r))) }, []) const updateQuranRow = useCallback((idx: number, patch: Partial) => { setQuranRows((prev) => prev.map((r, i) => (i === idx ? { ...r, ...patch } : r))) }, []) const handleSubmit = useCallback(async () => { setSubmitError(null) setSubmitOk(null) if (!canSubmit) return const unitIslamic: string[] = [] const chapterIslamic: string[] = [] for (const r of islamicRows) { const ch = r.chapter.trim() if (r.unitNumber === 'custom') { if (ch === '') continue unitIslamic.push(CUSTOM_UNIT) chapterIslamic.push(ch) } else if (typeof r.unitNumber === 'number' && r.unitNumber > 0) { if (ch === '') continue unitIslamic.push(unitLabelForPayload(curriculumIslamic, r.unitNumber)) chapterIslamic.push(ch) } } const unitQuran: string[] = [] const chapterQuran: string[] = [] for (const r of quranRows) { const ch = r.chapter.trim() if (r.unitNumber === 'custom' && ch !== '') { unitQuran.push(CUSTOM_UNIT) chapterQuran.push(ch) } else if (typeof r.unitNumber === 'number' && r.unitNumber > 0 && ch !== '') { unitQuran.push(unitLabelForPayload(curriculumQuran, r.unitNumber)) chapterQuran.push(ch) } } const fd = new FormData() fd.append('week_start', weekStart) if (defaults.school_year) fd.append('school_year', defaults.school_year) if (defaults.semester) fd.append('semester', defaults.semester) if (confirmOverwrite) fd.append('confirm_overwrite', '1') for (const slug of slugs) { const cov = (covered[slug] ?? '').trim() const hw = (homework[slug] ?? '').trim() fd.append(`covered_${slug}`, cov) if (hw) fd.append(`homework_${slug}`, hw) } appendArray(fd, 'unit_islamic', unitIslamic) appendArray(fd, 'chapter_islamic', chapterIslamic) appendArray(fd, 'unit_quran', unitQuran) appendArray(fd, 'chapter_quran', chapterQuran) attachmentIslamic.forEach((f) => fd.append('attachment_islamic[]', f)) attachmentQuran.forEach((f) => fd.append('attachment_quran[]', f)) setSubmitting(true) try { const res = await apiFetch<{ status?: boolean; message?: string }>('/api/v1/teacher/class-progress-submit', { method: 'POST', body: fd, }) setSubmitOk(typeof res?.message === 'string' ? res.message : 'Progress saved.') setConfirmOverwrite(false) reload() } catch (e: unknown) { if (e instanceof ApiHttpError && e.status === 409) { setSubmitError( 'A report already exists for this week. Check “Replace existing week” and submit again to overwrite.', ) return } const msg = e instanceof ApiHttpError ? e.message : 'Unable to save progress.' setSubmitError(msg) } finally { setSubmitting(false) } }, [ canSubmit, weekStart, defaults.school_year, defaults.semester, confirmOverwrite, slugs, covered, homework, islamicRows, quranRows, curriculumIslamic, curriculumQuran, attachmentIslamic, attachmentQuran, reload, ]) const subtitleParts = [defaults.school_year, defaults.semester].filter(Boolean).join(' · ') return ( {!loading && raw != null && data == null ? (
Unexpected response shape.
            {JSON.stringify(raw, null, 2)}
          
) : null} {data ? (
Past submissions
{classes.length === 0 ? (
You have no class assignments for the current term, so progress cannot be submitted.
) : null} {submitOk ? (
{submitOk}
) : null} {submitError ? (
{submitError}
) : null} {classes.length > 0 ? ( <>

Week & section

Islamic Studies — units / chapters

Select at least one unit/chapter pair (or Custom + description). Required before submit.

{islamicRows.map((row, idx) => (
{row.unitNumber != null && row.unitNumber !== 'custom' && typeof row.unitNumber === 'number' && chaptersForUnit(curriculumIslamic, row.unitNumber).length > 0 ? ( ) : ( updateIslamicRow(idx, { chapter: e.target.value })} placeholder={row.unitNumber === 'custom' ? 'Describe custom topic…' : 'Chapter or detail'} /> )}
{islamicRows.length > 1 ? ( ) : null}
))}
{slugs.map((slug) => { const label = subjectSections[slug]?.label ?? slug const attKey = slug === 'islamic' ? attachmentIslamic : attachmentQuran const setAtt = slug === 'islamic' ? setAttachmentIslamic : setAttachmentQuran return ( {slug === 'quran' && curriculumQuran.length > 0 ? (

Quran / Arabic — units / chapters (optional)

Optional rows when curriculum is available.

{quranRows.map((row, idx) => { const qUnits = unitChoices(curriculumQuran) return (
{row.unitNumber != null && row.unitNumber !== 'custom' && typeof row.unitNumber === 'number' && chaptersForUnit(curriculumQuran, row.unitNumber).length > 0 ? ( ) : ( updateQuranRow(idx, { chapter: e.target.value })} placeholder="Chapter or custom text…" /> )}
{quranRows.length > 1 ? ( ) : null}
) })}
) : null}

{label}