Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/frontend/src/apis/rules.api.ts
Original file line number Diff line number Diff line change
@@ -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<RulesetType>(apiUrls.rulesetTypeCreate(), payload);
};
27 changes: 26 additions & 1 deletion src/frontend/src/hooks/rules.hooks.ts
Original file line number Diff line number Diff line change
@@ -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<RulesetType, Error, CreateRulesetTypePayload>(
['rulesetTypes', 'create'],
async (payload: CreateRulesetTypePayload) => {
const { data } = await createRulesetType(payload);
return data;
},
{
onSuccess: () => {
queryClient.invalidateQueries(['rulesetTypes']);
}
}
);
};
46 changes: 23 additions & 23 deletions src/frontend/src/pages/RulesPage/RulesetTypePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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',
Expand All @@ -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 (
<PageLayout title="Ruleset Types">
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: 'calc(100vh - 120px)' }}>
Expand Down Expand Up @@ -165,29 +180,14 @@ const RulesetTypePage: React.FC = () => {
justifyContent: { xs: 'center', md: 'flex-end' }
}}
>
<Button
className="viewButton"
variant="contained"
sx={{
borderRadius: '8px',
color: '#ededed',
backgroundColor: '#dd514c',
padding: { xs: '8px 16px', md: '2px 20px' },
mb: 1,
mr: { xs: 0, md: 2 },
display: 'flex',
fontSize: { xs: '14px', md: '16px' },
fontWeight: 700,
textTransform: 'none',
width: { xs: '100%', sm: 'auto' },
maxWidth: { xs: '300px', sm: 'none' },
'&:hover': {
backgroundColor: '#c74340'
}
}}
>
Add Ruleset
</Button>
<NERButton variant="contained" onClick={() => setAddRulesetTypeModalShow(!addRulesetTypeModalShow)}>
Add Ruleset Type
</NERButton>
<AddRulesetTypeModal
open={addRulesetTypeModalShow}
onHide={handleAddRulesetTypeCancel}
onFormSubmit={handleAddRulesetTypeConfirm}
/>
{/* Temporary for navigation */}
<NERButton onClick={() => history.push(`${routes.RULES}/placeholder_ruleset_id`)}>FSAE Ruleset</NERButton>
</Box>
Expand Down
104 changes: 104 additions & 0 deletions src/frontend/src/pages/RulesPage/components/AddRulesetTypeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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<void>;
}

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<AddRulesetTypeModalProps> = ({ open, onHide, onFormSubmit }) => {
const toast = useToast();

const {
formState: { errors },
handleSubmit,
reset,
control
} = useForm<RulesetTypeFormData>({
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 (
<NERFormModal
open={open}
onHide={handleModalClose}
title="Add Ruleset Type"
reset={handleReset}
handleUseFormSubmit={handleSubmit}
onFormSubmit={handleFormSubmit}
formId={'add-ruleset-type-form'}
showCloseButton
>
<Box>
<FormControl fullWidth error={!!errors.name}>
<FormLabel sx={sectionHeaderStyle}>Name Ruleset:</FormLabel>
<Controller
name="name"
control={control}
render={({ field }) => (
<TextField
{...field}
autoComplete="off"
placeholder="Name Ruleset"
error={!!errors.name}
fullWidth
sx={{ minWidth: '400px' }}
/>
)}
/>
<FormHelperText error>{errors.name?.message}</FormHelperText>
</FormControl>
</Box>
</NERFormModal>
);
};

export default AddRulesetTypeModal;
10 changes: 10 additions & 0 deletions src/frontend/src/utils/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`;

Expand Down Expand Up @@ -738,5 +744,9 @@ export const apiUrls = {
retrospectiveTimelines,
retrospectiveBudgets,

ruleset,
rulesetTypeCreate,
rulesetsCreate,

version
};