From 01d4192db43deca9b7e2d04dce3be793ed34bf7e Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Wed, 24 Dec 2025 13:49:19 -0500 Subject: [PATCH 1/2] #3549 ruleset type modal --- src/frontend/src/apis/rules.api.ts | 14 ++- src/frontend/src/hooks/rules.hooks.ts | 27 ++++- .../src/pages/RulesPage/RulesetTypePage.tsx | 46 ++++---- .../components/AddRulesetTypeModal.tsx | 105 ++++++++++++++++++ src/frontend/src/utils/urls.ts | 10 ++ 5 files changed, 177 insertions(+), 25 deletions(-) create mode 100644 src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx diff --git a/src/frontend/src/apis/rules.api.ts b/src/frontend/src/apis/rules.api.ts index 5794b35b3e..c0f743b58a 100644 --- a/src/frontend/src/apis/rules.api.ts +++ b/src/frontend/src/apis/rules.api.ts @@ -1 +1,13 @@ -// write api functions below here! +import { RulesetType } from 'shared'; +import axios from '../utils/axios'; +import { apiUrls } from '../utils/urls'; + +/** + * Creates a new ruleset type + * + * @param payload the data for creating the ruleset type + * @returns the created ruleset type + */ +export const createRulesetType = (payload: { name: string }) => { + return axios.post(apiUrls.rulesetTypeCreate(), payload); +}; diff --git a/src/frontend/src/hooks/rules.hooks.ts b/src/frontend/src/hooks/rules.hooks.ts index 54a49f22a6..3619b60d4d 100644 --- a/src/frontend/src/hooks/rules.hooks.ts +++ b/src/frontend/src/hooks/rules.hooks.ts @@ -1 +1,26 @@ -// write hooks below here! +import { useMutation, useQueryClient } from 'react-query'; +import { RulesetType } from 'shared'; +import { createRulesetType } from '../apis/rules.api'; + +interface CreateRulesetTypePayload { + name: string; +} + +/** + * Custom React Hook to create a new ruleset type + */ +export const useCreateRulesetType = () => { + const queryClient = useQueryClient(); + return useMutation( + ['rulesetTypes', 'create'], + async (payload: CreateRulesetTypePayload) => { + const { data } = await createRulesetType(payload); + return data; + }, + { + onSuccess: () => { + queryClient.invalidateQueries(['rulesetTypes']); + } + } + ); +}; diff --git a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx index 50b51d2bf6..ba98024311 100644 --- a/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx +++ b/src/frontend/src/pages/RulesPage/RulesetTypePage.tsx @@ -20,6 +20,9 @@ import { datePipe } from '../../utils/pipes'; import { NERButton } from '../../components/NERButton'; import { useHistory } from 'react-router-dom'; import { routes } from '../../utils/routes'; +import AddRulesetTypeModal from './components/AddRulesetTypeModal'; +import { useState } from 'react'; +import { useCreateRulesetType } from '../../hooks/rules.hooks'; type RulesetTypeColumnId = 'id' | 'name' | 'lastUpdated' | 'revisions' | 'actions'; @@ -33,6 +36,8 @@ const RulesetTypePage: React.FC = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const [addRulesetTypeModalShow, setAddRulesetTypeModalShow] = useState(false); + const headCells: readonly RulesetTypeHeadCell[] = [ { id: 'name', @@ -58,6 +63,16 @@ const RulesetTypePage: React.FC = () => { { id: '2', name: 'Ruleset 2', lastUpdated: new Date('2024-01-14'), revisions: 3, actions: 'Edit' } ]; + const { mutateAsync: createRulesetType } = useCreateRulesetType(); + + const handleAddRulesetTypeConfirm = async (data: { name: string }) => { + await createRulesetType({ name: data.name }); + }; + + const handleAddRulesetTypeCancel = () => { + setAddRulesetTypeModalShow(false); + }; + return ( @@ -165,29 +180,14 @@ const RulesetTypePage: React.FC = () => { justifyContent: { xs: 'center', md: 'flex-end' } }} > - + setAddRulesetTypeModalShow(!addRulesetTypeModalShow)}> + Add Ruleset Type + + {/* Temporary for navigation */} history.push(`${routes.RULES}/placeholder_ruleset_id`)}>FSAE Ruleset diff --git a/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx b/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx new file mode 100644 index 0000000000..677e8758da --- /dev/null +++ b/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx @@ -0,0 +1,105 @@ +import { FormControl, FormHelperText, FormLabel, TextField } from '@mui/material'; +import { Box } from '@mui/system'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Controller, useForm } from 'react-hook-form'; +import NERFormModal from '../../../components/NERFormModal'; +import { useToast } from '../../../hooks/toasts.hooks'; + +interface RulesetTypeFormData { + name: string; +} + +interface AddRulesetTypeModalProps { + open: boolean; + onHide: () => void; + onFormSubmit: (data: RulesetTypeFormData) => Promise; +} + +const sectionHeaderStyle = { + fontWeight: 'bold', + color: '#ef4345', + textDecoration: 'underline', + fontSize: '1rem', + textUnderlineOffset: '5px', + marginBottom: '10px' +}; + +const schema = yup.object({ + name: yup.string().required('Name is required') +}); + +const AddRulesetTypeModal: React.FC = ({ open, onHide, onFormSubmit }) => { + const toast = useToast(); + + const { + formState: { errors }, + handleSubmit, + reset, + control + } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + name: '' + } + }); + + const handleFormSubmit = async (data: RulesetTypeFormData) => { + try { + await onFormSubmit(data); + toast.success('Ruleset Type Successfully Added'); + reset(); + onHide(); + } catch (error: unknown) { + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + const handleModalClose = () => { + reset(); + onHide(); + }; + + const handleReset = () => { + reset(); + }; + + return ( + + + + Name Ruleset: + ( + + )} + /> + {errors.name?.message} + + + + ); +}; + +export default AddRulesetTypeModal; diff --git a/src/frontend/src/utils/urls.ts b/src/frontend/src/utils/urls.ts index f8367bc87f..36785ec59b 100644 --- a/src/frontend/src/utils/urls.ts +++ b/src/frontend/src/utils/urls.ts @@ -436,6 +436,12 @@ const retrospectiveTimelines = (startDate?: Date, endDate?: Date) => (endDate ? `end=${encodeURIComponent(endDate.toISOString())}` : ''); const retrospectiveBudgets = () => `${API_URL}/retrospective/budgets`; +/************** Rule Endpoints ***************/ +const rules = () => `${API_URL}/rules`; +const ruleset = () => `${rules()}/ruleset`; +const rulesetTypeCreate = () => `${rules()}/rulesetType/create`; +const rulesetsCreate = () => `${ruleset()}/create`; + /**************** Other Endpoints ****************/ const version = () => `https://api.github.com/repos/Northeastern-Electric-Racing/FinishLine/releases/latest`; @@ -738,5 +744,9 @@ export const apiUrls = { retrospectiveTimelines, retrospectiveBudgets, + ruleset, + rulesetTypeCreate, + rulesetsCreate, + version }; From 9d67a3ce036451193b7294c3f64b81eb62ba9f24 Mon Sep 17 00:00:00 2001 From: Ciel Bellerose Date: Wed, 24 Dec 2025 13:56:43 -0500 Subject: [PATCH 2/2] #3549 typescript fix --- .../src/pages/RulesPage/components/AddRulesetTypeModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx b/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx index 677e8758da..0e9caf9c9c 100644 --- a/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx +++ b/src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx @@ -76,7 +76,6 @@ const AddRulesetTypeModal: React.FC = ({ open, onHide, onFormSubmit={handleFormSubmit} formId={'add-ruleset-type-form'} showCloseButton - submitText="Add Ruleset" >