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
@@ -0,0 +1,154 @@
import { useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { fetchCompetitionScoresIndex, fetchPublishedCompetitions } from '../../api/session'
import type { CompetitionScoresIndexCompetitionRow, PublicCompetitionRow } from '../../api/types'
import { ApiScopeNote, CompetitionPageShell, COMPETITION_WINNERS_BASE } from './competitionWinnersShared'
/** CI `admin/competition_winners/index.php` */
export function CompetitionWinnersIndexPage() {
const [publishedRows, setPublishedRows] = useState<PublicCompetitionRow[]>([])
const [adminRows, setAdminRows] = useState<CompetitionScoresIndexCompetitionRow[]>([])
const [sectionMap, setSectionMap] = useState<Record<string, string>>({})
const [activeClassName, setActiveClassName] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
;(async () => {
try {
const [published, teacher] = await Promise.allSettled([
fetchPublishedCompetitions(),
fetchCompetitionScoresIndex(),
])
if (published.status === 'fulfilled') {
setPublishedRows(published.value.data?.competitions ?? [])
}
if (teacher.status === 'fulfilled') {
setAdminRows(teacher.value.competitions ?? [])
setSectionMap(teacher.value.sectionMap ?? {})
setActiveClassName(teacher.value.activeClassName ?? null)
}
if (published.status === 'rejected' && teacher.status === 'rejected') {
setError('Unable to load competition winners data.')
}
} catch (e) {
setError(e instanceof Error ? e.message : 'Unable to load competition winners data.')
}
})()
}, [])
const tableRows = useMemo(() => {
if (adminRows.length > 0) return adminRows
return publishedRows.map((r) => ({
id: r.id,
title: r.title,
class_section_id: r.class_section_id,
is_locked: r.is_locked,
is_published: r.is_published,
})) as CompetitionScoresIndexCompetitionRow[]
}, [adminRows, publishedRows])
return (
<CompetitionPageShell title="Competition Winners">
<ApiScopeNote>
Lock/unlock, export quiz, and competition CRUD match CI once Laravel exposes those admin routes. Score entry and
published winners work with the current API.
</ApiScopeNote>
{error ? <div className="alert alert-danger">{error}</div> : null}
<div className="d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2">
<div />
<Link className="btn btn-primary" to={`${COMPETITION_WINNERS_BASE}/create`}>
+ New Competition
</Link>
</div>
<div className="table-responsive">
<table className="table table-bordered table-sm align-middle mb-0" data-no-mgmt-sticky>
<thead className="table-light">
<tr>
<th>ID</th>
<th>Title</th>
<th>Class Section</th>
<th>Winners Rule</th>
<th>Published</th>
<th>Locked</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{tableRows.length === 0 ? (
<tr>
<td colSpan={7} className="text-muted">
No competitions found.
</td>
</tr>
) : (
tableRows.map((c) => {
const sectionId = Number(c.class_section_id ?? 0)
const sectionName =
sectionId > 0 ? (sectionMap[String(sectionId)] ?? String(sectionId)) : 'All'
const isLocked = Boolean(c.is_locked)
const isPublished = Boolean(c.is_published)
const id = Number(c.id)
return (
<tr key={id}>
<td>{id}</td>
<td>{String(c.title ?? `Competition #${id}`)}</td>
<td>{sectionName}</td>
<td>Tiered per class</td>
<td>{isPublished ? 'Yes' : 'No'}</td>
<td>{isLocked ? 'Yes' : 'No'}</td>
<td>
<div className="d-flex flex-wrap gap-1">
<Link
className="btn btn-sm btn-outline-secondary"
to={`${COMPETITION_WINNERS_BASE}/${id}/settings`}
>
Settings
</Link>
<Link className="btn btn-sm btn-outline-primary" to={`${COMPETITION_WINNERS_BASE}/${id}`}>
Enter Scores
</Link>
<Link
className="btn btn-sm btn-outline-secondary"
to={`${COMPETITION_WINNERS_BASE}/${id}/preview`}
>
Preview
</Link>
<Link
className="btn btn-sm btn-outline-secondary"
to={`${COMPETITION_WINNERS_BASE}/${id}/winners`}
>
Winners
</Link>
<button type="button" className="btn btn-sm btn-outline-success" disabled title="Requires API">
Export Scores to Quiz
</button>
{isLocked ? (
<button type="button" className="btn btn-sm btn-outline-warning" disabled title="Requires API">
Unlock
</button>
) : (
<button type="button" className="btn btn-sm btn-outline-danger" disabled title="Requires API">
Lock
</button>
)}
</div>
</td>
</tr>
)
})
)}
</tbody>
</table>
</div>
{activeClassName ? (
<p className="text-muted small mt-3 mb-0">Active class context: {activeClassName}</p>
) : null}
</CompetitionPageShell>
)
}