import { type FormEvent, useCallback, useEffect, useState } from 'react' import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { ApiHttpError } from '../../api/http' import { useSchoolYearOptions } from '../../hooks/useSchoolYearOptions' import { fetchChargeInvoicesForParent, fetchExtraChargesPage, submitExtraCharge, type ExtraChargeRow, } from '../../api/paymentManagement' function fmtMoney(n: unknown): string { const v = Number(n) return Number.isFinite(v) ? v.toFixed(2) : '0.00' } /** Extra charges admin — `GET|POST /api/v1/administrator/charges` (JWT). SPA: `/app/admin/charges`. */ export function ExtraChargesPage() { const navigate = useNavigate() const { pathname } = useLocation() const [searchParams] = useSearchParams() const schoolYear = searchParams.get('school_year') ?? '' const semester = searchParams.get('semester') ?? '' const [rows, setRows] = useState([]) const [schoolYears, setSchoolYears] = useState([]) const [activeYear, setActiveYear] = useState(schoolYear) const [activeSem, setActiveSem] = useState(semester) const [parents, setParents] = useState>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [msg, setMsg] = useState(null) const [invoiceOptions, setInvoiceOptions] = useState>([]) const [modalOpen, setModalOpen] = useState(false) const [selectedParentId, setSelectedParentId] = useState('') const { options, selectedYear: defaultYear } = useSchoolYearOptions({ legacyYears: schoolYears, preferredYear: activeYear || schoolYear, }) useEffect(() => { setActiveYear(schoolYear) setActiveSem(semester) }, [schoolYear, semester]) const loadCharges = useCallback(() => { setLoading(true) setError(null) fetchExtraChargesPage({ school_year: schoolYear || undefined, semester: semester || undefined, }) .then((d) => { setRows(d.rows ?? []) setSchoolYears(d.schoolYears ?? []) setParents((d.parents ?? []) as typeof parents) if (d.schoolYear) setActiveYear(String(d.schoolYear)) if (d.semester) setActiveSem(String(d.semester)) }) .catch((e: unknown) => setError(e instanceof ApiHttpError ? e.message : 'Could not load charges.'), ) .finally(() => setLoading(false)) }, [schoolYear, semester]) useEffect(() => { loadCharges() }, [loadCharges]) useEffect(() => { if (!selectedParentId) { setInvoiceOptions([]) return } fetchChargeInvoicesForParent(selectedParentId) .then(setInvoiceOptions) .catch(() => setInvoiceOptions([])) }, [selectedParentId]) function onYearChange(e: React.ChangeEvent) { const y = e.target.value setActiveYear(y) const next = new URLSearchParams(searchParams) if (y) next.set('school_year', y) else next.delete('school_year') navigate({ pathname, search: next.toString() }) } async function onModalSubmit(e: FormEvent) { e.preventDefault() const fd = new FormData(e.currentTarget) if (activeYear) fd.set('school_year', activeYear) if (activeSem) fd.set('semester', activeSem) setMsg(null) try { await submitExtraCharge(fd) setMsg('Charge saved.') setModalOpen(false) loadCharges() } catch (err: unknown) { setMsg(err instanceof ApiHttpError ? err.message : 'Save failed.') } } let pageTotal = 0 for (const r of rows) pageTotal += Number(r.amount ?? 0) return (
{msg ?
{msg}
: null} {error ?
{error}
: null}

Add & Deduct Charges ({activeYear || '—'} — {activeSem || '—'})

{loading ?

Loading…

: null}
{rows.length === 0 && !loading ? ( ) : ( rows.map((r) => { const ctype = String(r.charge_type ?? '').toLowerCase() const amt = Number(r.amount ?? 0) const isAdd = ctype === 'add' || amt > 0 const isDed = ctype === 'deduct' || amt < 0 const badge = isAdd ? 'bg-success' : isDed ? 'bg-danger' : 'bg-secondary' const label = isAdd ? 'Add' : isDed ? 'Deduct' : ctype || '—' const st = String(r.status ?? '') const cls = st === 'pending' ? 'bg-info' : st === 'applied' ? 'bg-success' : st === 'void' ? 'bg-danger' : 'bg-light' return ( ) }) )}
ID Type Title Amount Parent Status Invoice Due
No charges
#{String(r.id)} {label}
{String(r.title ?? '')}
{r.description ? (
{String(r.description)}
) : null}
${fmtMoney(r.amount)} {String(r.parent_name ?? `ID: ${r.parent_id ?? ''}`)} {st} {r.invoice_number ? String(r.invoice_number) : r.invoice_id ? `#${r.invoice_id}` : '—'} {r.due_date ? String(r.due_date) : '—'}
Total Amount ${pageTotal.toFixed(2)}
{modalOpen ? (
Adjust Charge
) : null}
) }