Comprehensive admin UI stats audit: fix 16 display bugs

HIGH fixes:
- H1: Competition detail project count no longer double-counts across rounds
- H2: Rounds page header stats use unfiltered round set
- H3: Rounds page "eval" label corrected to "asgn" (assignment count)
- H4: Observer reports project count uses distinct analytics count
- H5: Awards eligibility count filters to only eligible=true (backend)
- H6: Round detail projectCount derived from projectStates for consistency
- H7: Deliberation hasVoted derived from votes array (was always undefined)

MEDIUM fixes:
- M1: Reports page round status badges use correct ROUND_ACTIVE/ROUND_CLOSED enums
- M2: Observer reports badges use ROUND_ prefix instead of stale STAGE_ prefix
- M3: Deliberation list status badges use correct VOTING/TALLYING/RUNOFF enums
- M4: Competition list/detail round count excludes special-award rounds (backend)
- M5: Messages page shows actual recipient count instead of hardcoded "1 user"

LOW fixes:
- L2: Observer analytics jurorCount scoped to round when roundId provided
- L3: Analytics round-scoped project count uses ProjectRoundState not assignments
- L4: JuryGroup delete audit log reports member count (not assignment count)
- L5: Project rankings include unevaluated projects at bottom instead of hiding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt
2026-02-19 09:56:09 +01:00
parent d117090fca
commit ae1685179c
12 changed files with 68 additions and 37 deletions

View File

@@ -441,7 +441,7 @@ export default function RoundDetailPage() {
}, [configJson])
// ── Computed values ────────────────────────────────────────────────────
const projectCount = round?._count?.projectRoundStates ?? 0
const projectCount = projectStates?.length ?? round?._count?.projectRoundStates ?? 0
const stateCounts = useMemo(() =>
projectStates?.reduce((acc: Record<string, number>, ps: any) => {
acc[ps.state] = (acc[ps.state] || 0) + 1

View File

@@ -284,7 +284,8 @@ export default function RoundsPage() {
const activeFilter = filterType !== 'all'
const totalProjects = (compDetail as any)?.distinctProjectCount ?? 0
const totalAssignments = rounds.reduce((s, r) => s + r._count.assignments, 0)
const allRounds = (compDetail?.rounds ?? []) as RoundWithStats[]
const totalAssignments = allRounds.reduce((s, r) => s + r._count.assignments, 0)
const activeRound = rounds.find((r) => r.status === 'ROUND_ACTIVE')
return (
@@ -326,7 +327,7 @@ export default function RoundsPage() {
</Tooltip>
</div>
<div className="flex items-center gap-4 mt-1 text-sm text-muted-foreground">
<span>{rounds.length} rounds</span>
<span>{allRounds.length} rounds</span>
<span className="text-muted-foreground/30">|</span>
<span>{totalProjects} projects</span>
<span className="text-muted-foreground/30">|</span>
@@ -493,7 +494,7 @@ export default function RoundsPage() {
{projectCount}
</span>
{assignmentCount > 0 && (
<span className="tabular-nums">{assignmentCount} eval</span>
<span className="tabular-nums">{assignmentCount} asgn</span>
)}
{(round.windowOpenAt || round.windowCloseAt) && (
<span className="flex items-center gap-1 tabular-nums">