fix teacher, parent and admin pages
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user