fix teacher, parent and admin pages
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { fetchPublishedCompetition } from '../../api/session'
|
||||
import type { PublicCompetitionRow, PublicCompetitionWinnerRow } from '../../api/types'
|
||||
import { ApiScopeNote, CompetitionPageShell, competitionLabel } from './competitionWinnersShared'
|
||||
|
||||
function usePublishedCompetition(id: number) {
|
||||
const [competition, setCompetition] = useState<PublicCompetitionRow | null>(null)
|
||||
const [winnersByClass, setWinnersByClass] = useState<Record<string, PublicCompetitionWinnerRow[]>>({})
|
||||
const [sectionMap, setSectionMap] = useState<Record<string, string>>({})
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!Number.isFinite(id) || id <= 0) return
|
||||
;(async () => {
|
||||
try {
|
||||
const response = await fetchPublishedCompetition(id)
|
||||
if (!response.status || !response.data) {
|
||||
setError(response.message ?? 'Competition not found.')
|
||||
return
|
||||
}
|
||||
setCompetition(response.data.competition)
|
||||
setWinnersByClass(response.data.winners_by_class ?? {})
|
||||
setSectionMap(response.data.section_map ?? {})
|
||||
setError(null)
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Unable to load winners.')
|
||||
}
|
||||
})()
|
||||
}, [id])
|
||||
|
||||
return { competition, winnersByClass, sectionMap, error }
|
||||
}
|
||||
|
||||
/** CI `admin/competition_winners/preview.php` */
|
||||
export function CompetitionWinnerPreviewPage() {
|
||||
const { id } = useParams()
|
||||
const competitionId = Number(id)
|
||||
const { competition, winnersByClass, sectionMap, error } = usePublishedCompetition(competitionId)
|
||||
|
||||
return (
|
||||
<CompetitionPageShell title="Preview Winners">
|
||||
<ApiScopeNote>
|
||||
Preview reflects published winners from the public API. Unpublished drafts are not shown until the backend exposes an
|
||||
admin preview.
|
||||
</ApiScopeNote>
|
||||
{error ? <div className="alert alert-danger">{error}</div> : null}
|
||||
{competition ? (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<div>
|
||||
<strong>Competition:</strong> {competitionLabel(competition)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>School Year:</strong> {competition.school_year ?? '—'}
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(winnersByClass).length === 0 ? (
|
||||
<div className="alert alert-warning">No published winners found for this competition.</div>
|
||||
) : (
|
||||
Object.entries(winnersByClass).map(([classId, rows]) => (
|
||||
<div key={classId} className="mb-4">
|
||||
<h5 className="mt-4">Top Winners - {sectionMap[classId] ?? `Class ${classId}`}</h5>
|
||||
<table className="table table-bordered table-sm">
|
||||
<thead className="table-light">
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Student</th>
|
||||
<th>Score</th>
|
||||
<th>Prize</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row, index) => (
|
||||
<tr key={`${classId}-${index}`}>
|
||||
<td>{row.rank ?? index + 1}</td>
|
||||
<td>
|
||||
{row.name ??
|
||||
(`${row.firstname ?? ''} ${row.lastname ?? ''}`.trim() ||
|
||||
`Student #${row.student_id ?? ''}`)}
|
||||
</td>
|
||||
<td>{row.score ?? '—'}</td>
|
||||
<td>{row.prize_amount ?? '—'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</CompetitionPageShell>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user