import { type FormEvent, useEffect, useState } from 'react' import { Link, useSearchParams } from 'react-router-dom' import { ApiHttpError } from '../../api/http' import { fetchPlacementSection, postPlacementSection } from '../../api/grading' import { AcademicFilterBar } from '../discounts/AcademicFilterBar' import { rowsFromUnknown } from './gradingPayloadHelpers' import { GRADING_PLACEMENT_INDEX_PATH } from './gradingPaths' /** CI `grading/placement.php` — per-section placement scores. */ export function PlacementSectionPage() { const [searchParams] = useSearchParams() const classSectionId = searchParams.get('class_section_id') ?? '' const [rows, setRows] = useState[]>([]) const [locked, setLocked] = useState(false) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) useEffect(() => { if (!classSectionId) { setLoading(false) return } let c = false setLoading(true) fetchPlacementSection(searchParams) .then((p) => { if (c) return setRows(rowsFromUnknown(p)) if (p && typeof p === 'object') { const o = p as Record setLocked(Boolean(o.scoresLocked ?? o.locked)) } setError(null) }) .catch((e: unknown) => { if (!c) setError(e instanceof ApiHttpError ? e.message : 'Unable to load placement.') }) .finally(() => { if (!c) setLoading(false) }) return () => { c = true } }, [searchParams, classSectionId]) const scoreKey = (row: Record): string => { if ('placement_score' in row) return 'placement_score' if ('score' in row) return 'score' return 'placement_score' } function setScore(i: number, value: string) { setRows((prev) => { const next = [...prev] const k = scoreKey(next[i] ?? {}) next[i] = { ...next[i], [k]: value === '' ? null : Number(value) } return next }) } async function onSubmit(e: FormEvent) { e.preventDefault() setSaving(true) setError(null) try { await postPlacementSection({ class_section_id: Number(classSectionId), rows, school_year: searchParams.get('school_year') ?? undefined, semester: searchParams.get('semester') ?? undefined, }) const next = await fetchPlacementSection(searchParams) setRows(rowsFromUnknown(next)) } catch (err: unknown) { setError(err instanceof ApiHttpError ? err.message : 'Save failed.') } finally { setSaving(false) } } const qs = searchParams.toString() const cols = rows[0] ? Object.keys(rows[0]) : ['student_id', 'firstname', 'lastname', 'placement_score'] if (!classSectionId) { return (
Add class_section_id to the URL (open from Placement index).
Placement index
) } return (

Placement scores

Section #{classSectionId}

All sections
{locked ?
Scores are locked.
: null} {error ?
{error}
: null} {loading ?

Loading…

: null} {!loading && rows.length > 0 ? (
void onSubmit(ev)}>
{cols.map((c) => ( ))} {rows.map((row, i) => { const sk = scoreKey(row) return ( {cols.map((col) => col === sk && !locked ? ( ) : ( ), )} ) })}
{c.replace(/_/g, ' ')}
setScore(i, ev.target.value)} /> {row[col] === null || row[col] === undefined ? '—' : String(row[col])}
) : null} {!loading && rows.length === 0 ? (
No rows for this section.
) : null}
) }