From 7f6133baf00b84b76888badb58496b126ebfcd52 Mon Sep 17 00:00:00 2001 From: AbhinavGonthina Date: Wed, 3 Dec 2025 19:08:46 -0500 Subject: [PATCH 1/2] delete rule modal push --- src/frontend/src/apis/rules.api.ts | 6 +- src/frontend/src/hooks/rules.hooks.ts | 25 +++- .../RulesComponents/DeleteRuleModal.tsx | 116 ++++++++++++++++++ .../src/pages/RulesPage/RulesetPage.tsx | 42 ++++++- src/frontend/src/utils/rules.utils.ts | 10 ++ 5 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 src/frontend/src/pages/RulesPage/RulesComponents/DeleteRuleModal.tsx create mode 100644 src/frontend/src/utils/rules.utils.ts diff --git a/src/frontend/src/apis/rules.api.ts b/src/frontend/src/apis/rules.api.ts index 5794b35b3e..0ce7c01c75 100644 --- a/src/frontend/src/apis/rules.api.ts +++ b/src/frontend/src/apis/rules.api.ts @@ -1 +1,5 @@ -// write api functions below here! +import axios from '../utils/axios'; + +export const deleteRule = (ruleId: string) => { + return axios.post(`/rules/rule/${ruleId}/delete`); +}; diff --git a/src/frontend/src/hooks/rules.hooks.ts b/src/frontend/src/hooks/rules.hooks.ts index 54a49f22a6..5ed962bc95 100644 --- a/src/frontend/src/hooks/rules.hooks.ts +++ b/src/frontend/src/hooks/rules.hooks.ts @@ -1 +1,24 @@ -// write hooks below here! +import { useMutation, useQueryClient } from 'react-query'; +import { deleteRule } from '../apis/rules.api'; +import { useToast } from './toasts.hooks'; + +export const useDeleteRule = () => { + const queryClient = useQueryClient(); + const toast = useToast(); + + return useMutation( + ['rules', 'delete'], + async (ruleId: string) => { + await deleteRule(ruleId); + }, + { + onSuccess: () => { + toast.success('Rule deleted successfully'); + queryClient.invalidateQueries(['rulesets']); + }, + onError: (error: Error) => { + toast.error(error.message); + } + } + ); +}; diff --git a/src/frontend/src/pages/RulesPage/RulesComponents/DeleteRuleModal.tsx b/src/frontend/src/pages/RulesPage/RulesComponents/DeleteRuleModal.tsx new file mode 100644 index 0000000000..ec83109c72 --- /dev/null +++ b/src/frontend/src/pages/RulesPage/RulesComponents/DeleteRuleModal.tsx @@ -0,0 +1,116 @@ +/* + * This file is part of NER's FinishLine and licensed under GNU AGPLv3. + * See the LICENSE file in the repository root folder for details. + */ + +import { Box, Typography, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; +import { Rule } from 'shared'; +import WarningIcon from '@mui/icons-material/Warning'; + +interface DeleteRuleModalProps { + open: boolean; + onHide: () => void; + onConfirm: () => void; + rule: Rule; + totalRulesToDelete: number; +} + +const DeleteRuleModal = ({ open, onHide, onConfirm, rule, totalRulesToDelete }: DeleteRuleModalProps) => { + const hasChildren = rule.subRuleIds.length > 0; + const titlePrefix = hasChildren ? 'Delete Rule Section:' : 'Delete Rule:'; + + const modalTitle = rule.ruleContent + ? `${titlePrefix} ${rule.ruleCode} - ${rule.ruleContent}` + : `${titlePrefix} ${rule.ruleCode}`; + + return ( + + {/* Header */} + + Confirm Deletion + + + {/* Body */} + + + {modalTitle} + + + + + {totalRulesToDelete} {totalRulesToDelete === 1 ? 'rule' : 'rules'} will be deleted + + + + + + {/* Footer */} + + + + + + + + + ); +}; + +export default DeleteRuleModal; diff --git a/src/frontend/src/pages/RulesPage/RulesetPage.tsx b/src/frontend/src/pages/RulesPage/RulesetPage.tsx index 5706ea16f8..35391c17b6 100644 --- a/src/frontend/src/pages/RulesPage/RulesetPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetPage.tsx @@ -14,6 +14,9 @@ import RuleActions from './RuleActions'; import { Rule } from 'shared'; import ErrorPage from '../ErrorPage'; import LoadingIndicator from '../../components/LoadingIndicator'; +import DeleteRuleModal from './RulesComponents/DeleteRuleModal'; +import { useDeleteRule } from '../../hooks/rules.hooks'; +import { countRulesToDelete } from '../../utils/rules.utils'; /** * Placeholder hook to fetch a single ruleset. @@ -158,9 +161,12 @@ const useSingleRuleset = (rulesetId: string) => { const RulesetPage: React.FC = () => { const { rulesetId } = useParams<{ rulesetId: string; tabValue?: string }>(); const [tabValue, setTabValue] = useState(0); + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [ruleToDelete, setRuleToDelete] = useState(null); const defaultTab = 'edit-rules'; const { data: ruleset, isError, error, isLoading } = useSingleRuleset(rulesetId); + const { mutateAsync: deleteRuleMutation, isLoading: isDeleting } = useDeleteRule(); const tabs = [ { tabUrlValue: 'edit-rules', tabName: 'Edit Rules' }, @@ -185,8 +191,11 @@ const RulesetPage: React.FC = () => { }; const handleRemoveRule = (ruleId: string) => { - // Placeholder - console.log('Remove rule:', ruleId); + const rule = ruleset.rules.find((r) => r.ruleId === ruleId); + if (rule) { + setRuleToDelete(rule); + setDeleteModalOpen(true); + } }; const handleEditRule = (ruleId: string) => { @@ -194,6 +203,25 @@ const RulesetPage: React.FC = () => { console.log('Edit rule:', ruleId); }; + const handleDeleteConfirm = async () => { + if (!ruleToDelete) return; + + try { + await deleteRuleMutation(ruleToDelete.ruleId); + setDeleteModalOpen(false); + setRuleToDelete(null); + } catch (error) { + console.error('Failed to delete rule:', error); + } + }; + + const handleDeleteCancel = () => { + setDeleteModalOpen(false); + setRuleToDelete(null); + }; + + const totalRulesToDelete = ruleToDelete ? countRulesToDelete(ruleToDelete, ruleset.rules) : 0; + // Filter to only show top-level rules const topLevelRules = ruleset.rules.filter((rule) => !rule.parentRule); @@ -286,6 +314,16 @@ const RulesetPage: React.FC = () => { {/* Assign Rules tab content will be added in a future ticket */} )} + + {ruleToDelete && ( + + )} ); }; diff --git a/src/frontend/src/utils/rules.utils.ts b/src/frontend/src/utils/rules.utils.ts new file mode 100644 index 0000000000..be98ae2471 --- /dev/null +++ b/src/frontend/src/utils/rules.utils.ts @@ -0,0 +1,10 @@ +import { Rule } from 'shared'; + +export const countRulesToDelete = (rule: Rule, allRules: Rule[]): number => { + let count = 1; + const children = allRules.filter((r) => rule.subRuleIds.includes(r.ruleId)); + for (const child of children) { + count += countRulesToDelete(child, allRules); + } + return count; +}; From 05edc6c53742496544b5d7b36221bef2c5acc0ef Mon Sep 17 00:00:00 2001 From: AbhinavGonthina Date: Wed, 3 Dec 2025 19:40:23 -0500 Subject: [PATCH 2/2] fixed linting errors --- src/frontend/src/pages/RulesPage/RulesetPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/pages/RulesPage/RulesetPage.tsx b/src/frontend/src/pages/RulesPage/RulesetPage.tsx index 35391c17b6..752bc77582 100644 --- a/src/frontend/src/pages/RulesPage/RulesetPage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetPage.tsx @@ -166,7 +166,7 @@ const RulesetPage: React.FC = () => { const defaultTab = 'edit-rules'; const { data: ruleset, isError, error, isLoading } = useSingleRuleset(rulesetId); - const { mutateAsync: deleteRuleMutation, isLoading: isDeleting } = useDeleteRule(); + const { mutateAsync: deleteRuleMutation } = useDeleteRule(); const tabs = [ { tabUrlValue: 'edit-rules', tabName: 'Edit Rules' },