From 732296aa8b807b55d14897e2a63dbe7660c94103 Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Tue, 7 Jul 2026 22:48:21 -0400 Subject: [PATCH 1/7] #4280 unified style for rules dashboard button --- .../ProjectRules/ProjectRulesTab.tsx | 15 ++----- .../src/pages/RulesPage/AssignRulesTab.tsx | 41 +++++++++---------- .../src/pages/RulesPage/RulesetEditPage.tsx | 39 +++--------------- .../src/pages/RulesPage/RulesetPage.tsx | 6 +-- .../src/pages/RulesPage/RulesetTypePage.tsx | 6 +-- .../components/RulesActionButton.tsx | 26 ++++++++++++ 6 files changed, 60 insertions(+), 73 deletions(-) create mode 100644 src/frontend/src/pages/RulesPage/components/RulesActionButton.tsx diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx index 20006e7bec..a8a3a93085 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx @@ -38,6 +38,7 @@ import { InfoOutlined } from '@mui/icons-material'; import { useHistory } from 'react-router-dom'; import { routes } from '../../../../utils/routes'; import RuleStatusTag from '../../../RulesPage/components/RuleStatusTag'; +import { RulesActionButton } from '../../../RulesPage/components/RulesActionButton'; interface ProjectRulesTabProps { project: Project; @@ -322,23 +323,13 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { > - + diff --git a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx index b1e00811ac..ed5441aca0 100644 --- a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx +++ b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx @@ -23,10 +23,17 @@ import ErrorPage from '../ErrorPage'; import { useHistory, useLocation, useParams } from 'react-router-dom'; import { routes } from '../../utils/routes'; import { useToast } from '../../hooks/toasts.hooks'; -import { NERButton } from '../../components/NERButton'; +import { RulesActionButton } from './components/RulesActionButton'; import RuleRow from './RuleRow'; import { useBulkToggleRuleTeam } from '../../hooks/rules.hooks'; +const ROW_BACKGROUND_COLOR = '#9d9d9d'; +const SELECTED_ROW_COLOR = '#b36b6b'; +const ROW_HOVER_COLOR = '#5e5e5e'; +const SELECTED_ROW_HOVER_COLOR = '#a05858'; +const ROW_TEXT_COLOR = '#000000'; +const HEADER_TEXT_COLOR = '#ffffff'; + /* * Props for the assign rules tab. */ @@ -65,8 +72,8 @@ const TeamRow: React.FC = ({ team, isSelected, onClick }) => { onClick={onClick} sx={{ borderBottom: '1px solid #7d7d7d', - backgroundColor: isSelected ? '#b36b6b' : '#CECECE', - '&:hover': { backgroundColor: isSelected ? '#a05858' : '#5e5e5e' }, + backgroundColor: isSelected ? SELECTED_ROW_COLOR : ROW_BACKGROUND_COLOR, + '&:hover': { backgroundColor: isSelected ? SELECTED_ROW_HOVER_COLOR : ROW_HOVER_COLOR }, cursor: 'pointer', '&:last-child': { borderBottom: 'none' } }} @@ -77,7 +84,7 @@ const TeamRow: React.FC = ({ team, isSelected, onClick }) => { padding: '8px 16px', backgroundColor: 'inherit', borderBottom: 'none', - color: '#000000' + color: ROW_TEXT_COLOR }} > {team.teamName} @@ -239,7 +246,7 @@ const AssignRulesTab: React.FC = ({ rules }) => { {/* Teams Column */} - + Teams: = ({ rules }) => { }} > - + {teams?.map((team) => ( = ({ rules }) => { {/* Rules Column */} - + Rules: = ({ rules }) => { }} >
- + {topLevelRules.map((rule) => ( = ({ rules }) => { backgroundColor={(r) => { const leafIds = getLeafRuleIds(r.ruleId, rules); const isSelected = leafIds.length > 0 && leafIds.every((id) => isRuleAssigned(id)); - return isSelected ? '#b36b6b' : '#CECECE'; + return isSelected ? SELECTED_ROW_COLOR : ROW_BACKGROUND_COLOR; }} hoverColor={(r) => { const leafIds = getLeafRuleIds(r.ruleId, rules); const isSelected = leafIds.length > 0 && leafIds.every((id) => isRuleAssigned(id)); - return isSelected ? '#a05858' : '#5e5e5e'; + return isSelected ? SELECTED_ROW_HOVER_COLOR : ROW_HOVER_COLOR; }} - textColor="#000000" + textColor={ROW_TEXT_COLOR} onRowClick={(r) => handleRuleToggle(r.ruleId)} middleContent={() => null} rightContent={(r) => renderTeamTags(r.ruleId)} @@ -332,17 +339,9 @@ const AssignRulesTab: React.FC = ({ rules }) => { }} /> - + {isSaving ? 'Saving...' : 'Save & Exit'} - + diff --git a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx index bd7992f551..f076ed53d3 100644 --- a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx @@ -17,6 +17,7 @@ import AddRuleSectionModal from './components/AddRuleSectionModal'; import AddRuleModal from './components/AddRuleModal'; import { AddRuleBox } from './components/AddRuleBox'; import AssignRulesTab from './AssignRulesTab'; +import { RulesActionButton } from './components/RulesActionButton'; import DeleteRuleModal from './components/DeleteRuleModal'; import { useDeleteRule, useEditRule, useSingleRuleset, useAllRulesForRuleset } from '../../hooks/rules.hooks'; import { countRulesToDelete } from '../../utils/rules.utils'; @@ -317,44 +318,14 @@ const RulesetEditPage: React.FC = () => { > Cancel - + ) : ( - + )} diff --git a/src/frontend/src/pages/RulesPage/RulesetPage.tsx b/src/frontend/src/pages/RulesPage/RulesetPage.tsx index b63fceb5ff..78ef917e98 100644 --- a/src/frontend/src/pages/RulesPage/RulesetPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetPage.tsx @@ -6,7 +6,7 @@ import { useParams } from 'react-router-dom'; import React from 'react'; import { useToast } from '../../hooks/toasts.hooks'; import { useCreateRuleset, useDeleteRuleset, useParseRuleset } from '../../hooks/rules.hooks'; -import { NERButton } from '../../components/NERButton'; +import { RulesActionButton } from './components/RulesActionButton'; import AddNewFileModal from './components/AddNewFileModal'; import PageLayout from '../../components/PageLayout'; import { Box } from '@mui/material'; @@ -109,9 +109,9 @@ const RulesetPage: React.FC = () => { }} > {/* Add New File Button */} - setAddFileModalShow(!AddFileModalShow)}> + setAddFileModalShow(!AddFileModalShow)}> Add New File - + setAddFileModalShow(false)} diff --git a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx index 3259a9bb1c..466ae82ff8 100644 --- a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import PageLayout from '../../components/PageLayout'; import { Box } from '@mui/material'; import RulesetTypeTable from './components/RulesetTypeTable'; -import { NERButton } from '../../components/NERButton'; +import { RulesActionButton } from './components/RulesActionButton'; import AddRulesetTypeModal from './components/AddRulesetTypeModal'; import { useState } from 'react'; import { useCreateRulesetType } from '../../hooks/rules.hooks'; @@ -49,9 +49,9 @@ const RulesetTypePage: React.FC = () => { justifyContent: { xs: 'center', md: 'flex-end' } }} > - setAddRulesetTypeModalShow(!addRulesetTypeModalShow)}> + setAddRulesetTypeModalShow(!addRulesetTypeModalShow)}> Add Ruleset Type - + ({ + borderRadius: '8px', + color: '#ededed', + backgroundColor: '#dd514c', + padding: '2px 15px', + fontSize: '16px', + fontWeight: 700, + textTransform: 'none', + '&:hover': { + backgroundColor: '#c74340' + }, + '&.Mui-disabled': { + backgroundColor: theme.palette.action.disabled, + color: theme.palette.text.disabled + } +})); From 462ed77879cd92ce314b3c7064cd797247cd6b50 Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Tue, 7 Jul 2026 23:09:29 -0400 Subject: [PATCH 2/7] #4280 standardize footer dividers and button spacing --- .../src/pages/RulesPage/AssignRulesTab.tsx | 6 +++--- .../src/pages/RulesPage/RulesetEditPage.tsx | 6 +++--- .../src/pages/RulesPage/RulesetPage.tsx | 19 ++++++++++++------- .../src/pages/RulesPage/RulesetTypePage.tsx | 19 ++++++++++++------- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx index ed5441aca0..5f01231189 100644 --- a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx +++ b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx @@ -333,12 +333,12 @@ const AssignRulesTab: React.FC = ({ rules }) => { > - + {isSaving ? 'Saving...' : 'Save & Exit'} diff --git a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx index f076ed53d3..8f0c5536d5 100644 --- a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx @@ -291,12 +291,12 @@ const RulesetEditPage: React.FC = () => { > - + {editingRuleId ? ( <>
- + {teams?.map((team) => ( handleTeamSelect(team.teamId)} /> ))} @@ -276,7 +282,7 @@ const AssignRulesTab: React.FC = ({ rules }) => { {/* Rules Column */} - + Rules: = ({ rules }) => { }} >
- + {topLevelRules.map((rule) => ( { - const leafIds = getLeafRuleIds(r.ruleId, rules); - const isSelected = leafIds.length > 0 && leafIds.every((id) => isRuleAssigned(id)); - return isSelected ? SELECTED_ROW_COLOR : ROW_BACKGROUND_COLOR; - }} - hoverColor={(r) => { - const leafIds = getLeafRuleIds(r.ruleId, rules); - const isSelected = leafIds.length > 0 && leafIds.every((id) => isRuleAssigned(id)); - return isSelected ? SELECTED_ROW_HOVER_COLOR : ROW_HOVER_COLOR; - }} - textColor={ROW_TEXT_COLOR} + backgroundColor={(r) => rowBackgroundColor(isRuleSelected(r))} + hoverColor={(r) => rowHoverColor(isRuleSelected(r))} + textColor={theme.palette.common.black} onRowClick={(r) => handleRuleToggle(r.ruleId)} middleContent={() => null} rightContent={(r) => renderTeamTags(r.ruleId)} diff --git a/src/frontend/src/pages/RulesPage/RuleRow.tsx b/src/frontend/src/pages/RulesPage/RuleRow.tsx index bc5cb65732..da96372462 100644 --- a/src/frontend/src/pages/RulesPage/RuleRow.tsx +++ b/src/frontend/src/pages/RulesPage/RuleRow.tsx @@ -72,7 +72,7 @@ const RuleRow: React.FC = ({ // Lazy load if allRules not provided const { data: fetchedSubRules = [] } = useGetChildRules(rule.ruleId, !allRules && isExpanded && hasSubRules); - // Use allRules if provided, otherwise use fetched. + // Use allRules if provided, otherwise use fetched. // Sorted by rule code so children render in a stable numeric order (e.g. F.2 before F.10) const subRules = [...(presentSubRules ?? fetchedSubRules)].sort(compareRuleCodes); diff --git a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx index efe0dded29..8114ac9d48 100644 --- a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx @@ -189,7 +189,7 @@ const RulesetEditPage: React.FC = () => {
- + {topLevelRules.map((rule) => ( { sx={{ backgroundColor: theme.palette.grey[100], '& .MuiOutlinedInput-root': { - color: '#000000', + color: theme.palette.common.black, '& fieldset': { borderColor: '#dd514c' }, @@ -226,7 +226,9 @@ const RulesetEditPage: React.FC = () => { ); } return ( - currentRule.ruleContent && {currentRule.ruleContent} + currentRule.ruleContent && ( + {currentRule.ruleContent} + ) ); }} rightContent={(currentRule) => ( @@ -235,12 +237,14 @@ const RulesetEditPage: React.FC = () => { onAdd={handleOpenAddMenu} onRemove={handleRemoveRule} onEdit={handleEditRule} - iconColor="#000000" + iconColor={theme.palette.common.black} /> )} - backgroundColor={(currentRule) => (editingRuleId === currentRule.ruleId ? '#c0c0c0' : '#9d9d9d')} - textColor="#000000" - hoverColor="#5e5e5e" + backgroundColor={(currentRule) => + editingRuleId === currentRule.ruleId ? theme.palette.grey[400] : theme.palette.grey[500] + } + textColor={theme.palette.common.black} + hoverColor={theme.palette.grey[700]} rowHeight="10px" verticalPadding="5px" /> diff --git a/src/frontend/src/pages/RulesPage/components/RulesetTeamView.tsx b/src/frontend/src/pages/RulesPage/components/RulesetTeamView.tsx index c6801d700d..8d750ec84b 100644 --- a/src/frontend/src/pages/RulesPage/components/RulesetTeamView.tsx +++ b/src/frontend/src/pages/RulesPage/components/RulesetTeamView.tsx @@ -110,7 +110,7 @@ const RulesetTeamView: React.FC = ({ allRules, teamRules, // Top level items are teams and unassigned to team const topLevelItems = [...teamRulesAsRules, ...(unassignedToTeamRule ? [unassignedToTeamRule] : [])]; - // Everything that isn't an actual rule (e.g., unassigned headers) should span the full row width + // Everything that isn't an actual rule (e.g., unassigned headers) should span the full row width const actualRuleIds = new Set(allRules.map((r) => r.ruleId)); return ( From 692d0c68bacf341e7f194ac75055449745c1fcba Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Fri, 10 Jul 2026 16:44:22 -0400 Subject: [PATCH 6/7] #4280 updated button --- .../ProjectRules/ProjectRulesTab.tsx | 7 ++--- .../src/pages/RulesPage/AssignRulesTab.tsx | 9 +++---- src/frontend/src/pages/RulesPage/RuleRow.tsx | 1 + .../src/pages/RulesPage/RulesetEditPage.tsx | 10 +++---- .../src/pages/RulesPage/RulesetPage.tsx | 10 ++++--- .../src/pages/RulesPage/RulesetTypePage.tsx | 10 ++++--- .../components/RulesActionButton.tsx | 26 ------------------- 7 files changed, 28 insertions(+), 45 deletions(-) delete mode 100644 src/frontend/src/pages/RulesPage/components/RulesActionButton.tsx diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx index 9ce0e54826..f31630294e 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx @@ -38,7 +38,7 @@ import { InfoOutlined } from '@mui/icons-material'; import { useHistory } from 'react-router-dom'; import { routes } from '../../../../utils/routes'; import RuleStatusTag from '../../../RulesPage/components/RuleStatusTag'; -import { RulesActionButton } from '../../../RulesPage/components/RulesActionButton'; +import { NERButton } from '../../../../components/NERButton'; import { compareRuleCodes } from '../../../../utils/rules.utils'; interface ProjectRulesTabProps { @@ -333,13 +333,14 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { > - setAddRuleModalOpen(true)} disabled={teamNames.length === 0 || hasNoActiveRuleset} > Add Rule - + diff --git a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx index b7a2f3ac15..3ab681c2a5 100644 --- a/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx +++ b/src/frontend/src/pages/RulesPage/AssignRulesTab.tsx @@ -23,14 +23,13 @@ import ErrorPage from '../ErrorPage'; import { useHistory, useLocation, useParams } from 'react-router-dom'; import { routes } from '../../utils/routes'; import { useToast } from '../../hooks/toasts.hooks'; -import { RulesActionButton } from './components/RulesActionButton'; import { NERButton } from '../../components/NERButton'; import NERModal from '../../components/NERModal'; import WarningIcon from '@mui/icons-material/Warning'; import RuleRow from './RuleRow'; import { useBulkToggleRuleTeam } from '../../hooks/rules.hooks'; import { compareRuleCodes } from '../../utils/rules.utils'; -import { getAncestorIds, getRuleAndDescendantIds } from '../../utils/rules.utils'; +import { getAncestorIds, getRuleAndDescendantIds, getDescendantLeafRules } from '../../utils/rules.utils'; /* * Props for the assign rules tab. @@ -136,7 +135,7 @@ const AssignRulesTab: React.FC = ({ rules }) => { // A rule is considered selected when all of its leaf rules are assigned to the current team. const isRuleSelected = (rule: Rule) => { - const leafIds = getLeafRuleIds(rule.ruleId, rules); + const leafIds = getDescendantLeafRules(rule, rules).map((leaf) => leaf.ruleId); return leafIds.length > 0 && leafIds.every((id) => isRuleAssigned(id)); }; @@ -388,9 +387,9 @@ const AssignRulesTab: React.FC = ({ rules }) => { }} /> - + {isSaving ? 'Saving...' : 'Save & Exit'} - + diff --git a/src/frontend/src/pages/RulesPage/RuleRow.tsx b/src/frontend/src/pages/RulesPage/RuleRow.tsx index da96372462..1fd93fe2f0 100644 --- a/src/frontend/src/pages/RulesPage/RuleRow.tsx +++ b/src/frontend/src/pages/RulesPage/RuleRow.tsx @@ -211,6 +211,7 @@ const RuleRow: React.FC = ({ e.stopPropagation()} sx={{ ...commonCellStyles, ...cardCellBg, diff --git a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx index 8114ac9d48..d5a46f24bc 100644 --- a/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetEditPage.tsx @@ -17,7 +17,7 @@ import AddRuleSectionModal from './components/AddRuleSectionModal'; import AddRuleModal from './components/AddRuleModal'; import { AddRuleBox } from './components/AddRuleBox'; import AssignRulesTab from './AssignRulesTab'; -import { RulesActionButton } from './components/RulesActionButton'; +import { NERButton } from '../../components/NERButton'; import DeleteRuleModal from './components/DeleteRuleModal'; import { useDeleteRule, useEditRule, useSingleRuleset, useAllRulesForRuleset } from '../../hooks/rules.hooks'; import { countRulesToDelete, compareRuleCodes } from '../../utils/rules.utils'; @@ -322,14 +322,14 @@ const RulesetEditPage: React.FC = () => { > Cancel - + Save - + ) : ( - + Add Rule Section - + )} diff --git a/src/frontend/src/pages/RulesPage/RulesetPage.tsx b/src/frontend/src/pages/RulesPage/RulesetPage.tsx index 790d1419af..64775f3c7a 100644 --- a/src/frontend/src/pages/RulesPage/RulesetPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetPage.tsx @@ -6,7 +6,7 @@ import { useParams } from 'react-router-dom'; import React from 'react'; import { useToast } from '../../hooks/toasts.hooks'; import { useCreateRuleset, useDeleteRuleset, useParseRuleset } from '../../hooks/rules.hooks'; -import { RulesActionButton } from './components/RulesActionButton'; +import { NERButton } from '../../components/NERButton'; import AddNewFileModal from './components/AddNewFileModal'; import PageLayout from '../../components/PageLayout'; import { Box } from '@mui/material'; @@ -114,9 +114,13 @@ const RulesetPage: React.FC = () => { }} > {/* Add New File Button */} - setAddFileModalShow(!AddFileModalShow)}> + setAddFileModalShow(!AddFileModalShow)} + > Add New File - + setAddFileModalShow(false)} diff --git a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx index 235d109a93..f92aeaf343 100644 --- a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx @@ -2,7 +2,7 @@ import React from 'react'; import PageLayout from '../../components/PageLayout'; import { Box } from '@mui/material'; import RulesetTypeTable from './components/RulesetTypeTable'; -import { RulesActionButton } from './components/RulesActionButton'; +import { NERButton } from '../../components/NERButton'; import AddRulesetTypeModal from './components/AddRulesetTypeModal'; import { useState } from 'react'; import { useCreateRulesetType } from '../../hooks/rules.hooks'; @@ -54,9 +54,13 @@ const RulesetTypePage: React.FC = () => { pb: 2 }} > - setAddRulesetTypeModalShow(!addRulesetTypeModalShow)}> + setAddRulesetTypeModalShow(!addRulesetTypeModalShow)} + > Add Ruleset Type - + ({ - borderRadius: '8px', - color: '#ededed', - backgroundColor: '#dd514c', - padding: '2px 15px', - fontSize: '16px', - fontWeight: 700, - textTransform: 'none', - '&:hover': { - backgroundColor: '#c74340' - }, - '&.Mui-disabled': { - backgroundColor: theme.palette.action.disabled, - color: theme.palette.text.disabled - } -})); From 1f2e6894614b14a7a981cb64324b8072a378409c Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Fri, 10 Jul 2026 19:30:14 -0400 Subject: [PATCH 7/7] #4280 memoize fix + update project rule naming --- .../ProjectRules/ProjectRulesTab.tsx | 18 +++++++++--------- .../components/RulesetGeneralView.tsx | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx index f31630294e..15a5b424e7 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/ProjectRules/ProjectRulesTab.tsx @@ -86,17 +86,17 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { const teamId = project.teams[0]?.teamId || ''; const teamNames = project.teams.map((team) => team.teamName); - // Convert project rules to rules - // Sorted by rule code so both top-level rows and their children render in stable numeric order. - const allRules = useMemo(() => { + // Convert project rules to rules for display + // Sorted by rule code so both top-level rows and their children render in stable numeric order + const projectRuleList = useMemo(() => { if (!projectRules) return []; return projectRules.map((pr) => pr.rule).sort(compareRuleCodes); }, [projectRules]); // Get top-level rules (rules without a parent) const topLevelRules = useMemo(() => { - return allRules.filter((rule) => !rule.parentRule); - }, [allRules]); + return projectRuleList.filter((rule) => !rule.parentRule); + }, [projectRuleList]); // Handle completion update const handleStatusUpdate = async (ruleId: string, isComplete: boolean) => { @@ -129,7 +129,7 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { const projectRule = projectRules?.find((pr) => pr.rule.ruleId === rule.ruleId); if (projectRule) { // Only allow status updates for leaf rules - const hasChildren = allRules.some((r) => r.parentRule?.ruleId === rule.ruleId); + const hasChildren = projectRuleList.some((r) => r.parentRule?.ruleId === rule.ruleId); if (!hasChildren) { setSelectedProjectRule(projectRule); setStatusPopoverAnchor(event.currentTarget); @@ -173,13 +173,13 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { // Right content for rule rows - status badge. Leaf rules are clickable to open // the completion popover; parents show an aggregated, read-only status. const renderRightContent = (rule: Rule) => { - const isLeafRule = !allRules.some((r) => r.parentRule?.ruleId === rule.ruleId); + const isLeafRule = !projectRuleList.some((r) => r.parentRule?.ruleId === rule.ruleId); const isPopoverOpenForRule = Boolean(statusPopoverAnchor) && selectedProjectRule?.rule.ruleId === rule.ruleId; return ( handleStatusClick(e, rule) : undefined} /> @@ -303,7 +303,7 @@ export const ProjectRulesTab = ({ project }: ProjectRulesTabProps) => { = ({ allRules, rules // Completion in general view is for the whole ruleset, so no projectId is passed in const { mutateAsync: setCompletion } = useSetRuleCompletion(rulesetId, ''); - // Sort once by rule code so both top-level rows and their children render in a stable numeric order - const sortedRules = [...allRules].sort(compareRuleCodes); - const topLevelRules = sortedRules.filter((rule) => !rule.parentRule); + // Sort once by rule code so both top-level rows and their children render in a stable numeric order. + const sortedRules = useMemo(() => [...allRules].sort(compareRuleCodes), [allRules]); + const topLevelRules = useMemo(() => sortedRules.filter((rule) => !rule.parentRule), [sortedRules]); const handleStatusClose = () => { setStatusPopoverAnchor(null);