add school year model

This commit is contained in:
root
2026-06-07 00:52:04 -04:00
parent c4d7a06a17
commit 8e79201a3c
32 changed files with 2031 additions and 213 deletions
+21 -6
View File
@@ -2,6 +2,7 @@ import { type FormEvent, useEffect, useMemo, useState } from 'react'
import { Link, useSearchParams } from 'react-router-dom'
import { ApiHttpError } from '../../api/http'
import { fetchParticipation, postParticipation } from '../../api/grading'
import { useSchoolYearOptions } from '../../hooks/useSchoolYearOptions'
import { GRADING_PATH } from './gradingPaths'
/** CI `grading/participation.php` */
@@ -11,6 +12,10 @@ function normalizeParticipationScore(value: unknown): number | string | null {
export function ParticipationPage() {
const [searchParams] = useSearchParams()
const requestedSchoolYear = searchParams.get('school_year') ?? ''
const { selectedYear } = useSchoolYearOptions({
preferredYear: requestedSchoolYear,
})
const [rows, setRows] = useState<
Array<{
student_id: number
@@ -24,18 +29,24 @@ export function ParticipationPage() {
const [title] = useState('Participation Scores')
const [sectionName, setSectionName] = useState('')
const [semester, setSemester] = useState(searchParams.get('semester') ?? '')
const [schoolYear, setSchoolYear] = useState(searchParams.get('school_year') ?? '')
const [schoolYear, setSchoolYear] = useState(requestedSchoolYear || selectedYear)
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
const [tableSearch, setTableSearch] = useState('')
const classSectionId = searchParams.get('class_section_id') ?? ''
const effectiveSchoolYear = schoolYear || selectedYear
const participationSearchParams = useMemo(() => {
const next = new URLSearchParams(searchParams)
if (effectiveSchoolYear) next.set('school_year', effectiveSchoolYear)
return next
}, [effectiveSchoolYear, searchParams])
useEffect(() => {
let c = false
setLoading(true)
fetchParticipation(searchParams)
fetchParticipation(participationSearchParams)
.then((p) => {
if (c) return
if (p && typeof p === 'object') {
@@ -57,7 +68,11 @@ export function ParticipationPage() {
setLocked(Boolean(o.scores_locked ?? o.scoresLocked ?? o.locked))
setSectionName(typeof o.class_section_name === 'string' ? o.class_section_name : '')
setSemester(typeof o.semester === 'string' ? o.semester : (searchParams.get('semester') ?? ''))
setSchoolYear(typeof o.school_year === 'string' ? o.school_year : (searchParams.get('school_year') ?? ''))
setSchoolYear(
typeof o.school_year === 'string' && o.school_year.trim() !== ''
? o.school_year
: effectiveSchoolYear,
)
}
setError(null)
})
@@ -70,7 +85,7 @@ export function ParticipationPage() {
return () => {
c = true
}
}, [searchParams])
}, [effectiveSchoolYear, participationSearchParams, searchParams])
const visibleRows = useMemo(() => {
const normalized = tableSearch.toLowerCase().trim()
@@ -95,7 +110,7 @@ export function ParticipationPage() {
try {
const body: Record<string, unknown> = {
class_section_id: classSectionId ? Number(classSectionId) : undefined,
school_year: schoolYear || undefined,
school_year: effectiveSchoolYear || undefined,
semester: semester || undefined,
scores: rows.reduce<Record<string, { score: number | string | null }>>((carry, row) => {
carry[String(row.student_id)] = { score: row.score ?? '' }
@@ -103,7 +118,7 @@ export function ParticipationPage() {
}, {}),
}
await postParticipation(body)
const next = await fetchParticipation(searchParams)
const next = await fetchParticipation(participationSearchParams)
if (next && typeof next === 'object') {
const o = next as Record<string, unknown>
const students = Array.isArray(o.students) ? o.students : []