From e0103fa956081cec82c9c4b4b9a7624a4afefaca Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 27 Apr 2026 14:30:12 +0200 Subject: [PATCH] feat: side panel adds country, description, and per-criterion scores Three side-panel additions on the ranking dashboard's project detail sheet: - Project country and team name as outline badges in the header, matching the row chips on the list view. - Collapsible 'Description' box (closed by default) that reveals the full project description without leaving the panel. - Expanding a juror row now shows their per-criterion scores in addition to the free-text feedback. Boolean criteria render with the form's trueLabel/falseLabel (or 'Yes'/'No' fallback); numeric criteria show the raw value next to the criterion label from the active form's criteriaJson. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../admin/round/ranking-dashboard.tsx | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/src/components/admin/round/ranking-dashboard.tsx b/src/components/admin/round/ranking-dashboard.tsx index 403fc47..46789dd 100644 --- a/src/components/admin/round/ranking-dashboard.tsx +++ b/src/components/admin/round/ranking-dashboard.tsx @@ -1112,6 +1112,18 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran {selectedProjectId ? `ID: …${selectedProjectId.slice(-8)}` : ''} +
+ {projectDetail?.project.country && ( + + + + )} + {projectDetail?.project.teamName && ( + + {projectDetail.project.teamName} + + )} +
{selectedProjectId && ( + + {/* Project description (collapsible) */} + {projectDetail.project.description && ( + + + Description + + + + {projectDetail.project.description} + + + )} + {/* Stats summary: combined Avg card with Raw + Balanced side-by-side */} {projectDetail.stats && (() => { const raw = selectedProjectId @@ -1266,10 +1292,55 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran })()} - {isExpanded && a.evaluation?.feedbackText && ( -

- {a.evaluation.feedbackText} -

+ {isExpanded && ( +
+ {/* Per-criterion scores */} + {(() => { + const scores = a.evaluation?.criterionScoresJson as Record | null + if (!scores || !evalForm?.criteriaJson) return null + const criteria = evalForm.criteriaJson as Array<{ + id: string + label: string + type?: string + trueLabel?: string + falseLabel?: string + scale?: number | string + }> + const rendered = criteria + .map((c) => { + const v = scores[c.id] + if (v == null || v === '') return null + let display: string + if (typeof v === 'boolean') { + display = v ? (c.trueLabel ?? 'Yes') : (c.falseLabel ?? 'No') + } else if (typeof v === 'number') { + display = String(v) + } else { + display = String(v) + } + return { label: c.label, display, type: c.type ?? 'numeric' } + }) + .filter((x): x is { label: string; display: string; type: string } => x != null) + if (rendered.length === 0) return null + return ( +
+ {rendered.map((c, i) => ( +
+ {c.label} + {c.display} +
+ ))} +
+ ) + })()} + + {/* Feedback text */} + {a.evaluation?.feedbackText && ( +

+ {a.evaluation.feedbackText} +

+ )} +
)} )