import { type FormEvent, useEffect, useMemo, useState } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { formatProgressDate } from '../classProgress/classProgressUtils' import { ClassProgressUnitDisplay } from '../classProgress/ClassProgressUnitDisplay' import { TeacherShell } from './TeacherShell' import { useTeacherResource } from './useTeacherResource' const STATUS_OPTIONS: Record = { on_track: 'On track', slightly_behind: 'Slightly behind', behind: 'Behind', } type Pagination = { current_page?: number last_page?: number per_page?: number total?: number } export type TeacherProgressReportRow = { id?: number class_section_id?: number class_section_name?: string | null week_start?: string | null week_end?: string | null subject?: string | null unit_title?: string | null covered?: string | null homework?: string | null status?: string | null status_label?: string | null teacher_name?: string | null updated_at?: string | null } /** Laravel `JsonResource::collection()` often serializes as `{ data: [...] }` under `items`. */ function extractReportRows(rawItems: unknown): TeacherProgressReportRow[] { if (rawItems == null) return [] if (Array.isArray(rawItems)) return rawItems as TeacherProgressReportRow[] if (typeof rawItems === 'object' && 'data' in (rawItems as object)) { const inner = (rawItems as { data: unknown }).data if (Array.isArray(inner)) return inner as TeacherProgressReportRow[] } return [] } function unwrapHistoryList(raw: unknown): { items: TeacherProgressReportRow[]; pagination?: Pagination } | null { if (raw == null || typeof raw !== 'object') return null const o = raw as Record let data = (o.data ?? o) as Record // Rare: double-wrapped `data` from intermediaries if (!('items' in data) && typeof data.data === 'object' && data.data !== null) { const inner = data.data as Record if ('items' in inner) data = inner } if (!('items' in data)) return null const rows = extractReportRows(data.items) return { items: rows, pagination: data.pagination as Pagination | undefined, } } /** Empty values (e.g. `week_start=`) break Laravel `date_format` validation on the index endpoint. */ function buildHistoryQueryString(qs: string): string { const p = new URLSearchParams(qs || undefined) const emptyKeys: string[] = [] p.forEach((v, k) => { if (v === '' || v == null) emptyKeys.push(k) }) emptyKeys.forEach((k) => p.delete(k)) if (!p.has('per_page')) p.set('per_page', '100') if (!p.has('sort_by')) p.set('sort_by', 'week_start') if (!p.has('sort_dir')) p.set('sort_dir', 'desc') return p.toString() } function statusBadgeClass(status: string | null | undefined): string { const s = String(status ?? '').toLowerCase() if (s === 'on_track') return 'text-bg-success' if (s === 'slightly_behind') return 'text-bg-warning text-dark' if (s === 'behind') return 'text-bg-danger' return 'text-bg-secondary' } function truncate(s: string | null | undefined, max: number): string { const t = String(s ?? '').trim() if (t.length <= max) return t return `${t.slice(0, max - 1)}…` } /** `GET /api/v1/teacher/class-progress-history` — paginated report rows for the logged-in teacher. */ export function TeacherClassProgressHistoryPage() { const [search, setSearch] = useSearchParams() const qs = search.toString() const listPath = useMemo(() => { return `/class-progress-history?${buildHistoryQueryString(qs)}` }, [qs]) const { data: raw, loading, error, reload } = useTeacherResource(listPath) const parsed = useMemo(() => unwrapHistoryList(raw), [raw]) const items = parsed?.items ?? [] const pagination = parsed?.pagination const [draftWeekStart, setDraftWeekStart] = useState(() => search.get('week_start') ?? '') const [draftWeekEnd, setDraftWeekEnd] = useState(() => search.get('week_end') ?? '') const [draftSection, setDraftSection] = useState(() => search.get('class_section_id') ?? '') const [draftStatus, setDraftStatus] = useState(() => search.get('status') ?? '') const [draftSortDir, setDraftSortDir] = useState(() => search.get('sort_dir') || 'desc') useEffect(() => { const p = new URLSearchParams(buildHistoryQueryString(qs)) setDraftWeekStart(p.get('week_start') ?? '') setDraftWeekEnd(p.get('week_end') ?? '') setDraftSection(p.get('class_section_id') ?? '') setDraftStatus(p.get('status') ?? '') setDraftSortDir(p.get('sort_dir') || 'desc') }, [qs]) const sectionOptions = useMemo(() => { const m = new Map() for (const r of items) { const id = Number(r.class_section_id ?? 0) if (!id) continue const name = String(r.class_section_name ?? '').trim() || `Section ${id}` if (!m.has(id)) m.set(id, name) } return [...m.entries()].sort((a, b) => a[0] - b[0]) }, [items]) function applyFilters(e: FormEvent) { e.preventDefault() const next = new URLSearchParams() next.set('per_page', search.get('per_page') || '100') next.set('sort_by', search.get('sort_by') || 'week_start') next.set('sort_dir', draftSortDir === 'asc' ? 'asc' : 'desc') if (draftWeekStart.trim()) next.set('week_start', draftWeekStart.trim()) if (draftWeekEnd.trim()) next.set('week_end', draftWeekEnd.trim()) if (draftSection.trim()) next.set('class_section_id', draftSection.trim()) if (draftStatus.trim()) next.set('status', draftStatus.trim()) next.delete('page') setSearch(next, { replace: true }) } function resetFilters() { setDraftWeekStart('') setDraftWeekEnd('') setDraftSection('') setDraftStatus('') const next = new URLSearchParams() next.set('per_page', '100') next.set('sort_by', 'week_start') next.set('sort_dir', 'desc') setDraftSortDir('desc') setSearch(next, { replace: true }) } const currentPage = Math.max(1, Number(pagination?.current_page) || 1) const lastPage = Math.max(1, Number(pagination?.last_page) || 1) const total = pagination?.total != null ? Number(pagination.total) : items.length function goPage(p: number) { const next = new URLSearchParams(buildHistoryQueryString(qs)) if (p <= 1) next.delete('page') else next.set('page', String(p)) setSearch(next, { replace: true }) } return (
Submit progress
{!loading && raw != null && parsed == null ? (
Unexpected response shape.
            {JSON.stringify(raw, null, 2)}
          
) : null}
setDraftWeekStart(e.target.value)} />
setDraftWeekEnd(e.target.value)} />
{items.length === 0 && !loading ? (

No progress reports match these filters.

) : null} {items.length > 0 ? ( <>
{items.map((row) => { const id = Number(row.id ?? 0) const week = row.week_start || row.week_end ? `${formatProgressDate(row.week_start)} – ${formatProgressDate(row.week_end)}` : '—' const section = String(row.class_section_name ?? '').trim() || `ID ${row.class_section_id ?? '—'}` const subject = String(row.subject ?? '—') const isQuran = subject.toLowerCase().includes('quran') const st = row.status_label ?? STATUS_OPTIONS[String(row.status ?? '')] ?? row.status ?? '—' return ( ) })}
Week Section Subject Status Unit Covered Homework Updated
{week} {section} {subject} {st} {truncate(row.covered, 120) || '—'} {truncate(row.homework, 80) || '—'} {formatProgressDate(row.updated_at, true)}
{lastPage > 1 ? (
Page {currentPage} of {lastPage} ({total} total)
) : null} ) : null}
) }