Skip to content

Commit 3eccaad

Browse files
patconclaude
andcommitted
Exclude masked participants from counts in UI components
Pass effectiveDisplayMask through MapOverlay to ParticipantCountBar, StatementExplorerDrawer, and StatementTable so that hidden participants are excluded from group counts, tab member counts, and vote comparison group sizes. The ungrouped "Rest" tab now hides when all unpainted participants are masked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b8ec9a0 commit 3eccaad

5 files changed

Lines changed: 47 additions & 16 deletions

File tree

src/components/convo-explorer/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ export const App: React.FC<AppProps> = ({ testAnimation = false, kedroBaseUrl, i
868868
showFilteredParticipants={showFilteredParticipants}
869869
onShowFilteredParticipantsChange={setShowFilteredParticipants}
870870
onClearAllColors={handleOpenClearDialog}
871+
displayMask={effectiveDisplayMask}
871872
// Representative statements props
872873
representativeStatements={representativeStatements}
873874
consensusStatements={consensusStatements}
@@ -890,6 +891,7 @@ export const App: React.FC<AppProps> = ({ testAnimation = false, kedroBaseUrl, i
890891
pointGroups={pointGroups}
891892
isProportional={true}
892893
isUnpaintedGrouped={isUnpaintedGrouped}
894+
displayMask={effectiveDisplayMask}
893895
onUnpaintedGroupedChange={(newValue) => {
894896
setIsUnpaintedGrouped(newValue);
895897
// Trigger recalculation of representative statements when grouping changes

src/components/convo-explorer/MapOverlay.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type MapOverlayProps = {
3737
showFilteredParticipants?: boolean;
3838
onShowFilteredParticipantsChange?: (value: boolean) => void;
3939
isUnpaintedGrouped?: boolean;
40+
/** Display mask parallel to pointGroups for filtering counts */
41+
displayMask?: boolean[];
4042
onClearAllColors?: () => void;
4143

4244
// Representative statements props
@@ -79,6 +81,7 @@ export function MapOverlay({
7981
showFilteredParticipants,
8082
onShowFilteredParticipantsChange,
8183
isUnpaintedGrouped = false,
84+
displayMask,
8285
onClearAllColors,
8386

8487
// Representative statements props
@@ -121,13 +124,17 @@ export function MapOverlay({
121124

122125
// --- NEW: compute activeColors from pointGroups ---
123126
const activeColors = React.useMemo(() => {
124-
const filtered = pointGroups.filter((x): x is number => x !== UNPAINTED_VALUE);
127+
const filtered = pointGroups.filter((x, i): x is number =>
128+
x !== UNPAINTED_VALUE && (!displayMask || displayMask[i])
129+
);
125130
const unique = [...new Set(filtered)];
126-
const hasUnpainted = pointGroups.some(group => group === UNPAINTED_VALUE);
131+
const hasUnpainted = pointGroups.some((group, i) =>
132+
group === UNPAINTED_VALUE && (!displayMask || displayMask[i])
133+
);
127134

128-
// Include UNPAINTED_VALUE in activeColors if there are unpainted points
135+
// Include UNPAINTED_VALUE in activeColors if there are visible unpainted points
129136
return hasUnpainted ? [...unique, UNPAINTED_VALUE] : unique;
130-
}, [pointGroups]);
137+
}, [pointGroups, displayMask]);
131138

132139
// Handle statement row click
133140
const handleStatementClick = React.useCallback((statementId: number) => {
@@ -169,6 +176,7 @@ export function MapOverlay({
169176
repStatementsError={repStatementsError}
170177
isUnpaintedGrouped={isUnpaintedGrouped}
171178
pointGroups={pointGroups}
179+
displayMask={displayMask}
172180
open={drawerOpen}
173181
onOpenChange={handleDrawerOpenChange}
174182
tabValue={drawerTab}

src/components/convo-explorer/ParticipantCountBar.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type ParticipantCountBarProps = {
1919
onUnpaintedGroupedChange?: (isGrouped: boolean) => void;
2020
isProportional?: boolean;
2121
className?: string;
22+
/** Display mask parallel to pointGroups: true = visible, false = hidden. When undefined, all points counted. */
23+
displayMask?: boolean[];
2224
};
2325

2426
export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
@@ -27,6 +29,7 @@ export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
2729
onUnpaintedGroupedChange,
2830
isProportional = true,
2931
className,
32+
displayMask,
3033
}) => {
3134
// Internal state for uncontrolled mode
3235
const [internalIsUnpaintedGrouped, setInternalIsUnpaintedGrouped] = React.useState(false);
@@ -41,14 +44,16 @@ export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
4144
let unpaintedGroup: PointGroupData | null = null;
4245
const groupCounts = new Map<number, number>();
4346

44-
// Count occurrences of each group
45-
pointGroups.forEach(group => {
47+
// Count occurrences of each group (skip masked participants)
48+
pointGroups.forEach((group, i) => {
49+
if (displayMask && !displayMask[i]) return;
4650
groupCounts.set(group, (groupCounts.get(group) || 0) + 1);
4751
});
4852

4953
// Handle unpainted group separately
5054
let unpaintedCount = 0;
51-
pointGroups.forEach(group => {
55+
pointGroups.forEach((group, i) => {
56+
if (displayMask && !displayMask[i]) return;
5257
if (group === UNPAINTED_VALUE) {
5358
unpaintedCount++;
5459
}
@@ -75,13 +80,15 @@ export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
7580
}
7681

7782
return { coloredGroups, unpaintedGroup };
78-
}, [pointGroups]);
83+
}, [pointGroups, displayMask]);
7984

8085
// Calculate proportional widths if needed
8186
const proportionalData = React.useMemo(() => {
8287
if (!isProportional) return null;
8388

84-
const totalPoints = pointGroups.length;
89+
const totalPoints = displayMask
90+
? displayMask.filter(Boolean).length
91+
: pointGroups.length;
8592
if (totalPoints === 0) return null;
8693

8794
// Calculate total number of badges - no gaps between colored badges now
@@ -99,7 +106,9 @@ export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
99106
} else {
100107
// All badges (including unpainted if grouped) share space proportionally
101108
// Calculate total excluding unpainted points when they're not grouped
102-
const unpaintedPoints = pointGroups.filter(group => group === UNPAINTED_VALUE).length;
109+
const unpaintedPoints = pointGroups.filter((group, i) =>
110+
group === UNPAINTED_VALUE && (!displayMask || displayMask[i])
111+
).length;
103112
coloredPointsTotal = totalPoints - (groupData.unpaintedGroup && !isUnpaintedGrouped ? unpaintedPoints : 0);
104113
availableWidthPercent = 100;
105114
}
@@ -122,7 +131,7 @@ export const ParticipantCountBar: React.FC<ParticipantCountBarProps> = ({
122131
totalBadges,
123132
gapWidth
124133
};
125-
}, [groupData, isProportional, pointGroups.length, isUnpaintedGrouped]);
134+
}, [groupData, isProportional, pointGroups, isUnpaintedGrouped, displayMask]);
126135

127136
const handleUnpaintedClick = () => {
128137
const newValue = !isUnpaintedGrouped;

src/components/convo-explorer/StatementExplorerDrawer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type StatementExplorerDrawerProps = {
5353
repStatementsError?: string | null;
5454
isUnpaintedGrouped?: boolean;
5555
pointGroups?: number[]; // Add pointGroups to check for unpainted participants
56+
/** Display mask parallel to pointGroups: true = visible, false = hidden. When undefined, all points counted. */
57+
displayMask?: boolean[];
5658

5759
open?: boolean;
5860
onOpenChange?: (open: boolean) => void;
@@ -84,6 +86,7 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
8486
repStatementsError = null,
8587
isUnpaintedGrouped = false,
8688
pointGroups = [],
89+
displayMask,
8790

8891
open,
8992
onOpenChange,
@@ -230,11 +233,12 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
230233
// Calculate group member counts from pointGroups (shared with convertVoteStatsToGroupVoteData)
231234
const groupMemberCounts = React.useMemo(() => {
232235
const counts: Record<number, number> = {};
233-
pointGroups.forEach(group => {
236+
pointGroups.forEach((group, i) => {
237+
if (displayMask && !displayMask[i]) return;
234238
counts[group] = (counts[group] || 0) + 1;
235239
});
236240
return counts;
237-
}, [pointGroups]);
241+
}, [pointGroups, displayMask]);
238242

239243
// Check if unpainted group should be shown as a tab
240244
const hasUnpaintedGroup = React.useMemo(() => {
@@ -485,6 +489,7 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
485489
dataset={dataset}
486490
pointGroups={pointGroups}
487491
activeColors={sortedColors}
492+
displayMask={displayMask}
488493
// Optimization: Don't pass vote stats props to avoid calculating stats for all statements
489494
// Vote stats are only needed for group tabs (representative statements) and consensus tab
490495
/>
@@ -543,6 +548,7 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
543548
dataset={dataset}
544549
pointGroups={pointGroups}
545550
activeColors={sortedColors}
551+
displayMask={displayMask}
546552
voteStats={voteStats}
547553
loadingVoteStats={loadingVoteStats}
548554
calculateVoteStatsForStatements={calculateVoteStatsForStatements}
@@ -570,6 +576,7 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
570576
dataset={dataset}
571577
pointGroups={pointGroups}
572578
activeColors={sortedColors}
579+
displayMask={displayMask}
573580
voteStats={voteStats}
574581
loadingVoteStats={loadingVoteStats}
575582
calculateVoteStatsForStatements={calculateVoteStatsForStatements}
@@ -660,6 +667,7 @@ export const StatementExplorerDrawer: React.FC<StatementExplorerDrawerProps> = (
660667
dataset={dataset}
661668
pointGroups={pointGroups}
662669
activeColors={sortedColors}
670+
displayMask={displayMask}
663671
voteStats={voteStats}
664672
loadingVoteStats={loadingVoteStats}
665673
calculateVoteStatsForStatements={calculateVoteStatsForStatements}

src/components/convo-explorer/StatementTable.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type StatementTableProps = {
2626
dataset?: [string, [number, number]][];
2727
pointGroups?: number[];
2828
activeColors?: number[];
29+
/** Display mask parallel to pointGroups: true = visible, false = hidden. When undefined, all points counted. */
30+
displayMask?: boolean[];
2931

3032
// Vote stats props (calculated at App level)
3133
voteStats?: Record<number, StatementVoteStats>;
@@ -53,6 +55,7 @@ export const StatementTable: React.FC<StatementTableProps> = ({
5355
dataset = [],
5456
pointGroups = [],
5557
activeColors = [],
58+
displayMask,
5659

5760
// Vote stats props
5861
voteStats = {},
@@ -86,9 +89,10 @@ export const StatementTable: React.FC<StatementTableProps> = ({
8689
return undefined;
8790
}
8891

89-
// Calculate actual group sizes from pointGroups
92+
// Calculate actual group sizes from pointGroups (skip masked participants)
9093
const groupSizes: Record<number, number> = {};
91-
pointGroups.forEach(group => {
94+
pointGroups.forEach((group, i) => {
95+
if (displayMask && !displayMask[i]) return;
9296
groupSizes[group] = (groupSizes[group] || 0) + 1;
9397
});
9498

@@ -110,7 +114,7 @@ export const StatementTable: React.FC<StatementTableProps> = ({
110114
});
111115

112116
return groupVotes.length > 1 ? groupVotes : undefined;
113-
}, [voteStats, dataset, pointGroups, activeColors]);
117+
}, [voteStats, dataset, pointGroups, activeColors, displayMask]);
114118

115119

116120
const insertBreaks = (val: string | null | undefined) => {

0 commit comments

Comments
 (0)