diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx new file mode 100644 index 0000000000..e4e6ba6e11 --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/CreateGuestDefinitionFormModal.tsx @@ -0,0 +1,22 @@ +import ErrorPage from '../../ErrorPage'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import { useCreateGuestDefinition } from '../../../hooks/recruitment.hooks'; +import { GuestDefinitionType } from 'shared'; +import GuestDefinitionFormModal from './GuestDefinitionFormModal'; + +interface CreateGuestDefinitionFormModalProps { + open: boolean; + handleClose: () => void; + type: GuestDefinitionType; +} + +const CreateGuestDefinitionFormModal = ({ open, handleClose, type }: CreateGuestDefinitionFormModalProps) => { + const { isLoading, isError, error, mutateAsync } = useCreateGuestDefinition(); + + if (isError) return ; + if (isLoading) return ; + + return ; +}; + +export default CreateGuestDefinitionFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx new file mode 100644 index 0000000000..f10c133d82 --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/EditGuestDefinitionFormModal.tsx @@ -0,0 +1,30 @@ +import ErrorPage from '../../ErrorPage'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import { useEditGuestDefinitions } from '../../../hooks/recruitment.hooks'; +import { GuestDefinition } from 'shared'; +import GuestDefinitionFormModal from './GuestDefinitionFormModal'; + +interface EditGuestDefinitionFormModalProps { + open: boolean; + handleClose: () => void; + definition: GuestDefinition; +} + +const EditGuestDefinitionFormModal = ({ open, handleClose, definition }: EditGuestDefinitionFormModalProps) => { + const { isLoading, isError, error, mutateAsync } = useEditGuestDefinitions(definition.definitionId); + + if (isError) return ; + if (isLoading) return ; + + return ( + + ); +}; + +export default EditGuestDefinitionFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx new file mode 100644 index 0000000000..2cde85f9fc --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionFormModal.tsx @@ -0,0 +1,121 @@ +import { useForm } from 'react-hook-form'; +import NERFormModal from '../../../components/NERFormModal'; +import { FormControl, FormLabel, FormHelperText } from '@mui/material'; +import ReactHookTextField from '../../../components/ReactHookTextField'; +import * as yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import useFormPersist from 'react-hook-form-persist'; +import { FormStorageKey } from '../../../utils/form'; +import { GuestDefinitionPayload } from '../../../hooks/recruitment.hooks'; +import { GuestDefinition, GuestDefinitionType } from 'shared'; + +interface GuestDefinitionFormModalProps { + open: boolean; + handleClose: () => void; + type: GuestDefinitionType; + defaultValues?: GuestDefinition; + onSubmit: (data: GuestDefinitionPayload) => Promise; +} + +const schema = yup.object().shape({ + term: yup.string().required('Term is required'), + description: yup.string().required('Description is required'), + order: yup.number().required('Order is required').min(0, 'Order must be non-negative'), + icon: yup.string().optional(), + buttonText: yup.string().optional(), + buttonLink: yup.string().optional() +}); + +const GuestDefinitionFormModal: React.FC = ({ + open, + handleClose, + type, + defaultValues, + onSubmit +}) => { + const onFormSubmit = async (data: Omit) => { + await onSubmit({ + ...data, + type, + icon: data.icon || undefined, + buttonText: data.buttonText || undefined, + buttonLink: data.buttonLink || undefined + }); + handleClose(); + }; + + const { + handleSubmit, + control, + reset, + formState: { errors }, + watch, + setValue + } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + term: defaultValues?.term ?? '', + description: defaultValues?.description ?? '', + order: defaultValues?.order ?? 0, + icon: defaultValues?.icon ?? '', + buttonText: defaultValues?.buttonText ?? '', + buttonLink: defaultValues?.buttonLink ?? '' + } + }); + + const formStorageKey = defaultValues ? FormStorageKey.EDIT_GUEST_DEFINITION : FormStorageKey.CREATE_GUEST_DEFINITION; + + useFormPersist(formStorageKey, { watch, setValue }); + + const handleCancel = () => { + reset({ term: '', description: '', order: 0, icon: '', buttonText: '', buttonLink: '' }); + sessionStorage.removeItem(formStorageKey); + handleClose(); + }; + + return ( + reset({ term: '', description: '', order: 0, icon: '', buttonText: '', buttonLink: '' })} + handleUseFormSubmit={handleSubmit} + onFormSubmit={onFormSubmit} + formId="guest-definition-form" + showCloseButton + > + + Term* + + {errors.term?.message} + + + Description* + + {errors.description?.message} + + + Order* + + {errors.order?.message} + + + Icon + + {errors.icon?.message} + + + Button Text + + {errors.buttonText?.message} + + + Button Link + + {errors.buttonLink?.message} + + + ); +}; + +export default GuestDefinitionFormModal; diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx new file mode 100644 index 0000000000..2208da7c1b --- /dev/null +++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestDefinitionsTable.tsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react'; +import { TableRow, TableCell, Box, Table as MuiTable, TableHead, TableBody, Typography, Button } from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { GuestDefinition, GuestDefinitionType } from 'shared'; +import { NERButton } from '../../../components/NERButton'; +import { useAllGuestDefinitions, useDeleteGuestDefinition } from '../../../hooks/recruitment.hooks'; +import LoadingIndicator from '../../../components/LoadingIndicator'; +import { useHistoryState } from '../../../hooks/misc.hooks'; +import ErrorPage from '../../ErrorPage'; +import NERDeleteModal from '../../../components/NERDeleteModal'; +import { useToast } from '../../../hooks/toasts.hooks'; +import CreateGuestDefinitionFormModal from './CreateGuestDefinitionFormModal'; +import EditGuestDefinitionFormModal from './EditGuestDefinitionFormModal'; + +interface GuestDefinitionsTableProps { + type: GuestDefinitionType; +} + +const GuestDefinitionsTable = ({ type }: GuestDefinitionsTableProps) => { + const [createModalShow, setCreateModalShow] = useHistoryState('', false); + const [definitionEditing, setDefinitionEditing] = useHistoryState('', undefined); + const [definitionToDelete, setDefinitionToDelete] = useState(undefined); + const { mutateAsync: deleteGuestDefinition } = useDeleteGuestDefinition(); + const toast = useToast(); + + const { isLoading, isError, error, data: allDefinitions } = useAllGuestDefinitions(); + + const handleDelete = async (id: string) => { + setDefinitionToDelete(undefined); + try { + await deleteGuestDefinition(id); + toast.success('Guest definition deleted successfully'); + } catch (e: unknown) { + if (e instanceof Error) { + toast.error(e.message, 3000); + } + } + }; + + if (isError) return ; + if (!allDefinitions || isLoading) return ; + + const definitions = allDefinitions.filter((d) => d.type === type); + + const rows = definitions.map((definition: GuestDefinition, index: number) => ( + + + {definition.term} + + + {definition.description} + + + + + + + )); + + return ( + + {createModalShow && ( + setCreateModalShow(false)} type={type} /> + )} + {definitionEditing && ( + setDefinitionEditing(undefined)} + definition={definitionEditing} + /> + )} + + + + + Term + + + Description + + + + {rows} + + + setCreateModalShow(true)}> + Add Guest Definition + + + {definitionToDelete && ( + setDefinitionToDelete(undefined)} + formId="delete-guest-definition-form" + dataType="Guest Definition" + onFormSubmit={() => { + handleDelete(definitionToDelete.definitionId); + }} + /> + )} + + ); +}; + +export default GuestDefinitionsTable; diff --git a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx index bc7a24613f..de745504c6 100644 --- a/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx +++ b/src/frontend/src/pages/AdminToolsPage/EditGuestView/GuestViewConfig.tsx @@ -20,6 +20,8 @@ import { useToast } from '../../../hooks/toasts.hooks'; import { MAX_FILE_SIZE } from 'shared'; import UsefulLinksTable from '../OnboardingConfig/UsefulLinks/UsefulLinksTable'; import LinkTypeTable from '../ProjectsConfig/LinkTypes/LinkTypeTable'; +import GuestDefinitionsTable from './GuestDefinitionsTable'; +import { GuestDefinitionType } from 'shared'; const platformDescriptionSchema = yup.object().shape({ platformDescription: yup.string().required() @@ -178,6 +180,18 @@ const GuestViewConfig: React.FC = () => { + + + Project Management Definitions + + + + + + Info Page Definitions + + + ); }; diff --git a/src/frontend/src/utils/form.ts b/src/frontend/src/utils/form.ts index 7f4bfe3daa..2d34d337db 100644 --- a/src/frontend/src/utils/form.ts +++ b/src/frontend/src/utils/form.ts @@ -86,5 +86,7 @@ export enum FormStorageKey { CREATE_MACHINERY = 'CREATE_MACHINERY', EDIT_MACHINERY = 'EDIT_MACHINERY', CREATE_EVENT_TYPE = 'CREATE_EVENT_TYPE', - EDIT_EVENT_TYPE = 'EDIT_EVENT_TYPE' + EDIT_EVENT_TYPE = 'EDIT_EVENT_TYPE', + EDIT_GUEST_DEFINITION = 'EDIT_GUEST_DEFINITION', + CREATE_GUEST_DEFINITION = 'CREATE_GUEST_DEFINITION' }