fix teacher, parent and admin pages

This commit is contained in:
root
2026-04-25 00:00:10 -04:00
parent 7fe34dde0d
commit 3e77fc92c7
275 changed files with 46412 additions and 3325 deletions
+233
View File
@@ -0,0 +1,233 @@
import { type FormEvent, useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { ApiHttpError } from '../../api/http'
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<string, SlipPreviewRow[]> = {}
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() {
const [searchParams, setSearchParams] = useSearchParams()
const schoolYear = searchParams.get('school_year') ?? ''
const semester = searchParams.get('semester') ?? ''
const [rows, setRows] = useState<SlipPreviewRow[]>([])
const [error, setError] = useState<string | null>(null)
useEffect(() => {
setError(null)
fetchSlipPreviewList({
school_year: schoolYear || undefined,
semester: semester || undefined,
})
.then((d) => setRows(d.rows ?? []))
.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<HTMLFormElement>) {
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')
}
return (
<div className="container-fluid px-0 py-4">
<div className="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
<div>
<h2 className="mb-0">Late Slip Preview</h2>
<small className="text-muted">View and print student late slips</small>
</div>
</div>
<form className="row gy-2 gx-3 align-items-end mb-4" onSubmit={applyFilter}>
<div className="col-md-4">
<label className="form-label">School Year</label>
<input
type="text"
name="school_year"
className="form-control"
defaultValue={schoolYear}
placeholder="e.g. 2025-2026"
/>
</div>
<div className="col-md-3">
<label className="form-label">Semester</label>
<select name="semester" className="form-select" defaultValue={semester}>
<option value="">All Semesters</option>
<option value="fall">Fall</option>
<option value="spring">Spring</option>
</select>
</div>
<div className="col-md-2">
<button type="submit" className="btn btn-primary w-100">
Filter
</button>
</div>
</form>
{error ? <div className="alert alert-danger">{error}</div> : null}
{rows.length === 0 ? (
<div className="alert alert-light border">No slips found.</div>
) : (
<>
<div className="row mb-4">
<div className="col-12">
<div className="card shadow-sm border-0">
<div className="card-header bg-white py-3">
<h5 className="card-title mb-0 fw-bold text-primary">Late Statistics by Date</h5>
</div>
<div className="card-body">
<div style={{ width: '100%' }}>
<div
className="d-flex align-items-end gap-1 px-2 border-bottom"
style={{ height: 240 }}
>
{chartLabels.map((label, i) => {
const barH = Math.round((chartData[i] / maxCount) * 220)
return (
<div
key={label + i}
className="d-flex flex-column align-items-center flex-fill"
style={{ maxWidth: 90 }}
>
<div
className="w-100 rounded-top bg-primary bg-opacity-75"
style={{
height: barH,
minHeight: chartData[i] > 0 ? 6 : 0,
}}
title={`${label}: ${chartData[i]}`}
/>
</div>
)
})}
</div>
<div className="d-flex gap-1 px-2 mt-1 flex-wrap justify-content-between">
{chartLabels.map((label, i) => (
<div
key={`lbl-${label}-${i}`}
className="small text-muted text-center text-truncate flex-fill"
style={{ fontSize: 10, maxWidth: 90 }}
>
{label}
<span className="d-block fw-semibold text-dark">{chartData[i]}</span>
</div>
))}
</div>
<div className="small text-muted text-center mt-2">
Recorded Dates · Total slips per day
</div>
</div>
</div>
</div>
</div>
</div>
{dateOrder.map((dateKey) => {
const groupRows = groupedRows[dateKey] ?? []
return (
<div key={dateKey} className="card mb-4">
<div className="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
<div className="fw-semibold">Date: {formatPreviewDateKey(dateKey)}</div>
<span className="badge bg-secondary">{groupRows.length} slips</span>
</div>
<div className="table-responsive">
<table className="table table-striped table-bordered align-middle w-100">
<thead className="table-light">
<tr>
<th>Student Name</th>
<th>Time In</th>
<th>Grade</th>
<th>Reason</th>
<th>School Year</th>
<th>Semester</th>
<th>Admin Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{groupRows.map((r, idx) => (
<tr key={idx}>
<td>{String(r.student_name ?? '')}</td>
<td>{String(r.time_in ?? '')}</td>
<td>{String(r.grade ?? '')}</td>
<td>{String(r.reason ?? '')}</td>
<td>{String(r.school_year ?? '')}</td>
<td>{displaySemester(r.semester)}</td>
<td>{String(r.admin_name ?? '')}</td>
<td>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => openPrint(r)}
>
Print
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
})}
</>
)}
</div>
)
}