import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { exportAdminCompetitionWinnerQuiz, fetchAdminCompetitionWinnersIndex, lockAdminCompetitionWinner, unlockAdminCompetitionWinner, } from '../../api/session' import type { CompetitionWinnerAdminCompetitionRow } from '../../api/types' import { ApiScopeNote, CompetitionPageShell, COMPETITION_WINNERS_BASE } from './competitionWinnersShared' function toFiniteNumber(value: unknown): number | null { const n = typeof value === 'number' ? value : Number(value) return Number.isFinite(n) ? n : null } function competitionId(row: CompetitionWinnerAdminCompetitionRow): number | null { return toFiniteNumber(row.id ?? row.competition_id) } export function CompetitionWinnersIndexPage() { const [rows, setRows] = useState([]) const [sectionMap, setSectionMap] = useState>({}) const [message, setMessage] = useState(null) const [error, setError] = useState(null) const [busyAction, setBusyAction] = useState(null) async function load() { try { const response = await fetchAdminCompetitionWinnersIndex() if (!response.ok) { setError(response.message ?? 'Unable to load competition winners data.') return } setRows(response.competitions ?? []) setSectionMap(response.sectionMap ?? {}) setError(null) } catch (e) { setError(e instanceof Error ? e.message : 'Unable to load competition winners data.') } } useEffect(() => { void load() }, []) async function handleExport(row: CompetitionWinnerAdminCompetitionRow) { const id = competitionId(row) if (id === null) { setError('This competition does not have a valid id yet.') return } try { setBusyAction(`export:${id}`) setMessage(null) const result = await exportAdminCompetitionWinnerQuiz(id, { class_section_id: toFiniteNumber(row.class_section_id), }) if (!result.ok) { setError(result.message ?? 'Unable to export competition scores to quiz.') return } setMessage(result.message ?? 'Competition scores exported to quiz.') } catch (e) { setError(e instanceof Error ? e.message : 'Unable to export competition scores to quiz.') } finally { setBusyAction(null) } } async function handleLockToggle(row: CompetitionWinnerAdminCompetitionRow) { const id = competitionId(row) if (id === null) { setError('This competition does not have a valid id yet.') return } try { setBusyAction(`lock:${id}`) setMessage(null) const result = row.is_locked ? await unlockAdminCompetitionWinner(id) : await lockAdminCompetitionWinner(id) if (!result.ok) { setError(result.message ?? 'Unable to update competition lock state.') return } setMessage(result.message ?? (row.is_locked ? 'Competition unlocked.' : 'Competition locked.')) await load() } catch (e) { setError(e instanceof Error ? e.message : 'Unable to update competition lock state.') } finally { setBusyAction(null) } } return ( Admin competition management now uses dedicated Laravel endpoints. Draft competitions keep working here, and preview is no longer limited to published rows. {error ?
{error}
: null} {message ?
{message}
: null}
+ New Competition
{rows.length === 0 ? ( ) : ( rows.map((row) => { const id = competitionId(row) const sectionId = toFiniteNumber(row.class_section_id) ?? 0 const sectionName = sectionId > 0 ? (sectionMap[String(sectionId)] ?? String(sectionId)) : 'All' const title = String(row.title ?? (id !== null ? `Competition #${id}` : 'Competition')) const exportBusy = busyAction === `export:${id}` const lockBusy = busyAction === `lock:${id}` return ( ) }) )}
ID Title Class Section Winners Rule Published Locked Actions
No competitions found.
{id ?? '—'} {title} {sectionName} Tiered per class {row.is_published ? 'Yes' : 'No'} {row.is_locked ? 'Yes' : 'No'}
{id !== null ? ( <> Settings Enter Scores Preview Winners ) : ( Invalid competition id )}
) }