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.
*