diff --git a/client/src/data/types/Trainee.ts b/client/src/data/types/Trainee.ts index b52f77e5..70a9906d 100644 --- a/client/src/data/types/Trainee.ts +++ b/client/src/data/types/Trainee.ts @@ -112,6 +112,7 @@ export interface Trainee { readonly createdAt: Date; readonly updatedAt: Date; displayName: string; + profilePath: string; imageURL?: string; thumbnailURL?: string; personalInfo: TraineePersonalInfo; diff --git a/client/src/features/cohorts/CohortsPage.tsx b/client/src/features/cohorts/CohortsPage.tsx index 52322cdf..38a0889c 100644 --- a/client/src/features/cohorts/CohortsPage.tsx +++ b/client/src/features/cohorts/CohortsPage.tsx @@ -1,5 +1,6 @@ import { ErrorBox, Loader } from '../../components'; +import { ActionsCard } from './components/ActionsCard'; import Box from '@mui/material/Box'; import { Cohort } from '../cohorts/Cohorts'; import CohortAccordion from './components/CohortAccordion'; @@ -17,34 +18,35 @@ const CohortsPage = () => { document.title = 'Cohorts | Dojo'; }, []); - const { isLoading, isError, data, error, isFetching } = useCohortsData(); - - if (isLoading || isFetching) { - return ; - } - - if (isError && error instanceof Error) { - return ( - //fixme: make this reusable - - return ; - - ); - } + const { isError, data, error, isPending } = useCohortsData(); return ( Cohorts Overview - - - {data?.sort(compareCohort).map((cohort: Cohort, index: number) => ( -
- -
- ))} -
-
+ + {isPending && ( + + + + )} + {isError && ( + + + + )} + + + {data?.sort(compareCohort).map((cohort: Cohort, index: number) => ( + + + + ))} +
); diff --git a/client/src/features/cohorts/components/ActionsCard.tsx b/client/src/features/cohorts/components/ActionsCard.tsx new file mode 100644 index 00000000..89144689 --- /dev/null +++ b/client/src/features/cohorts/components/ActionsCard.tsx @@ -0,0 +1,25 @@ +import { Box, Button } from '@mui/material'; + +import { Add } from '@mui/icons-material'; +import { AddTraineeDialog } from '../../trainee-profile/create/AddTraineeDialog'; +import { useState } from 'react'; + +export const ActionsCard = () => { + const [isAddTraineeDialogOpen, setIsAddTraineeDialogOpen] = useState(false); + + const handleOpenAddTraineeDialog = () => { + setIsAddTraineeDialogOpen(true); + }; + + const handleCloseAddTraineeDialog = () => { + setIsAddTraineeDialogOpen(false); + }; + return ( + + + + + ); +}; diff --git a/client/src/features/trainee-profile/context/useTraineeProfileContext.tsx b/client/src/features/trainee-profile/context/useTraineeProfileContext.tsx index bda0b19c..ff2f7835 100644 --- a/client/src/features/trainee-profile/context/useTraineeProfileContext.tsx +++ b/client/src/features/trainee-profile/context/useTraineeProfileContext.tsx @@ -1,7 +1,7 @@ import React, { createContext, useContext } from 'react'; -import { SaveTraineeRequestData } from '../personal-info/data/useTraineeInfoData'; import { Trainee } from '../../../data/types/Trainee'; +import { UpdateTraineeRequestData } from '../personal-info/data/useTraineeInfoData'; export type TraineeProfileContextType = { traineeId: string; @@ -12,7 +12,7 @@ export type TraineeProfileContextType = { setIsEditMode: (isEditMode: boolean) => void; isSavingProfile: boolean; setIsSavingProfile: React.Dispatch>; - getTraineeInfoChanges: (trainee: Trainee) => SaveTraineeRequestData; + getTraineeInfoChanges: (trainee: Trainee) => UpdateTraineeRequestData; }; export const TraineeProfileContext = createContext({ @@ -24,7 +24,7 @@ export const TraineeProfileContext = createContext({ setIsEditMode: () => {}, isSavingProfile: false, setIsSavingProfile: () => {}, - getTraineeInfoChanges: () => ({}) as SaveTraineeRequestData, + getTraineeInfoChanges: () => ({}) as UpdateTraineeRequestData, }); export const useTraineeProfileContext = () => useContext(TraineeProfileContext); diff --git a/client/src/features/trainee-profile/context/useTraineeProfileProvider.tsx b/client/src/features/trainee-profile/context/useTraineeProfileProvider.tsx index 6a36276a..8b9695f1 100644 --- a/client/src/features/trainee-profile/context/useTraineeProfileProvider.tsx +++ b/client/src/features/trainee-profile/context/useTraineeProfileProvider.tsx @@ -7,8 +7,8 @@ import { TraineePersonalInfo, } from '../../../data/types/Trainee'; -import { SaveTraineeRequestData } from '../personal-info/data/useTraineeInfoData'; import { TraineeProfileContext } from './useTraineeProfileContext'; +import { UpdateTraineeRequestData } from '../personal-info/data/useTraineeInfoData'; type TraineeInfoType = TraineePersonalInfo | TraineeContactInfo | TraineeEmploymentInfo | TraineeEducationInfo; @@ -32,10 +32,10 @@ export const TraineeProfileProvider = ({ /** * Function to get the changes made to the trainee's profile (every tab) - * @returns {SaveTraineeRequestData} - Object with the changes made to the trainee's profile. + * @returns {UpdateTraineeRequestData} - Object with the changes made to the trainee's profile. * The object is structured as follows: { personalInfo, contactInfo, educationInfo, employmentInfo } */ - const getTraineeInfoChanges = (): SaveTraineeRequestData => { + const getTraineeInfoChanges = (): UpdateTraineeRequestData => { const personalInfo: Partial | null = getChangedFields( originalTrainee.personalInfo, trainee.personalInfo @@ -53,7 +53,7 @@ export const TraineeProfileProvider = ({ trainee.educationInfo ); - const dataToSave: SaveTraineeRequestData = {}; + const dataToSave: UpdateTraineeRequestData = {}; // add the changed fields to the dataToSave object if not null if (personalInfo) dataToSave.personalInfo = personalInfo; diff --git a/client/src/features/trainee-profile/create/AddTraineeDialog.tsx b/client/src/features/trainee-profile/create/AddTraineeDialog.tsx new file mode 100644 index 00000000..d4a659dd --- /dev/null +++ b/client/src/features/trainee-profile/create/AddTraineeDialog.tsx @@ -0,0 +1,101 @@ +import { Alert, Box, Dialog, SelectChangeEvent, Typography } from '@mui/material'; +import { FormErrors, FormState, NewTraineeForm } from './components/NewTraineeForm'; +import { JobPath, LearningStatus } from '../../../data/types/Trainee'; + +import { useCreateTraineeProfile } from './data/mutations'; +import { useNavigate } from 'react-router-dom'; +import { useState } from 'react'; +import { validateAndCollectFormErrors } from './lib/formValidation'; + +interface AddTraineeDialogProps { + isOpen: boolean; + handleClose: () => void; +} +export const AddTraineeDialog: React.FC = ({ isOpen, handleClose }) => { + const initialState = { + firstName: '', + lastName: '', + gender: null, + email: '', + cohort: 0, + learningStatus: LearningStatus.Studying, + jobPath: JobPath.NotGraduated, + }; + + const navigate = useNavigate(); + const { + mutate: createTrainee, + isPending, + error: submitError, + } = useCreateTraineeProfile({ + onSuccess: (profilePath: string) => onSuccess(profilePath), + }); + + const [errors, setErrors] = useState(null); + + const [formState, setFormState] = useState(initialState); + const onClose = () => { + setFormState(initialState); + setErrors(null); + handleClose(); + }; + + const onSuccess = (path: string) => { + onClose(); + navigate(path); + }; + const handleTextChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormState((prevState: FormState) => ({ + ...prevState, + [name]: value, + })); + }; + + const handleSelectChange = (event: SelectChangeEvent) => { + const { name, value } = event.target; + + setFormState((prevState: FormState) => ({ + ...prevState, + [name]: value, + })); + }; + + const handleSubmit: React.ComponentProps<'form'>['onSubmit'] = (event) => { + event.preventDefault(); + const errors = validateAndCollectFormErrors(formState); + + if (errors) { + setErrors(errors); + return; + } + createTrainee(formState); + }; + + return ( + + + + New trainee profile + + + + {submitError && ( + + + An error occurred while creating the trainee profile: {submitError?.message && 'unknown'} + + + )} + + + ); +}; diff --git a/client/src/features/trainee-profile/create/api/api.ts b/client/src/features/trainee-profile/create/api/api.ts new file mode 100644 index 00000000..9f562818 --- /dev/null +++ b/client/src/features/trainee-profile/create/api/api.ts @@ -0,0 +1,8 @@ +import { CreateTraineeRequestData } from './types'; +import { Trainee } from '../../../../data/types/Trainee'; +import axios from 'axios'; + +export const createTrainee = async (request: CreateTraineeRequestData): Promise => { + const { data } = await axios.post(`/api/trainees`, request); + return data; +}; diff --git a/client/src/features/trainee-profile/create/api/types.ts b/client/src/features/trainee-profile/create/api/types.ts new file mode 100644 index 00000000..10923508 --- /dev/null +++ b/client/src/features/trainee-profile/create/api/types.ts @@ -0,0 +1,13 @@ +import { + TraineeContactInfo, + TraineeEducationInfo, + TraineeEmploymentInfo, + TraineePersonalInfo, +} from '../../../../data/types/Trainee'; + +export type CreateTraineeRequestData = { + personalInfo: Pick; + contactInfo: Pick; + educationInfo: Pick; + employmentInfo: Pick; +}; diff --git a/client/src/features/trainee-profile/create/components/NewTraineeForm.tsx b/client/src/features/trainee-profile/create/components/NewTraineeForm.tsx new file mode 100644 index 00000000..e2fec5d8 --- /dev/null +++ b/client/src/features/trainee-profile/create/components/NewTraineeForm.tsx @@ -0,0 +1,109 @@ +import { Button, SelectChangeEvent, Stack } from '@mui/material'; +import { Gender, JobPath, LearningStatus } from '../../../../data/types/Trainee'; + +import { GenderSelect } from '../../profile/components/GenderSelect'; +import { JobPathSelect } from '../../profile/components/JobPathSelect'; +import { LearningStatusSelect } from '../../profile/components/LearningStatusSelect'; +import TextFieldWrapper from './TextFieldWrapper'; + +export type FormState = { + firstName: string; + lastName: string; + gender: Gender | null; + email: string; + cohort: number; + learningStatus: LearningStatus; + jobPath: JobPath; +}; + +export type FormErrors = { + firstName?: string; + lastName?: string; + gender?: string; + email?: string; + cohort?: string; +}; + +export const NewTraineeForm: React.FC<{ + isLoading: boolean; + formState: FormState; + errors: FormErrors | null; + handleChange: (e: React.ChangeEvent) => void; + handleClose: () => void; + handleSelect: (event: SelectChangeEvent) => void; + handleSubmit: React.ComponentProps<'form'>['onSubmit']; +}> = ({ isLoading, formState, errors, handleChange, handleSelect, handleSubmit, handleClose }) => { + return ( +
+ + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/client/src/features/trainee-profile/create/components/TextFieldWrapper.tsx b/client/src/features/trainee-profile/create/components/TextFieldWrapper.tsx new file mode 100644 index 00000000..004fa281 --- /dev/null +++ b/client/src/features/trainee-profile/create/components/TextFieldWrapper.tsx @@ -0,0 +1,33 @@ +import TextField, { TextFieldProps } from '@mui/material/TextField'; + +import React from 'react'; +import { Stack } from '@mui/material'; + +type TextFieldWrapperProps = TextFieldProps & { + maxLength?: number; +}; + +const TextFieldWrapper: React.FC = (props) => { + const handleChange = (e: React.ChangeEvent) => { + const { value } = e.target; + // maxLength is ignored for number type, so we need to check the length of the value manually + if (props.maxLength && props.type === 'number' && value.length > props.maxLength) { + return; + } + + props.onChange?.(e); + }; + return ( + + + + ); +}; + +export default TextFieldWrapper; diff --git a/client/src/features/trainee-profile/create/data/mutations.ts b/client/src/features/trainee-profile/create/data/mutations.ts new file mode 100644 index 00000000..31f7c9d0 --- /dev/null +++ b/client/src/features/trainee-profile/create/data/mutations.ts @@ -0,0 +1,40 @@ +import { CreateTraineeRequestData } from '../api/types'; +import { FormState } from '../components/NewTraineeForm'; +import { Trainee } from '../../../../data/types/Trainee'; +import { createTrainee } from '../api/api'; +import { useMutation } from '@tanstack/react-query'; + +const mapFormStateToRequestData = (formState: FormState): CreateTraineeRequestData => { + return { + personalInfo: { + firstName: formState.firstName, + lastName: formState.lastName, + gender: formState.gender!, + }, + contactInfo: { + email: formState.email, + }, + educationInfo: { + startCohort: Number(formState.cohort), + currentCohort: Number(formState.cohort), + learningStatus: formState.learningStatus, + }, + employmentInfo: { + jobPath: formState.jobPath, + }, + }; +}; + +export const useCreateTraineeProfile = (options?: { + onSuccess?: (id: string) => void; + onError?: (err: unknown) => void; +}) => { + return useMutation({ + mutationFn: async (form: FormState) => createTrainee(mapFormStateToRequestData(form)), + onError: (error) => { + console.error('Error creating trainee profile:', error); + options?.onError?.(error); + }, + onSuccess: (data: Trainee) => options?.onSuccess?.(data.profilePath), + }); +}; diff --git a/client/src/features/trainee-profile/create/lib/formValidation.ts b/client/src/features/trainee-profile/create/lib/formValidation.ts new file mode 100644 index 00000000..f00fe8f0 --- /dev/null +++ b/client/src/features/trainee-profile/create/lib/formValidation.ts @@ -0,0 +1,44 @@ +import { FormErrors, FormState } from '../components/NewTraineeForm'; + +const FIELD_REQUIRED_ERROR = 'This field is required'; + +const nameValidationError = (name: string): string | null => { + if (!name) return FIELD_REQUIRED_ERROR; + if (name.length < 2) return 'Name must be at least 2 characters'; + return null; +}; + +const emailValidationError = (email: string) => { + if (!email) return FIELD_REQUIRED_ERROR; + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) return 'Email must be of format name@domain.com'; + return null; +}; + +const cohortValidationError = (cohort: number | undefined) => { + if (cohort === undefined) return FIELD_REQUIRED_ERROR; + if (cohort < 0) return 'Cohort must be a positive number'; + return null; +}; + +/** + * Validates fields of the form and returns an object with error messages for each field that fails validation. If there are no errors, returns null. + * @param formState + * @returns + */ +export const validateAndCollectFormErrors = (formState: FormState): FormErrors | null => { + const errors: FormErrors = {}; + const nameError = nameValidationError(formState.firstName); + if (nameError) errors.firstName = nameError; + + const lastNameError = nameValidationError(formState.lastName); + const emailError = emailValidationError(formState.email); + const cohortError = cohortValidationError(formState.cohort); + if (lastNameError) errors.lastName = lastNameError; + if (emailError) errors.email = emailError; + if (cohortError) errors.cohort = cohortError; + if (!formState.gender) errors.gender = FIELD_REQUIRED_ERROR; + + return Object.keys(errors).length > 0 ? errors : null; +}; diff --git a/client/src/features/trainee-profile/education/EducationInfo.tsx b/client/src/features/trainee-profile/education/EducationInfo.tsx index 9e0e5955..5b9f641f 100644 --- a/client/src/features/trainee-profile/education/EducationInfo.tsx +++ b/client/src/features/trainee-profile/education/EducationInfo.tsx @@ -3,6 +3,7 @@ import { LearningStatus, QuitReason, Trainee } from '../../../data/types/Trainee import { createSelectChangeHandler, createTextChangeHandler } from '../utils/formHelper'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { LearningStatusSelect } from '../profile/components/LearningStatusSelect'; import React from 'react'; import { StrikesComponent } from './strikes/StrikesComponent'; import { TestsComponent } from './tests/TestsComponent'; @@ -71,6 +72,8 @@ const EducationInfo = () => { {/* Learning status */} + + Learning status - Not graduated - Searching - Internship - Tech job - Non-tech job - Not searching - Other studies - No longer helping - - + {/* CV */} diff --git a/client/src/features/trainee-profile/personal-info/PersonalInfo.tsx b/client/src/features/trainee-profile/personal-info/PersonalInfo.tsx index df8dc04d..90e52265 100644 --- a/client/src/features/trainee-profile/personal-info/PersonalInfo.tsx +++ b/client/src/features/trainee-profile/personal-info/PersonalInfo.tsx @@ -1,15 +1,9 @@ -import { - Background, - EducationLevel, - EnglishLevel, - Gender, - Pronouns, - ResidencyStatus, -} from '../../../data/types/Trainee'; +import { Background, EducationLevel, EnglishLevel, Pronouns, ResidencyStatus } from '../../../data/types/Trainee'; import { Box, FormControl, InputLabel, MenuItem, Select, TextField } from '@mui/material'; import { createSelectChangeHandler, createTextChangeHandler } from '../utils/formHelper'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { GenderSelect } from '../profile/components/GenderSelect'; import { useTraineeProfileContext } from '../context/useTraineeProfileContext'; const NoIcon = () => null; @@ -88,24 +82,12 @@ const PersonalInfo = () => {
{/* Gender */} - - Gender - - + {/* Pronouns */} diff --git a/client/src/features/trainee-profile/personal-info/api/api.ts b/client/src/features/trainee-profile/personal-info/api/api.ts index 801dbb8e..254c25f4 100644 --- a/client/src/features/trainee-profile/personal-info/api/api.ts +++ b/client/src/features/trainee-profile/personal-info/api/api.ts @@ -1,13 +1,13 @@ -import axios from 'axios'; import { Trainee } from '../../../../data/types/Trainee'; -import { SaveTraineeRequestData } from './types'; +import { UpdateTraineeRequestData } from './types'; +import axios from 'axios'; -export const getTraineeInfo = async (traineeId: string) => { +export const getTrainee = async (traineeId: string) => { const { data } = await axios.get(`/api/trainees/${traineeId}`); return data; }; -export const saveTraineeInfo = async (traineeId: string, dataToSave: SaveTraineeRequestData) => { +export const updateTrainee = async (traineeId: string, dataToSave: UpdateTraineeRequestData) => { const { data } = await axios.patch(`/api/trainees/${traineeId}`, dataToSave); return data; }; diff --git a/client/src/features/trainee-profile/personal-info/api/types.ts b/client/src/features/trainee-profile/personal-info/api/types.ts index d568677a..b2271ab7 100644 --- a/client/src/features/trainee-profile/personal-info/api/types.ts +++ b/client/src/features/trainee-profile/personal-info/api/types.ts @@ -5,7 +5,7 @@ import { TraineePersonalInfo, } from '../../../../data/types/Trainee'; -export interface SaveTraineeRequestData { +export interface UpdateTraineeRequestData { personalInfo?: Partial; contactInfo?: Partial; educationInfo?: Partial; diff --git a/client/src/features/trainee-profile/personal-info/data/useTraineeInfoData.tsx b/client/src/features/trainee-profile/personal-info/data/useTraineeInfoData.tsx index dfcba073..db6cdde8 100644 --- a/client/src/features/trainee-profile/personal-info/data/useTraineeInfoData.tsx +++ b/client/src/features/trainee-profile/personal-info/data/useTraineeInfoData.tsx @@ -1,7 +1,8 @@ -import { Trainee } from '../../../../data/types/Trainee'; +import { getTrainee, updateTrainee } from '../api/api'; import { useMutation, useQuery } from '@tanstack/react-query'; -import { getTraineeInfo, saveTraineeInfo } from '../api/api'; -import { SaveTraineeRequestData } from '../api/types'; + +import { Trainee } from '../../../../data/types/Trainee'; +import { UpdateTraineeRequestData } from '../api/types'; /** * A React Query hook that fetches trainee information data form api. @@ -11,7 +12,7 @@ import { SaveTraineeRequestData } from '../api/types'; export const useTraineeInfoData = (traineeId: string) => { return useQuery({ queryKey: ['traineeInfo', traineeId], - queryFn: () => getTraineeInfo(traineeId), + queryFn: () => getTrainee(traineeId), enabled: !!traineeId, //Added because it keeps rendering refetchOnMount: false, // Prevent refetching on component mount @@ -27,8 +28,8 @@ export const useTraineeInfoData = (traineeId: string) => { */ export const useSaveTraineeInfo = (traineeId: string) => { return useMutation({ - mutationFn: (dataToSave: SaveTraineeRequestData) => saveTraineeInfo(traineeId, dataToSave), + mutationFn: (dataToSave: UpdateTraineeRequestData) => updateTrainee(traineeId, dataToSave), }); }; -export type { SaveTraineeRequestData }; +export type { UpdateTraineeRequestData }; diff --git a/client/src/features/trainee-profile/profile/components/DropdownSelect.tsx b/client/src/features/trainee-profile/profile/components/DropdownSelect.tsx new file mode 100644 index 00000000..2a7d0ed3 --- /dev/null +++ b/client/src/features/trainee-profile/profile/components/DropdownSelect.tsx @@ -0,0 +1,60 @@ +import { FormControl, FormHelperText, InputLabel, MenuItem, Select, SelectChangeEvent } from '@mui/material'; + +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; + +type CustomSelectProps = { + inputLabel: string; + disabled?: boolean; + id: string; + label: string; + name: string; + value?: string; //currently selected value + options: MenuItemType[]; // array of values to show in the dropdown + isEditing?: boolean; + error?: string; + onChange: (event: SelectChangeEvent) => void; +}; + +export type MenuItemType = { + label: string; //To be displayed to the user + value: string; +}; +export const DropdownSelect = ({ + disabled = false, + inputLabel, + id, + label, + name, + value = '', + options, + isEditing = false, + error, + onChange = () => {}, +}: CustomSelectProps) => { + const NoIcon = () => null; + + return ( + + {inputLabel} + + {!!error && {error}} + + ); +}; diff --git a/client/src/features/trainee-profile/profile/components/GenderSelect.tsx b/client/src/features/trainee-profile/profile/components/GenderSelect.tsx new file mode 100644 index 00000000..2bfaa7fe --- /dev/null +++ b/client/src/features/trainee-profile/profile/components/GenderSelect.tsx @@ -0,0 +1,24 @@ +import { DropdownSelect } from './DropdownSelect'; +import { Gender } from '../../../../data/types/Trainee'; +import { SelectChangeEvent } from '@mui/material'; +import { formatTextToFriendly } from '../../utils/formHelper'; + +const genderOptions = Object.values(Gender).map((gender) => ({ + label: formatTextToFriendly(gender), + value: gender, +})); + +type GenderSelectProps = { + initialValue?: string; + disabled?: boolean; + isEditing: boolean; + value?: string; + error?: string; + onChange: (event: SelectChangeEvent) => void; +}; + +export const GenderSelect: React.FC = (props) => { + return ( + + ); +}; diff --git a/client/src/features/trainee-profile/profile/components/JobPathSelect.tsx b/client/src/features/trainee-profile/profile/components/JobPathSelect.tsx new file mode 100644 index 00000000..dfc3ed4d --- /dev/null +++ b/client/src/features/trainee-profile/profile/components/JobPathSelect.tsx @@ -0,0 +1,24 @@ +import { DropdownSelect } from './DropdownSelect'; +import { JobPath } from '../../../../data/types/Trainee'; +import { SelectChangeEvent } from '@mui/material'; +import { formatJobPathToLabel } from '../../utils/formHelper'; + +const options = Object.values(JobPath).map((status) => ({ + label: formatJobPathToLabel(status), + value: status, +})); + +type JobPathSelectProps = { + initialValue?: string; + disabled?: boolean; + isEditing: boolean; + value?: string; + error?: string; + onChange: (event: SelectChangeEvent) => void; +}; + +export const JobPathSelect: React.FC = (props) => { + return ( + + ); +}; diff --git a/client/src/features/trainee-profile/profile/components/LearningStatusSelect.tsx b/client/src/features/trainee-profile/profile/components/LearningStatusSelect.tsx new file mode 100644 index 00000000..8692090b --- /dev/null +++ b/client/src/features/trainee-profile/profile/components/LearningStatusSelect.tsx @@ -0,0 +1,31 @@ +import { DropdownSelect } from './DropdownSelect'; +import { LearningStatus } from '../../../../data/types/Trainee'; +import { SelectChangeEvent } from '@mui/material'; +import { formatTextToFriendly } from '../../utils/formHelper'; + +const options = Object.values(LearningStatus).map((status) => ({ + label: formatTextToFriendly(status), + value: status, +})); + +type LearningStatusSelectProps = { + initialValue?: string; + disabled?: boolean; + isEditing: boolean; + value?: string; + error?: string; + onChange: (event: SelectChangeEvent) => void; +}; + +export const LearningStatusSelect: React.FC = (props) => { + return ( + + ); +}; diff --git a/client/src/features/trainee-profile/profile/components/TraineeProfile.tsx b/client/src/features/trainee-profile/profile/components/TraineeProfile.tsx index d85bd930..167c885f 100644 --- a/client/src/features/trainee-profile/profile/components/TraineeProfile.tsx +++ b/client/src/features/trainee-profile/profile/components/TraineeProfile.tsx @@ -1,6 +1,6 @@ import { Box, Snackbar } from '@mui/material'; import { - SaveTraineeRequestData, + UpdateTraineeRequestData, useSaveTraineeInfo, useTraineeInfoData, } from '../../personal-info/data/useTraineeInfoData'; @@ -62,7 +62,7 @@ const TraineeProfile = ({ id }: TraineeProfileProps) => { * Shows a snackbar with the result of the save operation and refreshes the trainee data. * @param editedFields */ - const saveTraineeData = async (editedFields: SaveTraineeRequestData) => { + const saveTraineeData = async (editedFields: UpdateTraineeRequestData) => { mutate(editedFields, { onSuccess: (data: Trainee) => { setSnackbarSeverity('success'); @@ -91,7 +91,7 @@ const TraineeProfile = ({ id }: TraineeProfileProps) => { return; } - const changedFields: SaveTraineeRequestData = getTraineeInfoChanges(traineeData!); + const changedFields: UpdateTraineeRequestData = getTraineeInfoChanges(traineeData!); saveTraineeData(changedFields); }; diff --git a/client/src/features/trainee-profile/utils/formHelper.ts b/client/src/features/trainee-profile/utils/formHelper.ts index 0857e0ec..05ad14db 100644 --- a/client/src/features/trainee-profile/utils/formHelper.ts +++ b/client/src/features/trainee-profile/utils/formHelper.ts @@ -1,5 +1,5 @@ import { ChangeEvent, ReactNode } from 'react'; -import { Trainee, TraineeInfoType } from '../../../data/types/Trainee'; +import { JobPath, Trainee, TraineeInfoType } from '../../../data/types/Trainee'; import { SelectChangeEvent } from '@mui/material'; @@ -55,7 +55,13 @@ export const createTextChangeHandler = ( * @param value * @returns */ +// TODO: rename this function export const formatTextToFriendly = (value: string): string => { // replace '-' with ' ' in the type string and capitilzie first letter return value.replace(/-/g, ' ').replace(/^\w/, (char) => char.toUpperCase()); }; + +export const formatJobPathToLabel = (jobPath: JobPath): string => { + if (jobPath === JobPath.NonTechJob) return 'Non-tech job'; + return formatTextToFriendly(jobPath); +};