From 16ee629748939a0fd3edf43b3db3fa08658fa717 Mon Sep 17 00:00:00 2001 From: mourads <111437871+mouradsellam2@users.noreply.github.com> Date: Fri, 24 Mar 2023 09:32:48 +0100 Subject: [PATCH] fix: age range limit min max, fix displaying date filter Ref gestion-de-projet#1786 * fix: age range limit min max, fix displaying date filter - Ref gestion-de-projet#1786 * fix: typo in date error message * fix: [age range] delete error message when trying to put a value lower than 0 - Ref gestion-de-projet#1786 * core: testing patients birthdates filters Ref gestion-de-projet#1786 * fix: fix format into patientSideBar Ref gestion-de-projet#1786 * fix: age range format * chore: cleaning console.log Ref gestion-de-projet#1786 --------- Co-authored-by: msellam Co-authored-by: Manelle G <39384110+ManelleG@users.noreply.github.com> Co-authored-by: manelleg Co-authored-by: Salah-BOUYAHIA Co-authored-by: Mehdi-BOUYAHIA <63298037+Mehdi-BOUYAHIA@users.noreply.github.com> --- .../Dashboard/PatientList/PatientList.tsx | 11 +- .../Filters/PatientFilters/PatientFilters.tsx | 23 ++- .../Inputs/InputAgeRange/InputAgeRange.tsx | 160 +++++++++--------- .../Patient/PatientSidebar/PatientSidebar.tsx | 13 +- src/types.ts | 8 +- src/utils/age.ts | 77 +++++---- src/utils/chips.ts | 2 +- 7 files changed, 170 insertions(+), 124 deletions(-) diff --git a/src/components/Dashboard/PatientList/PatientList.tsx b/src/components/Dashboard/PatientList/PatientList.tsx index 51937ae04..10248e26a 100644 --- a/src/components/Dashboard/PatientList/PatientList.tsx +++ b/src/components/Dashboard/PatientList/PatientList.tsx @@ -29,6 +29,7 @@ import { import { getGenderRepartitionSimpleData } from 'utils/graphUtils' import { buildPatientFiltersChips } from 'utils/chips' +import { substructAgeString } from 'utils/age' import { useDebounce } from 'utils/debounce' type PatientListProps = { @@ -64,7 +65,7 @@ const PatientList: React.FC = ({ const [filters, setFilters] = useState({ gender: PatientGenderKind._unknown, - birthdates: [moment().subtract(130, 'years').format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')], + birthdatesRanges: ['', ''], vitalStatus: VitalStatus.all }) @@ -110,13 +111,17 @@ const PatientList: React.FC = ({ // Set search state if (inputSearch !== searchInput) setSearchInput(inputSearch) if (_searchBy !== searchBy) setSearchBy(_searchBy) + const birthdates: [string, string] = [ + moment(substructAgeString(filters.birthdatesRanges[0])).format('MM/DD/YYYY'), + moment(substructAgeString(filters.birthdatesRanges[1])).format('MM/DD/YYYY') + ] const result = await services.cohorts.fetchPatientList( pageValue, _searchBy, inputSearch, filters.gender, - filters.birthdates, + birthdates, filters.vitalStatus, order.orderBy, order.orderDirection, @@ -167,7 +172,7 @@ const PatientList: React.FC = ({ case 'birthdates': setFilters((prevFilters) => ({ ...prevFilters, - birthdates: [moment().subtract(130, 'years').format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')] + birthdatesRanges: [moment().subtract(130, 'years').format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')] })) break case 'vitalStatus': diff --git a/src/components/Filters/PatientFilters/PatientFilters.tsx b/src/components/Filters/PatientFilters/PatientFilters.tsx index 273e39d38..43713e88d 100644 --- a/src/components/Filters/PatientFilters/PatientFilters.tsx +++ b/src/components/Filters/PatientFilters/PatientFilters.tsx @@ -31,15 +31,17 @@ const PatientFilters: React.FC = ({ open, onClose, onSubmit const classes = useStyles() const [_gender, setGender] = useState(filters.gender) - const [_birthdates, setBirthdates] = useState<[string, string]>(filters.birthdates) + const [birthdatesRanges, setBirthdatesRanges] = useState<[string, string]>(filters.birthdatesRanges) const [_vitalStatus, setVitalStatus] = useState(filters.vitalStatus) const [error, setError] = useState(false) + const [errorMessage, setErrorMessage] = useState('') useEffect(() => { setGender(filters.gender) - setBirthdates(filters.birthdates) + setBirthdatesRanges(filters.birthdatesRanges) setVitalStatus(filters.vitalStatus) + _onError(false) }, [open]) // eslint-disable-line const _onChangeGender = (event: React.ChangeEvent, value: string) => { @@ -53,13 +55,18 @@ const PatientFilters: React.FC = ({ open, onClose, onSubmit const _onSubmit = () => { onChangeFilters({ gender: _gender, - birthdates: _birthdates, + birthdatesRanges: birthdatesRanges, vitalStatus: _vitalStatus }) onSubmit() } + const _onError = (isError: boolean, errorMessage = '') => { + setError(isError) + setErrorMessage(errorMessage) + } + return ( Filtrer les patients : @@ -75,10 +82,12 @@ const PatientFilters: React.FC = ({ open, onClose, onSubmit setBirthdates(newBirthdates)} + error={{ isError: error, errorMessage: errorMessage }} + onError={_onError} + birthdatesRanges={birthdatesRanges} + onChangeBirthdatesRanges={(newBirthdatesRanges: [string, string]) => + setBirthdatesRanges(newBirthdatesRanges) + } /> diff --git a/src/components/Inputs/InputAgeRange/InputAgeRange.tsx b/src/components/Inputs/InputAgeRange/InputAgeRange.tsx index 5d033309f..62cff615d 100644 --- a/src/components/Inputs/InputAgeRange/InputAgeRange.tsx +++ b/src/components/Inputs/InputAgeRange/InputAgeRange.tsx @@ -1,99 +1,101 @@ import React, { useEffect, useState } from 'react' -import moment from 'moment' import { useAppSelector } from 'state' import { Grid, TextField, Typography } from '@material-ui/core' import useStyles from './styles' +import { AgeRangeType, ErrorType } from 'types' +import { convertAgeRangeTypeToString, convertStringToAgeRangeType, substructAgeRangeType } from 'utils/age' type InputAgeRangeAdvancedProps = { - birthdates: [string, string] - onChangeBirthdates: (newAge: [string, string]) => void - error: boolean - setError: (error: boolean) => void + birthdatesRanges: [string, string] + onChangeBirthdatesRanges: (newAge: [string, string]) => void + error: ErrorType + onError: (isError: boolean, errorMessage?: string) => void } -type InputsStateType = { - year?: number - month?: number - days?: number +const defaultMinDate: AgeRangeType = { + year: 0, + month: 0, + days: 0 } -const InputAgeRange: React.FC = ({ birthdates, onChangeBirthdates, error, setError }) => { +const defaultMaxDate: AgeRangeType = { + year: 130, + month: 0, + days: 0 +} +const InputAgeRange: React.FC = ({ + birthdatesRanges, + onChangeBirthdatesRanges, + error, + onError +}) => { const classes = useStyles() const { deidentifiedBoolean = true } = useAppSelector((state) => state.exploredCohort) - const [minState, setMinState] = useState({ - year: 0, - month: 0, - days: 0 - }) - const [maxState, setMaxState] = useState({ - year: 0, - month: 0, - days: 0 - }) + const [minState, setMinState] = useState(defaultMinDate) + const [maxState, setMaxState] = useState(defaultMaxDate) useEffect(() => { - const newMaxDate: InputsStateType = { - year: 0, - month: 0, - days: 0 - } - const newMinDate: InputsStateType = { - year: 0, - month: 0, - days: 0 - } - - newMaxDate.year = moment().diff(moment(birthdates[0], 'YYYY-MM-DD'), 'year') || 0 - newMaxDate.month = moment().subtract(newMaxDate.year, 'year').diff(moment(birthdates[0], 'YYYY-MM-DD'), 'month') - newMaxDate.days = moment() - .subtract(newMaxDate.year, 'year') - .subtract(newMaxDate.month, 'month') - .diff(moment(birthdates[0], 'YYYY-MM-DD'), 'days') - - newMinDate.year = moment().diff(moment(birthdates[1], 'YYYY-MM-DD'), 'year') || 0 - newMinDate.month = moment().subtract(newMinDate.year, 'year').diff(moment(birthdates[1], 'YYYY-MM-DD'), 'month') - newMinDate.days = moment() - .subtract(newMinDate.year, 'year') - .subtract(newMinDate.month, 'month') - .diff(moment(birthdates[1], 'YYYY-MM-DD'), 'days') - + const newMaxDate: AgeRangeType = convertStringToAgeRangeType(birthdatesRanges[0]) ?? defaultMaxDate + const newMinDate: AgeRangeType = convertStringToAgeRangeType(birthdatesRanges[1]) ?? defaultMinDate setMinState(newMinDate) setMaxState(newMaxDate) - }, [birthdates]) - - useEffect(() => { - if (maxState.days === 0 && maxState.month === 0 && maxState.year === 0) { - setError(true) - } else { - setError(false) + }, [birthdatesRanges]) + + const checkRange = (key: string, value: number) => { + if (key === 'days' && value <= 31 && value >= 0) { + return true + } else if (key === 'month' && value <= 12 && value >= 0) { + return true + } else if (key === 'year' && value >= 0) { + return true } - }, [maxState]) - - const _onChangeState = (stateName: 'minState' | 'maxState', key: 'year' | 'month' | 'days', value?: number) => { - const _minState = minState - const _maxState = maxState + return false + } - if (stateName === 'minState') { - _minState[key] = value - setMinState(_minState) + const _onChangeState = (stateName: 'minState' | 'maxState', key: 'year' | 'month' | 'days', value = 0) => { + const newMinState: AgeRangeType = { ...minState } + const newMaxState: AgeRangeType = { ...maxState } + let isError + if (!checkRange(key, value)) { + isError = true } else { - _maxState[key] = value - setMaxState(_maxState) + if (stateName === 'minState') { + newMinState[key] = value + } else { + newMaxState[key] = value + } + + const maxDate: Date = substructAgeRangeType(newMinState) + const minDate: Date = substructAgeRangeType(newMaxState) + + if (minDate > maxDate) { + onError(true, 'La date maximale doit être supérieure à la date minimale.') + isError = true + } else if (newMaxState.days === 0 && newMaxState.month === 0 && newMaxState.year === 0) { + onError(true, 'Au moins une des valeurs maximales ne doit pas être égale à 0') + isError = true + } } - const newMinDate = moment() - .subtract(_minState.days, 'days') - .subtract(_minState.month, 'month') - .subtract(_minState.year, 'year') - .format('YYYY-MM-DD') - const newMaxDate = moment() - .subtract(_maxState.days, 'days') - .subtract(_maxState.month, 'month') - .subtract(_maxState.year, 'year') - .format('YYYY-MM-DD') - - if (birthdates[1] !== newMinDate || birthdates[0] !== newMaxDate) { - onChangeBirthdates([newMaxDate, newMinDate] as [string, string]) + const oldBirthdatesRanges: [string, string] = [ + convertAgeRangeTypeToString(maxState), + convertAgeRangeTypeToString(minState) + ] + const newBirthdatesRanges: [string, string] = [ + convertAgeRangeTypeToString(newMaxState), + convertAgeRangeTypeToString(newMinState) + ] + + if (isError) { + onChangeBirthdatesRanges(oldBirthdatesRanges) + } else if (birthdatesRanges[1] !== newBirthdatesRanges[1] || birthdatesRanges[0] !== newBirthdatesRanges[0]) { + onError(false) + if (stateName === 'minState') { + setMinState(newMinState) + } else { + setMaxState(newMaxState) + } + onChangeBirthdatesRanges(newBirthdatesRanges) } } @@ -174,10 +176,10 @@ const InputAgeRange: React.FC = ({ birthdates, onCha )} - {error && ( - - Au moins une des valeurs maximales ne doit pas être égale à 0. - + {error.isError && ( + + {error.errorMessage} + )} diff --git a/src/components/Patient/PatientSidebar/PatientSidebar.tsx b/src/components/Patient/PatientSidebar/PatientSidebar.tsx index a0cb4e1fd..f1271155f 100644 --- a/src/components/Patient/PatientSidebar/PatientSidebar.tsx +++ b/src/components/Patient/PatientSidebar/PatientSidebar.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react' import { useLocation } from 'react-router-dom' -import moment from 'moment' import { CircularProgress, Divider, Drawer, Grid, IconButton, List, Typography } from '@material-ui/core' import Pagination from '@material-ui/lab/Pagination' @@ -10,12 +9,13 @@ import PatientSidebarItem from './PatientSidebarItem/PatientSidebarItem' import ChevronRightIcon from '@material-ui/icons/ChevronRight' -import { getAge } from 'utils/age' +import { getAge, substructAgeString } from 'utils/age' import services from 'services/aphp' import { PatientGenderKind } from '@ahryman40k/ts-fhir-types/lib/R4' import { CohortPatient, PatientFilters as PatientFiltersType, SearchByTypes, Sort, VitalStatus } from 'types' import useStyles from './styles' +import moment from 'moment/moment' type PatientSidebarTypes = { total: number @@ -51,7 +51,7 @@ const PatientSidebar: React.FC = ({ const [filters, setFilters] = useState({ gender: PatientGenderKind._unknown, - birthdates: [moment().subtract(130, 'years').format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')], + birthdatesRanges: ['', ''], vitalStatus: VitalStatus.all }) @@ -67,12 +67,17 @@ const PatientSidebar: React.FC = ({ const onSearchPatient = async (sort: Sort, page = 1) => { setLoadingStatus(true) + const birthdates: [string, string] = [ + moment(substructAgeString(filters.birthdatesRanges[0])).format('MM/DD/YYYY'), + moment(substructAgeString(filters.birthdatesRanges[1])).format('MM/DD/YYYY') + ] + const patientsResp = await services.cohorts.fetchPatientList( page, searchBy, searchInput, filters.gender, - filters.birthdates, + birthdates, filters.vitalStatus, sort.sortBy, sort.sortDirection, diff --git a/src/types.ts b/src/types.ts index ff1929e72..d522b4ef9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -153,7 +153,7 @@ export type PMSIFilters = { export type PatientFilters = { gender: PatientGenderKind - birthdates: [string, string] + birthdatesRanges: [string, string] vitalStatus: VitalStatus } @@ -755,3 +755,9 @@ export type IScope = { previous: string | null results: ScopePage[] } +export type ErrorType = { isError: boolean; errorMessage?: string } +export type AgeRangeType = { + year?: number + month?: number + days?: number +} diff --git a/src/utils/age.ts b/src/utils/age.ts index 28ed087c4..aaca1854f 100644 --- a/src/utils/age.ts +++ b/src/utils/age.ts @@ -1,4 +1,4 @@ -import { CohortPatient } from 'types' +import { AgeRangeType, CohortPatient } from 'types' import moment from 'moment' export const getAgeAphp = (ageObj: any, momentUnit: 'days' | 'months') => { @@ -38,30 +38,16 @@ export const getAge = (patient: CohortPatient): string => { } export const ageName = (dates: [string, string]) => { - const minDate: any = {} - const maxDate: any = {} - - maxDate.year = moment().diff(moment(dates[0], 'YYYY-MM-DD'), 'year') || 0 - maxDate.month = moment().subtract(maxDate.year, 'year').diff(moment(dates[0], 'YYYY-MM-DD'), 'month') - maxDate.days = moment() - .subtract(maxDate.year, 'year') - .subtract(maxDate.month, 'month') - .diff(moment(dates[0], 'YYYY-MM-DD'), 'days') - - minDate.year = moment().diff(moment(dates[1], 'YYYY-MM-DD'), 'year') || 0 - minDate.month = moment().subtract(minDate.year, 'year').diff(moment(dates[1], 'YYYY-MM-DD'), 'month') - minDate.days = moment() - .subtract(minDate.year, 'year') - .subtract(minDate.month, 'month') - .diff(moment(dates[1], 'YYYY-MM-DD'), 'days') + const minDate: AgeRangeType = convertStringToAgeRangeType(dates[1]) ?? { year: 0, month: 0, days: 0 } + const maxDate: AgeRangeType = convertStringToAgeRangeType(dates[0]) ?? { year: 0, month: 0, days: 0 } if ( - minDate.year === 0 && - minDate.month === 0 && - minDate.days === 0 && - maxDate.year === 130 && - maxDate.month === 0 && - maxDate.days === 0 + !minDate.year && + !minDate.month && + !minDate.days && + (maxDate.year === 130 || !maxDate.year) && + !maxDate.month && + !maxDate.days ) { return '' } @@ -69,13 +55,46 @@ export const ageName = (dates: [string, string]) => { return `Age entre ${ minDate.year || minDate.month || minDate.days - ? `${minDate.year > 0 ? `${minDate.year} an(s) ` : ``} - ${minDate.month > 0 ? `${minDate.month} mois ` : ``} - ${minDate.days > 0 ? `${minDate.days} jour(s) ` : ``}` + ? `${(minDate.year ?? 0) > 0 ? `${minDate.year} an(s) ` : ``} + ${(minDate.month ?? 0) > 0 ? `${minDate.month} mois ` : ``} + ${(minDate.days ?? 0) > 0 ? `${minDate.days} jour(s) ` : ``}` : 0 } et - ${maxDate.year > 0 ? `${maxDate.year} an(s) ` : ``} - ${maxDate.month > 0 ? `${maxDate.month} mois ` : ``} - ${maxDate.days > 0 ? `${maxDate.days} jour(s) ` : ``}` + ${(maxDate.year ?? 0) > 0 ? `${maxDate.year} an(s) ` : ``} + ${(maxDate.month ?? 0) > 0 ? `${maxDate.month} mois ` : ``} + ${(maxDate.days ?? 0) > 0 ? `${maxDate.days} jour(s) ` : ``}` +} + +export const substructAgeRangeType = (ageDate: AgeRangeType) => { + if (!ageDate) return new Date() + const today: Date = new Date() + const newDate: Date = new Date( + new Date().getUTCFullYear() - (ageDate.year ?? 0), + today.getUTCMonth() - (ageDate.month ?? 0), + today.getUTCDate() - (ageDate.days ?? 0), + 0, + 0, + 0 + ) + return newDate +} + +export const substructAgeString = (range: string) => { + const ageRangeType: AgeRangeType = convertStringToAgeRangeType(range) ?? { year: 0, month: 0, days: 0 } + return substructAgeRangeType(ageRangeType) +} + +export const convertStringToAgeRangeType = (age: string) => { + if (!age) return undefined + const newAge: AgeRangeType = { + year: Number(age.split('/')[2]), + month: Number(age.split('/')[1]), + days: Number(age.split('/')[0]) + } + return newAge +} + +export const convertAgeRangeTypeToString = (ageDate: AgeRangeType) => { + return ageDate.days + '/' + ageDate.month + '/' + ageDate.year } diff --git a/src/utils/chips.ts b/src/utils/chips.ts index 3eb9f98f9..2684ed8c9 100644 --- a/src/utils/chips.ts +++ b/src/utils/chips.ts @@ -66,7 +66,7 @@ export const buildPatientFiltersChips = ( handleDeleteChip: (filterName: 'gender' | 'birthdates' | 'vitalStatus') => void ) => { const gender = genderName(filters.gender) - const birthdates = ageName(filters.birthdates) + const birthdates = ageName(filters.birthdatesRanges) const vitalStatus = vitalStatusName(filters.vitalStatus) return [