import { type FormEvent, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' import { ApiHttpError } from '../../api/http' import { useSchoolYearOptions } from '../../hooks/useSchoolYearOptions' import { fetchSlipPreviewList, type SlipPreviewRow } from '../../api/slips' import { displaySemester, formatPreviewDateKey } from './slipFormatting' import { SLIPS_PRINT_PATH } from './slipPaths' function groupRowsByDate(rows: SlipPreviewRow[]) { const groupedRows: Record = {} const dateOrder: string[] = [] for (const r of rows) { let dateKey = String(r.date ?? '').trim() if (!dateKey) dateKey = 'Unknown Date' if (!(dateKey in groupedRows)) { groupedRows[dateKey] = [] dateOrder.push(dateKey) } groupedRows[dateKey].push(r) } const chartData = dateOrder.map((d) => groupedRows[d].length) const chartLabels = dateOrder.map((d) => formatPreviewDateKey(d)) return { groupedRows, dateOrder, chartData, chartLabels } } function slipPrintSearchParams(row: SlipPreviewRow): string { const fields = [ 'student_name', 'date', 'time_in', 'grade', 'reason', 'school_year', 'semester', 'admin_name', ] as const const qs = new URLSearchParams() for (const f of fields) { qs.set(f, String(row[f] ?? '')) } return qs.toString() } export function SlipPreviewListPage() { type SlipSortKey = | 'student_name' | 'time_in' | 'grade' | 'reason' | 'school_year' | 'semester' | 'admin_name' const [searchParams, setSearchParams] = useSearchParams() const schoolYear = searchParams.get('school_year') ?? '' const semester = searchParams.get('semester') ?? '' const [rows, setRows] = useState([]) const [error, setError] = useState(null) const [expandedDates, setExpandedDates] = useState>(new Set()) const [sortConfig, setSortConfig] = useState<{ key: SlipSortKey; direction: 'asc' | 'desc' }>({ key: 'student_name', direction: 'asc', }) const { options, selectedYear } = useSchoolYearOptions({ preferredYear: schoolYear, }) useEffect(() => { setError(null) fetchSlipPreviewList({ school_year: schoolYear || undefined, semester: semester || undefined, }) .then((d) => { const nextRows = d.rows ?? [] setRows(nextRows) const dateKeys = new Set( nextRows.map((row) => { const raw = String(row.date ?? '').trim() return raw || 'Unknown Date' }), ) setExpandedDates(dateKeys) }) .catch((e: unknown) => setError(e instanceof ApiHttpError ? e.message : 'Failed to load slips.'), ) }, [schoolYear, semester]) const { groupedRows, dateOrder, chartData, chartLabels } = useMemo( () => groupRowsByDate(rows), [rows], ) const maxCount = Math.max(1, ...chartData) function applyFilter(e: FormEvent) { e.preventDefault() const fd = new FormData(e.currentTarget) const next = new URLSearchParams() const sy = String(fd.get('school_year') ?? '') const sem = String(fd.get('semester') ?? '') if (sy) next.set('school_year', sy) if (sem) next.set('semester', sem) setSearchParams(next) } function openPrint(row: SlipPreviewRow) { const q = slipPrintSearchParams(row) window.open(`${SLIPS_PRINT_PATH}?${q}`, '_blank', 'noopener') } function toggleDate(dateKey: string) { setExpandedDates((prev) => { const next = new Set(prev) next.has(dateKey) ? next.delete(dateKey) : next.add(dateKey) return next }) } function requestSort(key: SlipSortKey) { setSortConfig((prev) => prev.key === key ? { key, direction: prev.direction === 'asc' ? 'desc' : 'asc' } : { key, direction: 'asc' }, ) } function sortIndicator(key: SlipSortKey) { if (sortConfig.key !== key) return '↕' return sortConfig.direction === 'asc' ? '↑' : '↓' } function sortRows(groupRows: SlipPreviewRow[]) { return [...groupRows].sort((left, right) => { const direction = sortConfig.direction === 'asc' ? 1 : -1 const leftValue = sortConfig.key === 'semester' ? displaySemester(left.semester) : String(left[sortConfig.key] ?? '') const rightValue = sortConfig.key === 'semester' ? displaySemester(right.semester) : String(right[sortConfig.key] ?? '') return ( leftValue.localeCompare(rightValue, undefined, { numeric: true, sensitivity: 'base', }) * direction ) }) } return (

Late Slip Preview

View and print student late slips
{error ?
{error}
: null} {rows.length === 0 ? (
No slips found.
) : ( <>
Late Statistics by Date
{chartLabels.map((label, i) => { const barH = Math.round((chartData[i] / maxCount) * 220) return (
0 ? 6 : 0, }} title={`${label}: ${chartData[i]}`} />
) })}
{chartLabels.map((label, i) => (
{label} {chartData[i]}
))}
Recorded Dates · Total slips per day
{dateOrder.map((dateKey) => { const groupRows = groupedRows[dateKey] ?? [] const expanded = expandedDates.has(dateKey) const displayedRows = sortRows(groupRows) return (
toggleDate(dateKey)} >
{expanded ? '▼' : '▶'} Date: {formatPreviewDateKey(dateKey)}
{groupRows.length} slips
{expanded ? (
{displayedRows.map((r, idx) => ( ))}
Action
{String(r.student_name ?? '')} {String(r.time_in ?? '')} {String(r.grade ?? '')} {String(r.reason ?? '')} {String(r.school_year ?? '')} {displaySemester(r.semester)} {String(r.admin_name ?? '')}
) : null}
) })} )}
) }