diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index f002b0643b3d6a387ecce79fc8d2fb399e364b54..c5e5e7350a3ef79c9fed8a541408f78e033c5799 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -20,7 +20,8 @@ "roleType": "Role", "roleStartDate": "From", "roleEndDate": "To", - "comment": "Comment" + "comment": "Comment", + "email": "E-mail" }, "loading": "Loading...", "termsHeader": "Terms", @@ -48,7 +49,8 @@ "invalidIdNumber": "Invalid national ID number", "nationalIdNumberRequired": "National ID number required", "roleTypeRequired": "Role type is required", - "roleEndRequired": "Role end date is required" + "roleEndRequired": "Role end date is required", + "emailRequired": "E-mail is required" }, "button": { "back": "Back", diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 314af2fff20c2c6cc5a5e1128e88260eb7dde61a..67239ab90429c20ff6083251b7058dfd8eba4776 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -20,7 +20,8 @@ "roleType": "Gjesterolle", "roleStartDate": "Fra", "roleEndDate": "Til", - "comment": "Kommentar" + "comment": "Kommentar", + "email": "E-post" }, "loading": "Laster...", "termsHeader": "Vilkår", @@ -48,7 +49,8 @@ "invalidIdNumber": "Ugyldig fødselsnummer", "nationalIdNumberRequired": "Fødselsnummer er påkrevd", "roleTypeRequired": "Rolletype er påkrevd", - "roleEndRequired": "Sluttdato for rolle er påkrevd" + "roleEndRequired": "Sluttdato for rolle er påkrevd", + "emailRequired": "E-post er obligatorisk" }, "button": { "back": "Tilbake", diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index f3263eefaff0c7d4a55ecbd4e2a7855772a9bbce..0c80b604abffb7bb7a7861173ebeb058600f16b4 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -21,7 +21,8 @@ "roleType": "Gjesterolle", "roleStartDate": "Frå", "roleEndDate": "Til", - "comment": "Kommentar" + "comment": "Kommentar", + "email": "E-post" }, "loading": "Lastar...", "termsHeader": "Vilkår", @@ -49,7 +50,8 @@ "invalidIdNumber": "Ugyldig fødselsnummer", "nationalIdNumberRequired": "Fødselsnummer er påkrevd", "roleTypeRequired": "Rolletype er påkrevd", - "roleEndRequired": "Sluttdato for rolle er påkrevd" + "roleEndRequired": "Sluttdato for rolle er påkrevd", + "emailRequired": "E-post er obligatorisk" }, "button": { "back": "Tilbake", diff --git a/frontend/src/context/index.ts b/frontend/src/context/index.ts deleted file mode 100644 index 6bbfa31f4ff7ee7c200d3e2e9d7db7553ad6c17c..0000000000000000000000000000000000000000 --- a/frontend/src/context/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react' - - -// eslint-disable-next-line import/prefer-default-export -export const RolesContext = createContext<any[]>([]) diff --git a/frontend/src/hooks/useRoleTypes/index.tsx b/frontend/src/hooks/useRoleTypes/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d02e0a8e91637d7904a5502803272da67e1b6404 --- /dev/null +++ b/frontend/src/hooks/useRoleTypes/index.tsx @@ -0,0 +1,32 @@ +import { useState, useEffect } from 'react' + +type RoleTypeData = { + identifier: string + name_en: string + name_nb: string +} + +function useRoleTypes(): RoleTypeData[] { + const [roleTypes, setRoleTypes] = useState<RoleTypeData[]>([]) + + async function fetchRoleTypes() { + fetch(`http://localhost:3000/api/ui/v1/roletypes`) + .then(data => data.text()) + .then((result => { + // The response is a JSON-array + setRoleTypes(JSON.parse(result)) + })) + .catch(error => { + console.error(error) + }) + } + + useEffect(() => { + fetchRoleTypes() + }, []) + + return roleTypes +} + +export type { RoleTypeData } +export default useRoleTypes diff --git a/frontend/src/routes/frontpage/index.tsx b/frontend/src/routes/frontpage/index.tsx index 56d8fed9f5686506a836903ce41d4dabd11f0181..4435a89a109d314fe9ef531107db7d0a248eb4a4 100644 --- a/frontend/src/routes/frontpage/index.tsx +++ b/frontend/src/routes/frontpage/index.tsx @@ -25,9 +25,6 @@ export default function FrontPage() { <li> <Link to="/register/">Registration</Link> </li> - <li> - <Link to="/registerwizard/">Registration wizard</Link> - </li> </ul> </p> <Debug /> diff --git a/frontend/src/routes/register/formData.ts b/frontend/src/routes/register/formData.ts index a496d7bf03744771145a7e404ef43a3d575a4095..b36b16dd44613af4debcc3c383cf9d80b2bb0d1d 100644 --- a/frontend/src/routes/register/formData.ts +++ b/frontend/src/routes/register/formData.ts @@ -1,11 +1,10 @@ export type RegisterFormData = { first_name?: string last_name?: string - date_of_birth?: Date - national_id_number?: string role_type?: string role_start?: Date role_end?: Date comment?: string - ou?: { value: string; label: string } + ou_id?: number + email?: string } \ No newline at end of file diff --git a/frontend/src/routes/register/index.tsx b/frontend/src/routes/register/index.tsx index 58764bcd6fc4840e546484f74c7e1e9435fe7c6e..470d2e8f4ee23ca84fb401e3ff2dde68e44a6209 100644 --- a/frontend/src/routes/register/index.tsx +++ b/frontend/src/routes/register/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react' +import React, { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { Box, Button, Step, StepLabel, Stepper } from '@mui/material' @@ -12,53 +12,26 @@ import StepPersonForm from './stepPersonForm' import { PersonFormMethods } from './personFormMethods' import { SummaryFormMethods } from './summaryFormMethods' -import { RolesContext } from '../../context' const steps = ['Register', 'Summary'] export default function StepRegistration() { const { t } = useTranslation(['common']) - const [roletypes, setRoletypes] = useState<any[]>([]) const [formData, setFormData] = useState<RegisterFormData>({ first_name: undefined, last_name: undefined, - date_of_birth: undefined, - national_id_number: undefined, role_type: undefined, role_start: undefined, role_end: undefined, comment: undefined, - ou: undefined, + ou_id: undefined, + email: undefined, }) const history = useHistory() const REGISTER_STEP = 0 const SUMMARY_STEP = 1 - - async function fetchRoletypes(onSuccess: (result: Array<any>) => void, onError: (error: any) => void) { - fetch(`http://localhost:3000/api/ui/v1/roletypes/`) - .then(data => data.text()) - .then((result => { - // The response is a JSON-array - onSuccess(JSON.parse(result)) - })) - .catch(error => { - onError(error) - }) - } - - useEffect(() => { - fetchRoletypes( - (result) => { - setRoletypes(result) - }, - (error) => { - console.log(error) - }) - // Having an empty dependency array causes the role types only to be loaded once - }, []) - const [activeStep, setActiveStep] = useState(0) const personFormRef = useRef<PersonFormMethods>(null) const summaryStepRef = useRef<SummaryFormMethods>(null) @@ -93,65 +66,63 @@ export default function StepRegistration() { } return ( - <RolesContext.Provider value={roletypes}> - <Page header='Register as a guest'> - - {/* Stepper at top of wizard */} - <Stepper activeStep={activeStep}> - {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */} - {steps.map((label, index) => { - const stepProps = {} - const labelProps = {} - return ( - <Step key={label} {...stepProps}> - <StepLabel {...labelProps}>{label}</StepLabel> - </Step> - ) - })} - </Stepper> - - {/* Current page in wizard */} - <Box sx={{ width: '100%' }}> - {activeStep === REGISTER_STEP && ( - <StepPersonForm nextHandler={handleForwardFromRegister} formData={formData} - ref={personFormRef} /> + <Page header='Register as a guest'> + + {/* Stepper at top of wizard */} + <Stepper activeStep={activeStep}> + {/* eslint-disable-next-line @typescript-eslint/no-unused-vars */} + {steps.map((label, index) => { + const stepProps = {} + const labelProps = {} + return ( + <Step key={label} {...stepProps}> + <StepLabel {...labelProps}>{label}</StepLabel> + </Step> ) - } - {activeStep === SUMMARY_STEP && ( - <StepSummary formData={formData} ref={summaryStepRef} /> - )} - </Box> - - <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, color: 'primary.main' }}> - {activeStep === REGISTER_STEP && ( - <Button data-testid='button-next' onClick={handleNext}> - {t('button.next')} - </Button> - )} - - {activeStep === SUMMARY_STEP && ( - <> - <Button - onClick={handleBack} - sx={{ mr: 1 }} - > - {t('button.back')} - </Button> - - <Button onClick={registerGuest} - sx={{ mr: 1 }} - > - {t('button.save')} - </Button> - </> - )} - - <Button onClick={handleCancel}> - {t('button.cancel')} + })} + </Stepper> + + {/* Current page in wizard */} + <Box sx={{ width: '100%' }}> + {activeStep === REGISTER_STEP && ( + <StepPersonForm nextHandler={handleForwardFromRegister} formData={formData} + ref={personFormRef} /> + ) + } + {activeStep === SUMMARY_STEP && ( + <StepSummary formData={formData} ref={summaryStepRef} /> + )} + </Box> + + <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2, color: 'primary.main' }}> + {activeStep === REGISTER_STEP && ( + <Button data-testid='button-next' onClick={handleNext}> + {t('button.next')} </Button> - </Box> + )} + + {activeStep === SUMMARY_STEP && ( + <> + <Button + onClick={handleBack} + sx={{ mr: 1 }} + > + {t('button.back')} + </Button> + + <Button onClick={registerGuest} + sx={{ mr: 1 }} + > + {t('button.save')} + </Button> + </> + )} + + <Button onClick={handleCancel}> + {t('button.cancel')} + </Button> + </Box> - </Page> - </RolesContext.Provider> + </Page> ) } diff --git a/frontend/src/routes/register/stepPersonForm.tsx b/frontend/src/routes/register/stepPersonForm.tsx index dec044bf5327a104bbb0c942c28b3a050a9de21d..306a0ac6fec6f9fb07cb53f9f39b1bda3b055ee6 100644 --- a/frontend/src/routes/register/stepPersonForm.tsx +++ b/frontend/src/routes/register/stepPersonForm.tsx @@ -1,13 +1,12 @@ import { Box, FormControl, InputLabel, MenuItem, Select, SelectChangeEvent, Stack, TextField } from '@mui/material' import { Controller, SubmitHandler, useForm } from 'react-hook-form' import { DatePicker } from '@mui/lab' -import React, { forwardRef, Ref, useEffect, useImperativeHandle, useContext, useState } from 'react' +import React, { forwardRef, Ref, useEffect, useImperativeHandle, useState } from 'react' import { useTranslation } from 'react-i18next' -import { isValidFnr } from '../../utils' import { RegisterFormData } from './formData' import { PersonFormMethods } from './personFormMethods' -import { RolesContext } from '../../context' import useOus, { OuData } from '../../hooks/useOus' +import useRoleTypes, { RoleTypeData } from '../../hooks/useRoleTypes' interface StepPersonFormProperties { @@ -19,9 +18,18 @@ interface StepPersonFormProperties { const StepPersonForm = forwardRef((props: StepPersonFormProperties, ref: Ref<PersonFormMethods>) => { const { i18n, t } = useTranslation(['common']) const { nextHandler, formData } = props - const roletypes = useContext(RolesContext) const ous = useOus() - const [ouChoice, setOuChoice] = useState('') + const [ouChoice, setOuChoice] = useState(formData.ou_id ? formData.ou_id : '') + const [selectedRoleType, setSelectedRoleType] = useState(formData.role_type ? formData.role_type : '') + const roleTypes = useRoleTypes() + + + const roleTypeSort = () => (a: RoleTypeData, b: RoleTypeData) => { + if (i18n.language === 'en') { + return a.name_nb.localeCompare(b.name_nb) + } + return a.name_en.localeCompare(b.name_en) + } const enSort = (a: OuData, b: OuData) => { if (a.en > b.en) { @@ -53,6 +61,7 @@ const StepPersonForm = forwardRef((props: StepPersonFormProperties, ref: Ref<Per handleSubmit, formState: { errors }, reset, + setValue, } = useForm<RegisterFormData>() const onSubmit = handleSubmit(submit) @@ -61,15 +70,28 @@ const StepPersonForm = forwardRef((props: StepPersonFormProperties, ref: Ref<Per reset(formData) }, []) - const handleChange = (event: SelectChangeEvent) => { - setOuChoice(event.target.value) + const handleOuChange = (event: SelectChangeEvent) => { + if (event.target.value) { + setOuChoice(event.target.value) + setValue('ou_id', parseInt(event.target.value, 10)) + } else { + setValue('ou_id', undefined) + } + } + + register('role_type', { + required: t('validation.roleTypeRequired').toString(), + }) + + const handleRoleTypeChange = (event: any) => { + setValue('role_type', event.target.value) + setSelectedRoleType(event.target.value) } function doSubmit() { return onSubmit() } - useImperativeHandle(ref, () => ({ doSubmit })) return ( @@ -94,49 +116,23 @@ const StepPersonForm = forwardRef((props: StepPersonFormProperties, ref: Ref<Per required: t('validation.lastNameRequired').toString(), })} /> - - <Controller name='date_of_birth' - control={control} - rules={{ - required: t( - 'validation.dateOfBirthRequired').toString(), - }} - render={({ field }) => ( - <DatePicker - mask='____-__-__' - label={t('input.dateOfBirth')} - value={field.value} - inputFormat='yyyy-MM-dd' - onChange={(value) => { - field.onChange(value) - }} - renderInput={(params) => <TextField {...params} />} - />)} - /> - <TextField - id='nationalIdNumber' - label={t('input.nationalIdNumber')} - error={!!errors.national_id_number} - helperText={ - errors.national_id_number && errors.national_id_number.message - } - {...register('national_id_number', { - required: t( - 'validation.nationalIdNumberRequired', - ).toString(), - validate: isValidFnr, + id='email' + label={t('input.email')} + error={!!errors.email} + helperText={errors.email && errors.email.message} + {...register(`email`, { + required: t('validation.emailRequired').toString(), })} /> - <FormControl fullWidth> <InputLabel id='ou-select-label'>{t('common:ou')}</InputLabel> <Select labelId='ou-select-label' id='ou-select-label' - value={ouChoice} + value={ouChoice?.toString()} label={t('common:ou')} - onChange={handleChange} + onChange={handleOuChange} > {ous.length > 0 ? ( ous @@ -155,18 +151,17 @@ const StepPersonForm = forwardRef((props: StepPersonFormProperties, ref: Ref<Per <TextField id='roletype-select' select - defaultValue='' + value={selectedRoleType} error={!!errors.role_type} label={t('input.roleType')} - {...register(`role_type`, { - required: t('validation.roleTypeRequired').toString(), - })} + onChange={handleRoleTypeChange} > { - // TODO Use selected language in role type list - roletypes.map((roletype) => ( - <MenuItem value={roletype.identifier}>{roletype.name_nb}</MenuItem> - )) + roleTypes.sort(roleTypeSort()) + .map((roleType) => ( + <MenuItem + value={roleType.identifier}>{i18n.language === 'en' ? roleType.name_en : roleType.name_nb}</MenuItem> + )) } </TextField> diff --git a/frontend/src/routes/register/stepSummary.tsx b/frontend/src/routes/register/stepSummary.tsx index 84bb2574bee7b3c439ae29eb3d313e6cfc8d0ae7..2d24c768b6289726ae634d4d794c58537c387e50 100644 --- a/frontend/src/routes/register/stepSummary.tsx +++ b/frontend/src/routes/register/stepSummary.tsx @@ -9,6 +9,8 @@ import { DatePicker } from '@mui/lab' import { RegisterFormData } from './formData' import { postJsonOpts } from '../../utils' import { PersonFormMethods } from './personFormMethods' +import useOus from '../../hooks/useOus' +import useRoleTypes from '../../hooks/useRoleTypes' interface StepSummaryProperties { @@ -16,20 +18,21 @@ interface StepSummaryProperties { } const StepSummary = forwardRef((props: StepSummaryProperties, ref: Ref<PersonFormMethods>) => { - const { t } = useTranslation(['common']) + const { i18n, t } = useTranslation(['common']) const { formData } = props + const ous = useOus() + const roleTypes = useRoleTypes() const submit: SubmitHandler<RegisterFormData> = (data) => { const payload = { first_name: data.first_name, last_name: data.last_name, - date_of_birth: data.date_of_birth === null ? null : format(data.date_of_birth as Date, 'yyyy-MM-dd'), - national_id_number: data.national_id_number, role_type: data.role_type, role_start: data.role_start === null ? null : format(data.role_start as Date, 'yyyy-MM-dd'), role_end: data.role_end === null ? null : format(data.role_end as Date, 'yyyy-MM-dd'), comment: data.comment, - ou: data.ou, + ou: data.ou_id, + email: data.email, } console.log('submitting', JSON.stringify(payload)) fetch('http://localhost:3000/api/ui/v1/register/', postJsonOpts(payload)) @@ -42,6 +45,38 @@ const StepSummary = forwardRef((props: StepSummaryProperties, ref: Ref<PersonFor }) } + const getSelectedOrganisationalUnit = () => { + if (formData?.ou_id) { + const ousArray = ous.filter(((value) => value.id === formData.ou_id)) + + if (ousArray.length === 0) { + return '' + } + + if (i18n.language === 'en') { + return ousArray[0].en + } + return ousArray[0].nb + } + return '' + } + + const getSelectedRoleType = () => { + if (formData?.role_type) { + const roleTypeArray = roleTypes.filter(((value) => value.identifier === formData.role_type)) + + if (roleTypeArray.length === 0) { + return '' + } + + if (i18n.language === 'en') { + return roleTypeArray[0].name_en + } + return roleTypeArray[0].name_nb + } + return '' + } + const { reset, register, @@ -81,35 +116,25 @@ const StepSummary = forwardRef((props: StepSummaryProperties, ref: Ref<PersonFor {...register('last_name')} /> - <Controller name='date_of_birth' - control={control} - render={({ field }) => ( - <DatePicker - mask='____-__-__' - label={t('input.dateOfBirth')} - disabled - value={field.value} - inputFormat='yyyy-MM-dd' - onChange={(value) => { - field.onChange(value) - }} - renderInput={(params) => <TextField {...params} />} - />)} + <TextField + id='email' + label={t('input.email')} + disabled + {...register('email')} /> <TextField - id='nationalIdNumber' - label={t('input.nationalIdNumber')} + id='ou' + value={getSelectedOrganisationalUnit()} + label={t('ou')} disabled - {...register('national_id_number')} /> <TextField id='roletype-select' - defaultValue='' + value={getSelectedRoleType()} label={t('input.roleType')} disabled - {...register(`role_type`)} /> <Controller name='role_start'