diff --git a/imports/ui/birthdate/useBirthdatePickerProps.ts b/imports/ui/birthdate/useBirthdatePickerProps.ts new file mode 100644 index 000000000..e275534e9 --- /dev/null +++ b/imports/ui/birthdate/useBirthdatePickerProps.ts @@ -0,0 +1,21 @@ +import {useMemo} from 'react'; + +import format from 'date-fns/format'; +import endOfToday from 'date-fns/endOfToday'; +import parseISO from 'date-fns/parseISO'; +import subYears from 'date-fns/subYears'; + +import {useDateMask} from '../../i18n/datetime'; + +const useBirthdatePickerProps = () => { + const mask = useDateMask(); + const maxDateString = format(endOfToday(), 'yyyy-MM-dd'); + + return useMemo(() => { + const maxDate = parseISO(maxDateString); + const minDate = subYears(maxDate, 200); + return {minDate, maxDate, mask}; + }, [maxDateString, mask]); +}; + +export default useBirthdatePickerProps; diff --git a/imports/ui/patients/NewPatientForm.tsx b/imports/ui/patients/NewPatientForm.tsx index 76b08b6c9..17ef6054a 100644 --- a/imports/ui/patients/NewPatientForm.tsx +++ b/imports/ui/patients/NewPatientForm.tsx @@ -1,9 +1,8 @@ -import React, {useState} from 'react'; +import React, {useCallback, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; import {styled} from '@mui/material/styles'; -import {useNavigate} from 'react-router-dom'; - import Grid from '@mui/material/Grid'; import Card from '@mui/material/Card'; @@ -17,14 +16,24 @@ import Checkbox from '@mui/material/Checkbox'; import Input from '@mui/material/Input'; import InputLabel from '@mui/material/InputLabel'; import TextField from '@mui/material/TextField'; +import DatePicker from '@mui/lab/DatePicker'; import Select from '@mui/material/Select'; import MenuItem from '@mui/material/MenuItem'; import Button from '@mui/material/Button'; import PersonAddIcon from '@mui/icons-material/PersonAdd'; -import call from '../../api/endpoint/call'; +import {useSnackbar} from 'notistack'; + +import isValid from 'date-fns/isValid'; +import dateFormat from 'date-fns/format'; + +import {BIRTHDATE_FORMAT} from '../../api/collection/patients'; + import patientsInsert from '../../api/endpoint/patients/insert'; +import call from '../../api/endpoint/call'; + import useUniqueId from '../hooks/useUniqueId'; +import useBirthdatePickerProps from '../birthdate/useBirthdatePickerProps'; const PREFIX = 'NewPatientForm'; @@ -48,38 +57,93 @@ const StyledGrid = styled(Grid)(({theme}) => ({ }, })); +interface OnSubmitProps { + niss: string; + firstname: string; + lastname: string; + birthdate: Date | null; + sex: string; + noshow: number; +} + +const useSubmit = ({ + niss, + firstname, + lastname, + birthdate, + sex, + noshow, +}: OnSubmitProps) => { + const {enqueueSnackbar, closeSnackbar} = useSnackbar(); + const navigate = useNavigate(); + return useCallback( + async (event) => { + event.preventDefault(); + + const serializeDate = (datetime: Date) => + dateFormat(datetime, BIRTHDATE_FORMAT); + + const patient = { + niss, + firstname, + lastname, + birthdate: + birthdate !== null && isValid(birthdate) + ? serializeDate(birthdate) + : undefined, + sex, + noshow, + }; + + const key = enqueueSnackbar( + `Creating patient ${lastname} ${firstname}...`, + { + variant: 'info', + persist: true, + }, + ); + + return call(patientsInsert, patient).then( + (_id) => { + closeSnackbar(key); + enqueueSnackbar('Patient created!', {variant: 'success'}); + navigate(`/patient/${_id}`); + }, + (error: unknown) => { + closeSnackbar(key); + const message = + error instanceof Error ? error.message : 'unknown error'; + enqueueSnackbar(message, {variant: 'error'}); + }, + ); + }, + [niss, firstname, lastname, birthdate, sex, noshow], + ); +}; + const NewPatientForm = () => { const [niss, setNiss] = useState(''); const [firstname, setFirstname] = useState(''); const [lastname, setLastname] = useState(''); - const [birthdate, setBirthdate] = useState(''); + const [error, setError] = useState(null); + const [birthdate, setBirthdate] = useState(null); const [sex, setSex] = useState(''); const [noshow, setNoshow] = useState(0); - const lastnameId = useUniqueId('new-patient-form-input-lastname'); - const firstnameId = useUniqueId('new-patient-form-input-firstname'); + const formId = useUniqueId('new-patient-form'); + const lastnameId = `${formId}-input-lastname`; + const firstnameId = `${formId}-input-firstname`; - const navigate = useNavigate(); + const birthdatePickerProps = useBirthdatePickerProps(); - const handleSubmit = async (event) => { - event.preventDefault(); - - const patient = { - niss, - firstname, - lastname, - birthdate, - sex, - noshow, - }; - - try { - const _id = await call(patientsInsert, patient); - navigate(`/patient/${_id}`); - } catch (error: unknown) { - console.error(error); - } - }; + const submit = useSubmit({ + niss, + firstname, + lastname, + birthdate, + sex, + noshow, + }); return ( { - { - setBirthdate(e.target.value); + renderInput={(props) => ( + + )} + onChange={(pickedDatetime) => { + setBirthdate(pickedDatetime); + }} + onError={(reason) => { + setError( + reason === null + ? null + : reason === 'maxDate' + ? 'Birthdate is in the future' + : reason === 'minDate' + ? 'Birthdate is too far in the past' + : reason === 'invalidDate' + ? 'Birthdate is invalid' + : reason, + ); }} /> @@ -179,10 +260,11 @@ const NewPatientForm = () => { diff --git a/imports/ui/patients/PatientPersonalInformationStatic.tsx b/imports/ui/patients/PatientPersonalInformationStatic.tsx index b15b4e4ee..ab305a1d4 100644 --- a/imports/ui/patients/PatientPersonalInformationStatic.tsx +++ b/imports/ui/patients/PatientPersonalInformationStatic.tsx @@ -47,11 +47,9 @@ import { makeRegExpIndex, } from '../../api/string'; -import { - useDateFormat, - useDateFormatAge, - useDateMask, -} from '../../i18n/datetime'; +import {useDateFormat, useDateFormatAge} from '../../i18n/datetime'; + +import useBirthdatePickerProps from '../birthdate/useBirthdatePickerProps'; import usePrompt from '../navigation/usePrompt'; import NoContent from '../navigation/NoContent'; @@ -247,7 +245,7 @@ const PatientPersonalInformationStatic = ( const localizeBirthdate = useDateFormat('PPP'); const localizeAge = useDateFormatAge(); - const localizedDateMask = useDateMask(); + const birthdatePickerProps = useBirthdatePickerProps(); const componentId = useUniqueId('patient-personal-information'); @@ -394,11 +392,11 @@ const PatientPersonalInformationStatic = ( - + {...birthdatePickerProps} label="Birth date" + value={_birthdate} + disabled={!editing} renderInput={(props) => (