From f0cdab6ede6d83b3512e95fedd3b3f5a00bbba6d Mon Sep 17 00:00:00 2001 From: CoolGame8 Date: Mon, 23 Feb 2026 18:18:30 +0200 Subject: [PATCH] show EXCEEDS notes in deliberation and compare --- .../components/deliberation-table.tsx | 13 +++- .../[category]/deliberation-context.tsx | 3 + .../deliberation/[category]/types.ts | 1 + .../compare/components/rubric-scores.tsx | 10 +++- .../compare/components/section-score-row.tsx | 59 ++++++++++++------- .../optional-awards-data-grid.tsx | 13 +++- .../final/final-deliberation-context.tsx | 8 ++- .../(volunteer)/deliberation/final/types.ts | 3 + .../lems/(volunteer)/deliberation/utils.ts | 56 ++++++++++++++++++ 9 files changed, 139 insertions(+), 27 deletions(-) diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/components/deliberation-table.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/components/deliberation-table.tsx index 49ae19a3c..8d099af99 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/components/deliberation-table.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/components/deliberation-table.tsx @@ -128,7 +128,18 @@ export function DeliberationTable() { align: 'center' as const, renderCell: params => { const value = params.row.rubricFields[label]; - return value !== null ? value : '-'; + const notes = params.row.rubricFieldNotes[label]; + const hasExceedsNote = value === 4 && notes; + + if (value === null) return '-'; + + return hasExceedsNote ? ( + + {value} + + ) : ( + value + ); } }) as GridColDef ), diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/deliberation-context.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/deliberation-context.tsx index 7cda612d9..95afd9c73 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/deliberation-context.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/deliberation-context.tsx @@ -12,6 +12,7 @@ import { getFieldDisplayLabels, computeNormalizedScores, getOrganizedRubricFields, + getOrganizedRubricFieldNotes, getGPScores } from '../utils'; import type { DeliberationContextValue, EnrichedTeam } from './types'; @@ -126,6 +127,7 @@ export function CategoryDeliberationProvider({ const rank = computeRank(scores, teamScores, category); const isEligible = computeEligibility(team, deliberation); const rubricFields = getOrganizedRubricFields(team, category); + const rubricFieldNotes = getOrganizedRubricFieldNotes(team, category); const gpScores = getGPScores(team); return { @@ -141,6 +143,7 @@ export function CategoryDeliberationProvider({ rank, eligible: isEligible, rubricFields, + rubricFieldNotes, gpScores, rubricId: team.rubrics[hypenatedCategory as keyof typeof team.rubrics]?.id ?? null, awardNominations: team.rubrics.core_values?.data?.awards ?? {} diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/types.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/types.ts index 42a48efaf..4aef85967 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/types.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/[category]/types.ts @@ -20,6 +20,7 @@ export interface EnrichedTeam { eligible: boolean; rubricFields: Record; + rubricFieldNotes: Record; gpScores: Record; rubricId: string | null; diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/rubric-scores.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/rubric-scores.tsx index 296305d50..39ee5a96f 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/rubric-scores.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/rubric-scores.tsx @@ -26,6 +26,7 @@ interface SectionFields { fieldId: string; value: number | null; color: 'success' | 'error' | 'default'; + notes?: string; }>; } @@ -54,7 +55,8 @@ const processFieldsBySections = ( fieldId: id, category: 'innovation-project' as const, value: field.value, - color: getFieldComparisonColor(id, team.id, fieldComparisons, 'ip') + color: getFieldComparisonColor(id, team.id, fieldComparisons, 'ip'), + notes: field.notes })), ...Object.entries(team.rubrics.robot_design?.data?.fields || {}) .filter(([id]) => @@ -66,7 +68,8 @@ const processFieldsBySections = ( fieldId: id, category: 'robot-design' as const, value: field.value, - color: getFieldComparisonColor(id, team.id, fieldComparisons, 'rd') + color: getFieldComparisonColor(id, team.id, fieldComparisons, 'rd'), + notes: field.notes })) ]; if (fields.length > 0) result[cat] = { 'core-values': fields }; @@ -83,7 +86,8 @@ const processFieldsBySections = ( fieldId: field.id, category: cat, value: rubricData.fields[field.id].value, - color: getFieldComparisonColor(field.id, team.id, fieldComparisons) + color: getFieldComparisonColor(field.id, team.id, fieldComparisons), + notes: rubricData.fields[field.id].notes })); if (sectionFields.length > 0) sections[section.id] = sectionFields; }); diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/section-score-row.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/section-score-row.tsx index 285bfd398..4e5c018c1 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/section-score-row.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/compare/components/section-score-row.tsx @@ -1,11 +1,16 @@ -import { Stack, Typography, Chip } from '@mui/material'; +import { Stack, Typography, Chip, Tooltip } from '@mui/material'; import { useRubricsTranslations } from '@lems/localization'; import { JudgingCategory } from '@lems/types/judging'; interface SectionScoreRowProps { category: JudgingCategory; sectionId: string; - scores: Array<{ fieldId: string; value: number | null; color: 'success' | 'error' | 'default' }>; + scores: Array<{ + fieldId: string; + value: number | null; + color: 'success' | 'error' | 'default'; + notes?: string; + }>; showSectionName?: boolean; showAllScores?: boolean; } @@ -45,25 +50,37 @@ export function SectionScoreRow({ alignItems: 'center' }} > - {(showAllScores ? scores : scores.slice(0, 2)).map((score, index) => ( - - ))} + {(showAllScores ? scores : scores.slice(0, 2)).map((score, index) => { + const hasExceedsNote = score.value === 4 && score.notes; + + return ( + + + + ); + })} ); diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/optional-awards/optional-awards-data-grid.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/optional-awards/optional-awards-data-grid.tsx index cd8514b6d..475da1092 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/optional-awards/optional-awards-data-grid.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/components/optional-awards/optional-awards-data-grid.tsx @@ -172,7 +172,18 @@ export function OptionalAwardsDataGrid() { align: 'center' as const, renderCell: params => { const value = params.row.rubricsFields['core-values'][label]; - return value !== null ? value : '-'; + const notes = params.row.rubricsFieldNotes['core-values'][label]; + const hasExceedsNote = value === 4 && notes; + + if (value === null) return '-'; + + return hasExceedsNote ? ( + + {value} + + ) : ( + value + ); } }) as GridColDef ), diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx index eefaf98e6..513b09f4f 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/final-deliberation-context.tsx @@ -9,7 +9,8 @@ import { computeRoomMetrics, computeTeamScores, getGPScores, - getOrganizedRubricFields + getOrganizedRubricFields, + getOrganizedRubricFieldNotes } from '../utils'; import { DeliberationAwards, @@ -209,6 +210,11 @@ export const FinalDeliberationProvider = ({ 'innovation-project': getOrganizedRubricFields(team, 'innovation-project'), 'core-values': getOrganizedRubricFields(team, 'core-values') }, + rubricsFieldNotes: { + 'robot-design': getOrganizedRubricFieldNotes(team, 'robot-design'), + 'innovation-project': getOrganizedRubricFieldNotes(team, 'innovation-project'), + 'core-values': getOrganizedRubricFieldNotes(team, 'core-values') + }, rubricIds: { 'robot-design': team.rubrics.robot_design?.id || null, 'innovation-project': team.rubrics.innovation_project?.id || null, diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts index a06050838..a6185b165 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/final/types.ts @@ -11,7 +11,9 @@ export type OptionalAwardNominations = Partial>; export type RanksPerCategory = Record; export type EligiblityPerStage = Record; export type RubricFields = Record; +export type RubricFieldNotes = Record; export type RubricsFields = Record; +export type RubricsFieldNotes = Record; /** * Enriched team data with all computed values for final deliberation. @@ -33,6 +35,7 @@ export type EnrichedTeam = { eligibility: EligiblityPerStage; rubricsFields: RubricsFields; + rubricsFieldNotes: RubricsFieldNotes; gpScores: Record; rubricIds: Record; diff --git a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/utils.ts b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/utils.ts index 2078975c5..31dd3abf5 100644 --- a/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/utils.ts +++ b/apps/frontend/src/app/[locale]/lems/(volunteer)/deliberation/utils.ts @@ -251,6 +251,62 @@ export function buildFieldMetadata(category: JudgingCategory): FieldMetadata[] { return fields; } +/** + * Extracts organized field notes for a team in a specific category. + * + * Returns field notes mapped by their display labels, ordered by field number. + * For core-values category, includes both IP and RD fields with their respective prefixes. + * + * @param team - The team to extract field notes from + * @param category - The deliberation category (hyphenated) + * @returns Object mapping display labels (e.g., 'IP-1') to field notes + */ +export function getOrganizedRubricFieldNotes( + team: Team, + category: JudgingCategory +): Record { + const fields = buildFieldMetadata(category); + const result: Record = {}; + + const categoryKey = hyphensToUnderscores(category) as + | 'innovation_project' + | 'robot_design' + | 'core_values'; + + const categoryMap = { + innovation_project: team.rubrics.innovation_project, + robot_design: team.rubrics.robot_design, + core_values: team.rubrics.core_values + } as const; + + if (categoryKey === 'core_values') { + // For core-values, include both IP and RD fields with prefixes + const ipFields = buildFieldMetadata('innovation-project').filter(f => f.coreValues); + const rdFields = buildFieldMetadata('robot-design').filter(f => f.coreValues); + + ipFields.forEach(field => { + const ipRubric = team.rubrics.innovation_project; + const notes = ipRubric?.data?.fields?.[field.id]?.notes; + result[field.displayLabel] = notes; + }); + + rdFields.forEach(field => { + const rdRubric = team.rubrics.robot_design; + const notes = rdRubric?.data?.fields?.[field.id]?.notes; + result[field.displayLabel] = notes; + }); + } else { + // For IP/RD, include only their own fields + fields.forEach(field => { + const rubric = categoryMap[categoryKey]; + const notes = rubric?.data?.fields?.[field.id]?.notes; + result[field.displayLabel] = notes; + }); + } + + return result; +} + /** * Extracts GP scores with formatted keys for a team. *