Admin UI audit round 2: fix 28 display bugs across 23 files
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m51s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m51s
HIGH fixes (broken features / wrong data): - H1: Fix roundAssignments → projectRoundStates in project router (7 occurrences) - H2: Fix deliberation results panel blank table (wrong field names) - H3: Fix deliberation participant names blank (wrong data path) - H4: Fix awards "Evaluated" stat duplicating "Eligible" count - H5: Fix cross-round comparison enabled at 1 round (backend requires 2) - H6: Fix setState during render anti-pattern (6 occurrences) - H7: Fix round detail jury member count always showing 0 - H8: Remove 4 invalid status values from observer dashboard filter - H9: Fix filtering progress bar always showing 100% MEDIUM fixes (misleading display): - M1: Filter special-award rounds from competition timeline - M2: Exclude special-award rounds from distinct project count - M3: Fix MENTORING pipeline node hardcoded "0 mentored" - M4: Fix DELIB_LOCKED badge using red for success state - M5: Add status label maps to deliberation session detail - M6: Humanize deliberation category + tie-break method displays - M8: Rename setStageId → setRoundId, "Select Stage" → "Select Round" - M9: Add missing INVITED/ACTIVE/SUSPENDED to members status labels - M10: Add ROUND_DRAFT/ACTIVE/CLOSED/ARCHIVED to StatusBadge - M11: Fix unsent messages showing "Scheduled" instead of "Draft" - M12: Rename misleading totalEvaluations → totalAssignments - M13: Rename "Stage" column to "Program" in projects page LOW fixes (cosmetic / edge-case): - L1: Use unfiltered rounds array for active round detection - L2: Use all rounds length for new round sort order - L3: Filter special-award rounds from header count - L4: Fix single-underscore replace in award status badges - L5: Fix score bucket boundary gaps (4.99 dropped between buckets) - L6: Title-case LIVE_FINAL pipeline metric status - L7: Fix roundType.replace only replacing first underscore - L8: Remove duplicate severity sort in smart-actions component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,10 @@ export function ResultsPanel({ sessionId }: ResultsPanelProps) {
|
||||
);
|
||||
const { data: aggregatedResults } = trpc.deliberation.aggregate.useQuery(
|
||||
{ sessionId },
|
||||
{ refetchInterval: 10_000 }
|
||||
{
|
||||
refetchInterval: 10_000,
|
||||
enabled: session?.status === 'TALLYING' || session?.status === 'RUNOFF' || session?.status === 'DELIB_LOCKED',
|
||||
}
|
||||
);
|
||||
|
||||
const initRunoffMutation = trpc.deliberation.initRunoff.useMutation({
|
||||
@@ -52,34 +55,32 @@ export function ResultsPanel({ sessionId }: ResultsPanelProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="p-12 text-center">
|
||||
<p className="text-muted-foreground">No voting results yet</p>
|
||||
<p className="text-muted-foreground">
|
||||
{session?.status === 'DELIB_OPEN' || session?.status === 'VOTING'
|
||||
? 'Voting has not been tallied yet'
|
||||
: 'No voting results yet'}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// Detect ties: check if two or more top-ranked candidates share the same totalScore
|
||||
const hasTie = (() => {
|
||||
const rankings = aggregatedResults.rankings as Array<{ totalScore?: number; projectId: string }> | undefined;
|
||||
// Detect ties using the backend-computed flag, with client-side fallback
|
||||
const hasTie = aggregatedResults.hasTies ?? (() => {
|
||||
const rankings = aggregatedResults.rankings as Array<{ score?: number; projectId: string }> | undefined;
|
||||
if (!rankings || rankings.length < 2) return false;
|
||||
// Group projects by totalScore
|
||||
const scoreGroups = new Map<number, string[]>();
|
||||
for (const r of rankings) {
|
||||
const score = r.totalScore ?? 0;
|
||||
const score = r.score ?? 0;
|
||||
const group = scoreGroups.get(score) || [];
|
||||
group.push(r.projectId);
|
||||
scoreGroups.set(score, group);
|
||||
}
|
||||
// A tie exists if the highest score is shared by 2+ projects
|
||||
const topScore = Math.max(...scoreGroups.keys());
|
||||
const topGroup = scoreGroups.get(topScore);
|
||||
return (topGroup?.length ?? 0) >= 2;
|
||||
})();
|
||||
const tiedProjectIds = hasTie
|
||||
? (aggregatedResults.rankings as Array<{ totalScore?: number; projectId: string }>)
|
||||
.filter((r) => r.totalScore === (aggregatedResults.rankings as Array<{ totalScore?: number }>)[0]?.totalScore)
|
||||
.map((r) => r.projectId)
|
||||
: [];
|
||||
const tiedProjectIds = aggregatedResults.tiedProjectIds ?? [];
|
||||
const canFinalize = session?.status === 'TALLYING' && !hasTie;
|
||||
|
||||
return (
|
||||
@@ -101,17 +102,17 @@ export function ResultsPanel({ sessionId }: ResultsPanelProps) {
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 font-bold">
|
||||
#{index + 1}
|
||||
#{result.rank ?? index + 1}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{result.projectTitle}</p>
|
||||
<p className="font-medium">{result.projectTitle ?? result.projectId}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{result.votes} votes • {result.averageRank?.toFixed(2)} avg rank
|
||||
{result.voteCount} votes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="text-lg">
|
||||
{result.totalScore?.toFixed(1) || 0}
|
||||
{result.score?.toFixed?.(1) ?? 0}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -53,6 +53,9 @@ const statusColors: Record<string, 'default' | 'success' | 'secondary' | 'destru
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
NONE: 'Not Invited',
|
||||
INVITED: 'Invited',
|
||||
ACTIVE: 'Active',
|
||||
SUSPENDED: 'Suspended',
|
||||
}
|
||||
|
||||
const roleColors: Record<string, 'default' | 'outline' | 'secondary'> = {
|
||||
|
||||
Reference in New Issue
Block a user