diff --git a/src/components/CreationCohort/DataList_Criteria.tsx b/src/components/CreationCohort/DataList_Criteria.tsx index 3fcf1d6d9..6c3b076f0 100644 --- a/src/components/CreationCohort/DataList_Criteria.tsx +++ b/src/components/CreationCohort/DataList_Criteria.tsx @@ -6,18 +6,24 @@ import DocumentsForm from './DiagramView/components/LogicalOperator/components/C import EncounterForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/EncounterForm' import CCAMForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CCAM' import Cim10Form from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/Cim10Form' -import GhmForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM' import MedicationForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/MedicationForm' import BiologyForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/BiologyForm' import DemographicForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/DemographicForm' -import ImagingForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm' import PregnantForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/PregnantForm' -import HospitForm from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm' import services from 'services/aphp' import { CriteriaType, CriteriaTypeLabels } from 'types/requestCriterias' import { getConfig } from 'config' +import GHMForm2, { + form as ghmForm +} from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHMForm' +import HospitForm2, { + form as hospitForm +} from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm' +import ImagingForm2, { + form as imagingForm +} from './DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm' const criteriaList: () => CriteriaItemType[] = () => { const ODD_QUESTIONNAIRE = getConfig().features.questionnaires.enabled @@ -115,7 +121,8 @@ const criteriaList: () => CriteriaItemType[] = () => { title: CriteriaTypeLabels.CLAIM, color: '#0063AF', fontWeight: 'normal', - components: GhmForm, + formDefinition: ghmForm(), + components: GHMForm2, fetch: { ghmData: services.cohortCreation.fetchGhmData, encounterStatus: services.cohortCreation.fetchEncounterStatus @@ -204,7 +211,8 @@ const criteriaList: () => CriteriaItemType[] = () => { color: ODD_QUESTIONNAIRE ? '#0063AF' : '#808080', fontWeight: 'normal', disabled: !ODD_QUESTIONNAIRE, - components: HospitForm, + formDefinition: hospitForm(), + components: HospitForm2, fetch: { inUteroTransfer: services.cohortCreation.fetchInUteroTransfer, pregnancyMonitoring: services.cohortCreation.fetchPregnancyMonitoring, @@ -247,7 +255,8 @@ const criteriaList: () => CriteriaItemType[] = () => { title: CriteriaTypeLabels.IMAGING, color: ODD_IMAGING ? '#0063AF' : '#808080', fontWeight: 'bold', - components: ImagingForm, + formDefinition: imagingForm(), + components: ImagingForm2, disabled: !ODD_IMAGING, fetch: { modalities: services.cohortCreation.fetchModalities, diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/components/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/components/index.tsx new file mode 100644 index 000000000..7c2635ba7 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/components/index.tsx @@ -0,0 +1,113 @@ +import { FormLabel, Tooltip } from '@mui/material' +import React, { PropsWithChildren } from 'react' +import { + CriteriaData, + CriteriaFormItemView, + CriteriaFormItemViewProps, + CriteriaItem, + CriteriaItemType, + CriteriaSection, + DataTypeMapping +} from '../types' +import useStyles from '../style' +import { BlockWrapper } from 'components/ui/Layout' +import Collapse from 'components/ui/Collapse' +import { CriteriaLabel } from 'components/ui/CriteriaLabel' +import InfoIcon from '@mui/icons-material/Info' +import { isFunction, isString } from 'lodash' + +type CriteriaItemRuntimeProps = { + setError: (error?: string) => void + updateData: (data: T) => void + data: T + getValueSetOptions: CriteriaFormItemViewProps['getValueSetOptions'] + searchCode: CriteriaFormItemViewProps['searchCode'] + viewRenderers: { [key in CriteriaItemType]: CriteriaFormItemView } +} + +type CritieraItemProps = CriteriaItemRuntimeProps & CriteriaItem + +export const renderLabel = (label: string, tooltip?: string, altStyle?: boolean) => { + if (altStyle) { + return ( + + Fin de prise en charge + {tooltip && ( + + + + )} + + ) + } + return label +} + +export const CFItem = >(props: CritieraItemProps) => { + const { valueKey, updateData, data, setError, getValueSetOptions, searchCode, viewRenderers } = props + const View = viewRenderers[props.type] as CriteriaFormItemView + const fieldValue = data[valueKey] as DataTypeMapping[U['type']]['dataType'] + const displayCondition = props.displayCondition + if ( + displayCondition && + ((isFunction(displayCondition) && !displayCondition(data)) || + (isString(displayCondition) && !eval(displayCondition))) + ) { + return null + } + const disableCondition = props.disableCondition + const disabled = + disableCondition && + ((isFunction(disableCondition) && disableCondition(data)) || (isString(disableCondition) && eval(disableCondition))) + return ( + updateData({ ...data, [valueKey]: value })} + getValueSetOptions={getValueSetOptions} + searchCode={searchCode} + setError={setError} + /> + ) +} + +export const CFSection = ( + props: PropsWithChildren, 'items'> & { collapsed?: boolean }> +) => { + const { classes } = useStyles() + return props.title ? ( + + + {props.children} + + + ) : ( + <>{props.children} + ) +} + +export const CFItemWrapper = (props: PropsWithChildren<{ label?: string; info?: string }>) => { + const { classes } = useStyles() + return ( + + {props.label ? ( + + {props.info && ( + + + + )} + + ) : ( + '' + )} + {props.children} + + ) +} + +export default { CFItemWrapper, CFSection, renderLabel, CFItem } diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx new file mode 100644 index 000000000..a0b531a3c --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/index.tsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from 'react' +import CriteriaLayout from 'components/ui/CriteriaLayout' +import { + CriteriaData, + CriteriaForm as CriteriaFormDefinition, + CriteriaFormItemViewProps, + NumberAndComparatorDataType, + NewDurationRangeType +} from './types' +import { CFItem, CFItemWrapper, CFSection } from './components' +import FORM_ITEM_RENDERER from './renderers' +import { useAppSelector } from 'state' +import { LabelObject } from 'types/searchCriterias' + +export type CriteriaFormRuntimeProps = { + isEdition: boolean + goBack: () => void + data?: T + updateData: (data: T) => void + searchCode: CriteriaFormItemViewProps['searchCode'] +} + +type CriteriaFormProps = CriteriaFormDefinition & CriteriaFormRuntimeProps + +export default function CriteriaForm(props: CriteriaFormProps) { + const [criteriaData, setCriteriaData] = useState(props.data || props.initialData) + const valueSets = useAppSelector((state) => state.valueSets) + const { goBack, updateData, label, warningAlert, itemSections, errorMessages, onDataChange } = props + const isEdition = !!props.data + const [error, setError] = useState() + + useEffect(() => { + onDataChange?.(criteriaData) + }, [criteriaData, onDataChange]) + + console.log('rendering form') + + return ( + setCriteriaData({ ...criteriaData, title })} + isEdition={isEdition} + goBack={goBack} + onSubmit={() => updateData(criteriaData)} + disabled={error !== undefined} + isInclusive={criteriaData.isInclusive} + onChangeIsInclusive={(isInclusive) => setCriteriaData({ ...criteriaData, isInclusive: isInclusive })} + infoAlert={['Tous les éléments des champs multiples sont liés par une contrainte OU']} + warningAlert={warningAlert} + errorAlert={error ? [errorMessages[error]] : undefined} + > + {itemSections.map((section, index) => ( + { + const value = criteriaData[item.valueKey] + let isNull = !value || (Array.isArray(value) && value.length === 0) + if (item.type === 'numberAndComparator') { + isNull = isNull || !(value as NumberAndComparatorDataType).value + } else if (item.type === 'duration') { + const duration = value as NewDurationRangeType + isNull = isNull || (duration.start === null && duration.end === null) + } + if (!isNull) console.log(isNull, item.valueKey, value) + + return isNull + })} + > + {section.items.map((item, index) => ( + + { + return (valueSets.entities[valueSetId]?.options || []) as LabelObject[] + }, + searchCode: props.searchCode, + updateData: (newData: T) => { + setCriteriaData({ ...criteriaData, ...newData }) + }, + setError + }} + /> + + ))} + + ))} + + ) +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/legacyFormAdapter.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/legacyFormAdapter.tsx new file mode 100644 index 000000000..ba8d3892a --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/legacyFormAdapter.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import { CriteriaDrawerComponentProps, CriteriaItemDataCache } from 'types' +import { CriteriaData } from './types' +import CriteriaForm from '.' +import { CriteriaForm as CriteriaFormDefinition } from './types' +import { SelectedCriteriaType } from 'types/requestCriterias' +import { fetchValueSet } from 'services/aphp/callApi' +import { cp } from 'fs' + +export type LegacyAdapterProps = { + form: () => CriteriaFormDefinition + adapter: { + mapFromLegacyDataType: (legacyData: U, criteriaData: CriteriaItemDataCache) => T + mapToLegacyDataType: (data: T) => Omit + } +} + +/** + * Enable the use of the new CriteriaForm component with the legacy data format + * @param props contains the form definition and the adapter to convert the legacy data to the new data format + * @returns the legacy Drawer component with the new CriteriaForm component + */ +export default function withLegacyAdapter( + props: LegacyAdapterProps +) { + return (legacyProps: CriteriaDrawerComponentProps) => { + const { criteriaData, goBack, selectedCriteria, onChangeSelectedCriteria } = legacyProps + const isEdition = selectedCriteria !== null + const formDefinition = props.form() + const runtimeProps = { + data: selectedCriteria + ? props.adapter.mapFromLegacyDataType(selectedCriteria as U, criteriaData) + : formDefinition.initialData, + updateData: (data: T) => onChangeSelectedCriteria(props.adapter.mapToLegacyDataType(data) as U), + goBack + } + return ( + + fetchValueSet( + codeSystemUrl, + { valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false }, + abortSignal + ) + } + /> + ) + } +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/renderers.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/renderers.tsx new file mode 100644 index 000000000..c5476ba80 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/renderers.tsx @@ -0,0 +1,137 @@ +import React from 'react' +import { CriteriaFormItemView, CriteriaItemType } from './types' +import { renderLabel } from './components' +import CalendarRange from 'components/ui/Inputs/CalendarRange' +import { Autocomplete, FormControlLabel, Radio, RadioGroup, TextField } from '@mui/material' +import ExecutiveUnitsInput from 'components/ui/Inputs/ExecutiveUnit' +import OccurenceInput from 'components/ui/Inputs/Occurences' +import SearchbarWithCheck from 'components/ui/Inputs/SearchbarWithCheck' +import AsyncAutocomplete from 'components/ui/Inputs/AsyncAutocomplete' +import CheckedTextfield from 'components/ui/Inputs/CheckedTextfield' +import { isArray } from 'lodash' +import useStyles from './style' +import { useAppSelector } from 'state' + +const FORM_ITEM_RENDERER: { [key in CriteriaItemType]: CriteriaFormItemView } = { + text: (props) => , + textWithRegex: (props) => { + return ( + props.setError(isError ? 'error' : undefined)} + onChange={(value) => props.updateData(value)} + /> + ) + }, + duration: (props) => ( + <> + + props.updateData({ start: range[0] || null, end: range[1] || null, includeNull }) + } + onError={(isError) => props.setError(isError ? props.definition.errorType : undefined)} + includeNullValues={props.value?.includeNull} + onChangeIncludeNullValues={ + props.definition.withOptionIncludeNull + ? () => { + /* dummy TODO change CalendarRange to accept a boolean to activate the includeNull checkbox */ + } + : undefined + } + /> + + ), + autocomplete: (props) => { + const value = props.definition.singleChoice ? props.value?.at(0) || null : props.value || [] + return ( + option.label} + isOptionEqualToValue={(option, value) => option.id === value.id} + value={value} + onChange={(e, value) => props.updateData(!!value ? (isArray(value) ? value : [value]) : null)} + renderInput={(params) => } + /> + ) + }, + radioChoice: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { classes } = useStyles() + return ( + props.updateData(value)} + > + {props.definition.choices.map((choice, index) => ( + } label={choice.label} /> + ))} + + ) + }, + number: (props) => , + executiveUnit: (props) => ( + props.updateData(value)} + /> + ), + numberAndComparator: (props) => ( + { + props.updateData({ value: newCount, comparator: newComparator }) + }} + withHierarchyInfo={props.definition.withHierarchyInfo} + /> + ), + boolean: (props) => , + textWithCheck: (props) => ( + props.updateData(value)} + placeholder={props.definition.placeholder} + onError={(isError) => props.setError(isError ? props.definition.errorType : undefined)} + /> + ), + codeSearch: (props) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const codeCache = useAppSelector((state) => state.valueSets.cache[props.definition.valueSetId]) + const valueWithLabels = (props.value || []).map((code) => codeCache.find((c) => c.id === code.id) || code) + return ( + props.searchCode(search, props.definition.valueSetId, signal)} + onChange={(value) => props.updateData(value)} + /> + ) + } +} + +export default FORM_ITEM_RENDERER diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts similarity index 98% rename from src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts rename to src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts index 22ddd251b..8765a8bcb 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/styles.ts +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/style.ts @@ -13,7 +13,7 @@ const useStyles = makeStyles()(() => ({ padding: 20, backgroundColor: '#317EAA', color: 'white', - //not default + // Not default marginBottom: 46 }, backButton: { color: 'white' }, diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts new file mode 100644 index 000000000..f1523765f --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types.ts @@ -0,0 +1,268 @@ +import { ReactNode } from 'react' +import { ScopeElement } from 'types' +import { Hierarchy } from 'types/hierarchy' +import { Comparators, CriteriaType } from 'types/requestCriterias' +import { SourceType } from 'types/scope' +import { LabelObject } from 'types/searchCriterias' + +/********************************************************************************/ +/* Criteria Types */ +/********************************************************************************/ +// When adding a new criteria type you must complete the following steps: +// 1. **REQUIRED** Add the new type to the CriteriaItemType union type +// 2. **REQUIRED** create a new CriteriaItem type (with required props "type") and then add the new type to the CriteriaItems union type +// 3. Optionnaly create the new data type definition and add it to the DataType union type +// 5. **REQUIRED** Update the DataTypeMapping type with the new type mapping + +// List of criteria item types +export type CriteriaItemType = + | 'duration' + | 'text' + | 'autocomplete' + | 'number' + | 'executiveUnit' + | 'numberAndComparator' + | 'boolean' + | 'textWithCheck' + | 'codeSearch' + | 'textWithRegex' + | 'radioChoice' + +/****************************************************************/ +/* Criteria Data Types */ +/****************************************************************/ + +export type NewDurationRangeType = { + start: string | null + end: string | null + includeNull: boolean +} + +export type NumberAndComparatorDataType = { + value: number + comparator: Comparators +} + +// Union of all criteria data types +export type DataType = + | NewDurationRangeType + | string + | LabelObject[] + | number + | Hierarchy[] + | NumberAndComparatorDataType + | boolean + | null + +/****************************************************************/ +/* Criteria Item Definition Types */ +/****************************************************************/ + +type BaseCriteriaItem = { + label?: string + labelAltStyle?: boolean + info?: string + // these are used to display external label and info on top of the component + extraLabel?: string + extraInfo?: string + // for conditionnal fields + displayCondition?: (data: CriteriaData) => boolean | string + disableCondition?: (data: CriteriaData) => boolean | string +} + +type WithLabel = { + label: string +} + +type WithErrorType = { + errorType: string +} + +export type TextCriteriaItem = BaseCriteriaItem & { + type: 'text' +} + +export type TextWithRegexCriteriaItem = BaseCriteriaItem & { + type: 'textWithRegex' + regex: string + checkErrorMessage?: string + placeholder?: string + multiline?: boolean +} + +export type NumberCriteriaItem = BaseCriteriaItem & { + type: 'number' +} + +export type BooleanCriteriaItem = BaseCriteriaItem & { + type: 'boolean' +} + +export type RadioChoiceCriteriaItem = BaseCriteriaItem & { + type: 'radioChoice' + choices: LabelObject[] +} + +export type NumberWithComparatorCriteriaItem = BaseCriteriaItem & + WithLabel & { + type: 'numberAndComparator' + withHierarchyInfo?: boolean + floatValues?: boolean + } + +export type CalendarItem = BaseCriteriaItem & + WithErrorType & { + type: 'duration' + withOptionIncludeNull?: boolean + } + +export type AutoCompleteItem = BaseCriteriaItem & { + type: 'autocomplete' + noOptionsText: string + singleChoice?: boolean + valueSetId: string + valueSetData?: LabelObject[] +} + +export type CodeSearchItem = BaseCriteriaItem & { + type: 'codeSearch' + noOptionsText: string + valueSetId: string +} + +export type ExecutiveUnitItem = BaseCriteriaItem & + WithLabel & { + type: 'executiveUnit' + sourceType: SourceType + } + +export type TextWithCheckItem = BaseCriteriaItem & + WithErrorType & { + type: 'textWithCheck' + placeholder: string + } + +// Union of all criteria item types +export type CriteriaItems = + | CalendarItem + | AutoCompleteItem + | ExecutiveUnitItem + | TextWithCheckItem + | BooleanCriteriaItem + | TextCriteriaItem + | NumberCriteriaItem + | CodeSearchItem + | NumberWithComparatorCriteriaItem + | TextWithRegexCriteriaItem + | RadioChoiceCriteriaItem + +/****************************************************************/ +/* Type Mapping */ +/****************************************************************/ + +type CriteriaTypeMapping = { + dataType: T + definition: U +} + +// Mapping of criteria item types to their respective data types and definitions +export type DataTypeMapping = { + duration: CriteriaTypeMapping + text: CriteriaTypeMapping + autocomplete: CriteriaTypeMapping + number: CriteriaTypeMapping + executiveUnit: CriteriaTypeMapping[], ExecutiveUnitItem> + numberAndComparator: CriteriaTypeMapping + boolean: CriteriaTypeMapping + textWithCheck: CriteriaTypeMapping + codeSearch: CriteriaTypeMapping + textWithRegex: CriteriaTypeMapping + radioChoice: CriteriaTypeMapping +} + +/********************************************************************************/ +/* Criteria Form Types */ +/********************************************************************************/ + +/****************************************************************/ +/* Criteria Form Data */ +/****************************************************************/ + +// Will replace SelectedCriteriaType +export type CriteriaData = { + id?: number + type: CriteriaType + title: string + isInclusive: boolean + occurrence: NumberAndComparatorDataType + encounterStatus: LabelObject[] +} + +export type GenericCriteriaData = CriteriaData & { [key: string]: DataType } + +// helpers +export type WithOccurenceCriteriaDataType = { + startOccurrence: NewDurationRangeType | null +} + +export type WithEncounterDateDataType = { + encounterStartDate: NewDurationRangeType | null + encounterEndDate: NewDurationRangeType | null +} + +export type WithEncounterStatusDataType = { + encounterStatus: LabelObject[] + encounterService: Hierarchy[] | null +} + +/****************************************************************/ +/* Criteria Form Definition */ +/****************************************************************/ + +// TODO: will be used later in utils/cohortCreation.ts to build / unbuild the criteria data +export type BuildInfo = { + fhirKey: string + // TODO find a better way to type this instead of DataType + buildMethod: ((value: DataType, deidentified: boolean) => string[] | undefined | string) | string + unbuildMethod: ((value: string) => Promise) | string + chipDisplayMethod: (value: DataType) => ReactNode | string +} + +// Criteria item definition +export type CriteriaItem = { + valueKey: keyof T + build?: BuildInfo +} & CriteriaItems + +// Criteria section listing criteria items +export type CriteriaSection = { + title?: string + defaulCollapsed?: boolean + items: CriteriaItem[] +} + +// Criteria form definition +export type CriteriaForm = { + label: string + warningAlert?: ReactNode[] + initialData: T + errorMessages: { [key: string]: string } + onDataChange?: (data: T) => void + itemSections: CriteriaSection[] +} + +/****************************************************************/ +/* Criteria Item Render */ +/****************************************************************/ + +export type CriteriaFormItemViewProps = { + value: DataTypeMapping[T]['dataType'] + definition: DataTypeMapping[T]['definition'] + disabled: boolean + getValueSetOptions: (valueSetId: string) => LabelObject[] + searchCode: (code: string, codeSystemUrl: string, abortSignal: AbortSignal) => Promise + updateData: (value: DataTypeMapping[T]['dataType'] | null) => void + setError: (error?: string) => void +} + +export type CriteriaFormItemView = React.FC> diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/utils.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/utils.ts new file mode 100644 index 000000000..32bdd6542 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/utils.ts @@ -0,0 +1,32 @@ +import { CriteriaItemDataCache } from 'types' +import { CriteriaDataKey } from 'types/requestCriterias' +import { DurationRangeType, LabelObject } from 'types/searchCriterias' +import { NewDurationRangeType } from './types' + +export const mappingLabelObject = ( + criteriaToMap: LabelObject[] | null, + key: CriteriaDataKey, + mapping: CriteriaItemDataCache +) => { + if (criteriaToMap) { + return criteriaToMap.map((criteria) => { + const mappedCriteria = mapping.data?.[key]?.find((c: LabelObject) => c?.id === criteria?.id) + return mappedCriteria + }) + } else { + return [] + } +} + +export const mapToNewDurationRange = ( + oldDuration: DurationRangeType, + oldIncludeNull?: boolean | null +): NewDurationRangeType | null => { + return !oldDuration[0] && !oldDuration[1] + ? null + : { + start: oldDuration[0] || null, + end: oldDuration[1] || null, + includeNull: oldIncludeNull || false + } +} diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx deleted file mode 100644 index 247d8e2f4..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/components/Form/GhmForm.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import React, { useState } from 'react' - -import { - Alert, - Autocomplete, - Button, - Divider, - FormLabel, - Grid, - IconButton, - Link, - Switch, - TextField, - Typography -} from '@mui/material' - -import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' - -import useStyles from './styles' -import { useAppDispatch, useAppSelector } from 'state' -import { fetchClaim } from 'state/pmsi' -import { CriteriaItemDataCache, HierarchyTree } from 'types' -import AdvancedInputs from '../../../AdvancedInputs/AdvancedInputs' -import AsyncAutocomplete from 'components/ui/Inputs/AsyncAutocomplete' -import services from 'services/aphp' -import { Comparators, GhmDataType, SelectedCriteriaType } from 'types/requestCriterias' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' - -type GHMFormProps = { - isOpen: boolean - isEdition: boolean - criteriaData: CriteriaItemDataCache - selectedCriteria: GhmDataType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - onChangeValue: (key: string, value: any) => void - goBack: () => void - onChangeSelectedCriteria: (data: SelectedCriteriaType) => void -} - -enum Error { - ADVANCED_INPUTS_ERROR, - NO_ERROR -} - -const GhmForm: React.FC = (props) => { - const { isOpen, isEdition, criteriaData, selectedCriteria, onChangeValue, onChangeSelectedCriteria, goBack } = props - - const { classes } = useStyles() - const dispatch = useAppDispatch() - const initialState: HierarchyTree | null = useAppSelector((state) => state.syncHierarchyTable) - const currentState = { ...selectedCriteria, ...initialState } - const [multiFields, setMultiFields] = useState(localStorage.getItem('multiple_fields')) - const [occurrence, setOccurrence] = useState(currentState.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - currentState.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [error, setError] = useState(Error.NO_ERROR) - - const getGhmOptions = async (searchValue: string, signal: AbortSignal) => - await services.cohortCreation.fetchGhmData(searchValue, false, signal) - const _onSubmit = () => { - onChangeSelectedCriteria({ ...currentState, occurrence: occurrence, occurrenceComparator: occurrenceComparator }) - dispatch(fetchClaim()) - } - const defaultValuesCode = currentState.code - ? currentState.code.map((code) => { - const criteriaCode = criteriaData.data.ghmData - ? criteriaData.data.ghmData.find((g: Hierarchy) => g.id === code.id) - : null - return { - id: code.id, - label: code.label ? code.label : criteriaCode?.label ?? '?' - } - }) - : [] - - return isOpen ? ( - - - {!isEdition ? ( - <> - - - - - Ajouter un critère de GHM - - ) : ( - Modifier un critère de GHM - )} - - - - {!multiFields && ( - { - localStorage.setItem('multiple_fields', 'ok') - setMultiFields('ok') - }} - > - Tous les éléments des champs multiples sont liés par une contrainte OU - - )} - - - Données actuellement disponibles : PMSI ORBIS. Pour plus d'informations sur les prochaines intégrations de - données, veuillez vous référer au tableau trimestriel de disponibilité des données disponible{' '} - - ici - - - - - GHM - - onChangeValue('title', e.target.value)} - /> - - - onChangeValue('isInclusive', !currentState.isInclusive)} - style={{ margin: 'auto 1em' }} - component="legend" - > - Exclure les patients qui suivent les règles suivantes - - onChangeValue('isInclusive', !event.target.checked)} - color="secondary" - /> - - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - withHierarchyInfo - /> - - - { - onChangeValue('code', value) - }} - /> - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={currentState.encounterStatus} - onChange={(e, value) => onChangeValue('encounterStatus', value)} - renderInput={(params) => } - /> - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - /> - - - - {!isEdition && ( - - )} - - - - - ) : ( - <> - ) -} - -export default GhmForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx index 88af99817..ca311d69d 100644 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHM/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' import { Tab, Tabs } from '@mui/material' -import GHMForm from './components/Form/GhmForm' import GHMHierarchy from './components/Hierarchy/GhmHierarchy' import useStyles from './styles' @@ -12,6 +11,7 @@ import { EXPLORATION } from '../../../../../../../../constants' import { CriteriaDrawerComponentProps } from 'types' import { Comparators, GhmDataType, CriteriaType } from 'types/requestCriterias' import { Hierarchy } from 'types/hierarchy' +import GHMForm from '../GHMForm' export const defaultClaim: Omit = { type: CriteriaType.CLAIM, @@ -75,17 +75,15 @@ const Index = (props: CriteriaDrawerComponentProps) => { - { + {selectedTab === 'form' ? ( - } + ) : null} { CriteriaForm = () => ({ + label: 'GHM', + initialData: { + title: 'Critères GHM', + type: CriteriaType.CLAIM, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + startOccurrence: null, + encounterStartDate: null, + encounterEndDate: null, + code: [] + }, + warningAlert: [ +
+ Données actuellement disponibles : PMSI ORBIS. Pour plus d'informations sur les prochaines intégrations de + données, veuillez vous référer au tableau trimestriel de disponibilité des données disponible{' '} + + ici + +
+ ], + errorMessages: {}, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurences", + withHierarchyInfo: true + }, + { + valueKey: 'code', + type: 'codeSearch', + valueSetId: getConfig().features.claim.valueSets.claimHierarchy.url, + noOptionsText: 'Aucun GHM trouvé', + label: 'Code GHM' + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Aucun statut trouvé' + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.GHM + }, + { + valueKey: 'encounterStartDate', + type: 'duration', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: 'Prise en charge', + withOptionIncludeNull: true + }, + { + valueKey: 'encounterEndDate', + type: 'duration', + label: 'Fin de prise en charge', + labelAltStyle: true, + info: 'Ne concerne pas les consultations', + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true + }, + { + valueKey: 'startOccurrence', + type: 'duration', + errorType: 'ADVANCED_INPUTS_ERROR', + extraLabel: 'Date de classement en GHM' + } + ] + } + ] +}) + +export default withLegacyAdapter({ + form, + adapter: { + mapFromLegacyDataType: (legacyData: GhmDataType, criteriaDataCache: CriteriaItemDataCache) => ({ + id: legacyData.id, + type: CriteriaType.CLAIM, + title: legacyData.title, + isInclusive: legacyData.isInclusive || true, + occurrence: { + value: legacyData.occurrence || 1, + comparator: legacyData.occurrenceComparator || Comparators.GREATER_OR_EQUAL + }, + encounterService: legacyData.encounterService || null, + encounterStatus: legacyData.encounterStatus, + encounterStartDate: mapToNewDurationRange( + legacyData.encounterStartDate, + legacyData.includeEncounterStartDateNull + ), + encounterEndDate: mapToNewDurationRange(legacyData.encounterEndDate, legacyData.includeEncounterEndDateNull), + startOccurrence: mapToNewDurationRange(legacyData.startOccurrence), + code: mappingLabelObject(legacyData.code, CriteriaDataKey.GHM_DATA, criteriaDataCache) + }), + mapToLegacyDataType: (data: NewGhmDataType) => ({ + id: data.id, + type: CriteriaType.CLAIM, + title: data.title, + isInclusive: data.isInclusive, + occurrence: data.occurrence.value, + startOccurrence: [data.startOccurrence?.start, data.startOccurrence?.end], + occurrenceComparator: data.occurrence.comparator, + encounterStatus: data.encounterStatus, + encounterService: data.encounterService || undefined, + encounterStartDate: [data.encounterStartDate?.start, data.encounterStartDate?.end], + encounterEndDate: [data.encounterEndDate?.start, data.encounterEndDate?.end], + includeEncounterStartDateNull: data.encounterStartDate?.includeNull, + includeEncounterEndDateNull: data.encounterEndDate?.includeNull, + label: undefined, + code: data.code + }) + } +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm.tsx new file mode 100644 index 000000000..dc0559832 --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm.tsx @@ -0,0 +1,703 @@ +import React from 'react' +import { Comparators, CriteriaDataKey, CriteriaType, HospitDataType } from 'types/requestCriterias' +import withLegacyAdapter from './CriteriaForm/legacyFormAdapter' +import { + CriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterStatusDataType, + WithOccurenceCriteriaDataType +} from './CriteriaForm/types' +import { LabelObject } from 'types/searchCriterias' +import { Link } from '@mui/material' +import { SourceType } from 'types/scope' +import { mappingLabelObject, mapToNewDurationRange } from './CriteriaForm/utils' +import { CriteriaItemDataCache } from 'types' +import { getConfig } from 'config' + +export type NewHospitDataType = CriteriaData & + WithOccurenceCriteriaDataType & + WithEncounterStatusDataType & { + hospitReason: string + inUteroTransfer: LabelObject[] | null + pregnancyMonitoring: LabelObject[] | null + vme: LabelObject[] | null + maturationCorticotherapie: LabelObject[] | null + chirurgicalGesture: LabelObject[] | null + childbirth: LabelObject[] | null + hospitalChildBirthPlace: LabelObject[] | null + otherHospitalChildBirthPlace: LabelObject[] | null + homeChildBirthPlace: LabelObject[] | null + childbirthMode: LabelObject[] | null + maturationReason: LabelObject[] | null + maturationModality: LabelObject[] | null + imgIndication: LabelObject[] | null + laborOrCesareanEntry: LabelObject[] | null + pathologyDuringLabor: LabelObject[] | null + obstetricalGestureDuringLabor: LabelObject[] | null + analgesieType: LabelObject[] | null + birthDeliveryDate: NewDurationRangeType | null + birthDeliveryWeeks: NumberAndComparatorDataType + birthDeliveryDays: NumberAndComparatorDataType + birthDeliveryWay: LabelObject[] | null + instrumentType: LabelObject[] | null + cSectionModality: LabelObject[] | null + presentationAtDelivery: LabelObject[] | null + birthMensurationsGrams: NumberAndComparatorDataType + birthMensurationsPercentil: NumberAndComparatorDataType + apgar1: NumberAndComparatorDataType + apgar3: NumberAndComparatorDataType + apgar5: NumberAndComparatorDataType + apgar10: NumberAndComparatorDataType + arterialPhCord: NumberAndComparatorDataType + arterialCordLactates: NumberAndComparatorDataType + birthStatus: LabelObject[] | null + postpartumHemorrhage: LabelObject[] | null + conditionPerineum: LabelObject[] | null + exitPlaceType: LabelObject[] | null + feedingType: LabelObject[] | null + complication: LabelObject[] | null + exitFeedingMode: LabelObject[] | null + exitDiagnostic: LabelObject[] | null + } + +export const form: () => CriteriaForm = () => ({ + label: "de fiche d'hopsitalisation", + initialData: { + title: "Critère de fiche d'hospitalisation", + type: CriteriaType.HOSPIT, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + startOccurrence: null, + hospitReason: '', + inUteroTransfer: null, + pregnancyMonitoring: null, + vme: null, + maturationCorticotherapie: null, + chirurgicalGesture: null, + childbirth: null, + hospitalChildBirthPlace: null, + otherHospitalChildBirthPlace: null, + homeChildBirthPlace: null, + childbirthMode: null, + maturationReason: null, + maturationModality: null, + imgIndication: null, + laborOrCesareanEntry: null, + pathologyDuringLabor: null, + obstetricalGestureDuringLabor: null, + analgesieType: null, + birthDeliveryDate: null, + birthDeliveryWeeks: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthDeliveryDays: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthDeliveryWay: null, + instrumentType: null, + cSectionModality: null, + presentationAtDelivery: null, + birthMensurationsGrams: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthMensurationsPercentil: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar1: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar3: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar5: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + apgar10: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + arterialPhCord: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + arterialCordLactates: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + birthStatus: null, + postpartumHemorrhage: null, + conditionPerineum: null, + exitPlaceType: null, + feedingType: null, + complication: null, + exitFeedingMode: null, + exitDiagnostic: null + }, + warningAlert: [ +
+ Données actuellement disponibles : PMSI ORBIS. Pour plus d'informations sur les prochaines intégrations de + données, veuillez vous référer au tableau trimestriel de disponibilité des données disponible{' '} + + ici + +
+ ], + errorMessages: {}, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurences", + withHierarchyInfo: true + }, + { + valueKey: 'encounterService', + type: 'executiveUnit', + label: 'Unité exécutrice', + sourceType: SourceType.MATERNITY + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée' + } + ] + }, + { + title: 'ADMISSION', + defaulCollapsed: true, + items: [ + { + valueKey: 'hospitReason', + type: 'textWithCheck', + placeholder: "Motif(s) d'hospitalisation", + errorType: 'SEARCHINPUT_ERROR' + }, + { + valueKey: 'inUteroTransfer', + type: 'autocomplete', + label: 'Transfert in utero', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'pregnancyMonitoring', + type: 'autocomplete', + label: 'Grossesse peu ou pas suivie', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'vme', + type: 'autocomplete', + label: 'VME', + noOptionsText: 'Veuillez entrer des valeurs de VME', + valueSetId: getConfig().features.questionnaires.valueSets.vme.url, + valueSetData: getConfig().features.questionnaires.valueSets.vme.data + }, + { + valueKey: 'maturationCorticotherapie', + type: 'autocomplete', + label: 'Corticothérapie pour maturation fœtale faite', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'chirurgicalGesture', + type: 'autocomplete', + label: 'Type de geste ou de chirurgie', + noOptionsText: 'Veuillez entrer un type de geste ou de chirurgie', + valueSetId: getConfig().features.questionnaires.valueSets.chirurgicalGesture.url + } + ] + }, + { + title: 'SYNTHESE', + defaulCollapsed: true, + items: [ + { + valueKey: 'childbirth', + type: 'autocomplete', + label: 'Accouchement', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'hospitalChildBirthPlace', + type: 'autocomplete', + label: "Accouchement à la maternité de l'hospitalisation", + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'otherHospitalChildBirthPlace', + type: 'autocomplete', + label: 'Accouchement dans un autre hôpital', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data + }, + { + valueKey: 'homeChildBirthPlace', + type: 'autocomplete', + label: 'Accouchement à domicile', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data + }, + { + valueKey: 'childbirthMode', + type: 'autocomplete', + label: 'Mode de mise en travail', + noOptionsText: 'Veuillez entrer un mode de mise en travail', + valueSetId: getConfig().features.questionnaires.valueSets.childBirthMode.url + }, + { + valueKey: 'maturationReason', + type: 'autocomplete', + label: 'Motif(s) de maturation / déclenchement', + noOptionsText: 'Veuillez entrer un motif de maturation / déclenchement', + valueSetId: getConfig().features.questionnaires.valueSets.maturationReason.url + }, + { + valueKey: 'maturationModality', + type: 'autocomplete', + label: 'Modalités de maturation cervicale initiale', + noOptionsText: 'Veuillez entrer une modalité de maturation cervicale initiale', + valueSetId: getConfig().features.questionnaires.valueSets.maturationModality.url + }, + { + valueKey: 'imgIndication', + type: 'autocomplete', + label: "Indication de l'IMG", + noOptionsText: "Veuillez entrer une indication de l'IMG", + valueSetId: getConfig().features.questionnaires.valueSets.imgIndication.url + }, + { + valueKey: 'laborOrCesareanEntry', + type: 'autocomplete', + label: "Présentation à l'entrée en travail ou en début de césarienne", + noOptionsText: "Veuillez entrer une présentation à l'entrée en travail ou en début de césarienne", + valueSetId: getConfig().features.questionnaires.valueSets.laborOrCesareanEntry.url + }, + { + valueKey: 'pathologyDuringLabor', + type: 'autocomplete', + label: 'Pathologie pendant le travail', + noOptionsText: 'Veuillez entrer une pathologie pendant le travail', + valueSetId: getConfig().features.questionnaires.valueSets.pathologyDuringLabor.url + }, + { + valueKey: 'obstetricalGestureDuringLabor', + type: 'autocomplete', + label: 'Geste ou manoeuvre obstétricale pendant le travail', + noOptionsText: 'Veuillez entrer un geste ou manoeuvre obstétricale pendant le travail', + valueSetId: getConfig().features.questionnaires.valueSets.obstetricalGestureDuringLabor.url + } + ] + }, + { + title: 'ANALGESIE / ANESTHESIE', + defaulCollapsed: true, + items: [ + { + valueKey: 'analgesieType', + type: 'autocomplete', + label: 'Analgésie / Anesthésie - Type', + noOptionsText: "Veuillez entrer un type d'Analgésie / Anesthésie - Type", + valueSetId: getConfig().features.questionnaires.valueSets.analgesieType.url + } + ] + }, + { + title: 'ACCOUCHEMENT ET NAISSANCE', + defaulCollapsed: true, + items: [ + { + valueKey: 'birthDeliveryDate', + type: 'duration', + label: "Date/heure de l'accouchement", + errorType: 'INCOHERENT_AGE_ERROR' + }, + { + valueKey: 'birthDeliveryWeeks', + type: 'numberAndComparator', + label: 'Accouchement - Terme - Semaines' + }, + { + valueKey: 'birthDeliveryDays', + type: 'numberAndComparator', + label: 'Accouchement - Terme - Jours' + }, + { + valueKey: 'birthDeliveryWay', + type: 'autocomplete', + label: 'Voie d’accouchement', + noOptionsText: 'Veuillez entrer une voie d’accouchement', + valueSetId: getConfig().features.questionnaires.valueSets.birthDeliveryWay.url + }, + { + valueKey: 'instrumentType', + type: 'autocomplete', + label: "Type d'instrument", + noOptionsText: "Veuillez entrer un type d'instrument", + valueSetId: getConfig().features.questionnaires.valueSets.instrumentType.url + }, + { + valueKey: 'cSectionModality', + type: 'autocomplete', + label: 'Modalités de la césarienne', + noOptionsText: 'Veuillez entrer une modalité de la césarienne', + valueSetId: getConfig().features.questionnaires.valueSets.cSectionModality.url + }, + { + valueKey: 'presentationAtDelivery', + type: 'autocomplete', + label: "Présentation à l'accouchement", + noOptionsText: "Veuillez entrer une présentation à l'accouchement", + valueSetId: getConfig().features.questionnaires.valueSets.presentationAtDelivery.url + }, + { + valueKey: 'birthMensurationsGrams', + type: 'numberAndComparator', + label: 'Mensurations naissance - Poids (g)' + }, + { + valueKey: 'birthMensurationsPercentil', + type: 'numberAndComparator', + floatValues: true, + label: 'Mensurations naissance - Poids (percentile)' + }, + { + valueKey: 'apgar1', + type: 'numberAndComparator', + label: 'Score Apgar - 1 min' + }, + { + valueKey: 'apgar3', + type: 'numberAndComparator', + label: 'Score Apgar - 3 min' + }, + { + valueKey: 'apgar5', + type: 'numberAndComparator', + label: 'Score Apgar - 5 min' + }, + { + valueKey: 'apgar10', + type: 'numberAndComparator', + label: 'Score Apgar - 10 min' + }, + { + valueKey: 'arterialPhCord', + type: 'numberAndComparator', + floatValues: true, + label: 'pH artériel au cordon' + }, + { + valueKey: 'arterialCordLactates', + type: 'numberAndComparator', + floatValues: true, + label: 'Lactate artériel au cordon (mmol/L)' + }, + { + valueKey: 'birthStatus', + type: 'autocomplete', + label: 'Statut vital à la naissance', + noOptionsText: 'Veuillez entrer un statut vital à la naissance', + valueSetId: getConfig().features.questionnaires.valueSets.birthStatus.url, + valueSetData: getConfig().features.questionnaires.valueSets.birthStatus.data + }, + { + valueKey: 'postpartumHemorrhage', + type: 'autocomplete', + label: 'Hémorragie du post-partum', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanOpenChoiceFields.data + }, + { + valueKey: 'conditionPerineum', + type: 'autocomplete', + label: 'État du périnée', + noOptionsText: 'Veuillez entrer un état du périnée', + valueSetId: getConfig().features.questionnaires.valueSets.conditionPerineum.url + }, + { + valueKey: 'exitPlaceType', + type: 'autocomplete', + label: 'Type de lieu de sortie', + noOptionsText: 'Veuillez entrer un type de lieu de sortie', + valueSetId: getConfig().features.questionnaires.valueSets.exitPlaceType.url + } + ] + }, + { + title: 'SUITES DE COUCHES', + defaulCollapsed: true, + items: [ + { + valueKey: 'feedingType', + type: 'autocomplete', + label: "Type d'allaitement", + noOptionsText: "Veuillez entrer un type d'allaitement", + valueSetId: getConfig().features.questionnaires.valueSets.feedingType.url + }, + { + valueKey: 'complication', + type: 'autocomplete', + label: 'Aucune complication', + noOptionsText: 'Veuillez entrer "oui" ou "non"', + valueSetId: getConfig().features.questionnaires.valueSets.booleanFields.url, + valueSetData: getConfig().features.questionnaires.valueSets.booleanFields.data + } + ] + }, + { + title: 'SORTIE', + defaulCollapsed: true, + items: [ + { + valueKey: 'exitFeedingMode', + type: 'autocomplete', + label: "Mode d'allaitement à la sortie", + noOptionsText: "Veuillez entrer un mode d'allaitement à la sortie", + valueSetId: getConfig().features.questionnaires.valueSets.exitFeedingMode.url + }, + { + valueKey: 'exitDiagnostic', + type: 'autocomplete', + label: 'Diagnostic de sortie', + noOptionsText: 'Veuillez entrer un diagnostic de sortie', + valueSetId: getConfig().features.questionnaires.valueSets.exitDiagnostic.url + } + ] + } + ] +}) + +export default withLegacyAdapter({ + form, + adapter: { + mapFromLegacyDataType: (legacyData: HospitDataType, criteriaDataCache: CriteriaItemDataCache) => ({ + id: legacyData.id, + type: CriteriaType.HOSPIT, + title: legacyData.title, + isInclusive: legacyData.isInclusive || true, + occurrence: { + value: legacyData.occurrence || 1, + comparator: legacyData.occurrenceComparator || Comparators.GREATER_OR_EQUAL + }, + encounterService: legacyData.encounterService || null, + encounterStatus: legacyData.encounterStatus, + startOccurrence: mapToNewDurationRange(legacyData.startOccurrence), + hospitReason: legacyData.hospitReason || '', + inUteroTransfer: mappingLabelObject( + legacyData.inUteroTransfer, + CriteriaDataKey.IN_UTERO_TRANSFER, + criteriaDataCache + ), + pregnancyMonitoring: mappingLabelObject( + legacyData.pregnancyMonitoring, + CriteriaDataKey.PREGNANCY_MONITORING, + criteriaDataCache + ), + vme: mappingLabelObject(legacyData.vme, CriteriaDataKey.VME, criteriaDataCache), + maturationCorticotherapie: mappingLabelObject( + legacyData.maturationCorticotherapie, + CriteriaDataKey.MATURATION_CORTICOTHERAPIE, + criteriaDataCache + ), + chirurgicalGesture: mappingLabelObject( + legacyData.chirurgicalGesture, + CriteriaDataKey.CHIRURGICAL_GESTURE, + criteriaDataCache + ), + childbirth: mappingLabelObject(legacyData.childbirth, CriteriaDataKey.CHILDBIRTH, criteriaDataCache), + hospitalChildBirthPlace: mappingLabelObject( + legacyData.hospitalChildBirthPlace, + CriteriaDataKey.HOSPITALCHILDBIRTHPLACE, + criteriaDataCache + ), + otherHospitalChildBirthPlace: mappingLabelObject( + legacyData.otherHospitalChildBirthPlace, + CriteriaDataKey.OTHERHOSPITALCHILDBIRTHPLACE, + criteriaDataCache + ), + homeChildBirthPlace: mappingLabelObject( + legacyData.homeChildBirthPlace, + CriteriaDataKey.HOMECHILDBIRTHPLACE, + criteriaDataCache + ), + childbirthMode: mappingLabelObject(legacyData.childbirthMode, CriteriaDataKey.CHILDBIRTH_MODE, criteriaDataCache), + maturationReason: mappingLabelObject( + legacyData.maturationReason, + CriteriaDataKey.MATURATION_REASON, + criteriaDataCache + ), + maturationModality: mappingLabelObject( + legacyData.maturationModality, + CriteriaDataKey.MATURATION_MODALITY, + criteriaDataCache + ), + imgIndication: mappingLabelObject(legacyData.imgIndication, CriteriaDataKey.IMG_INDICATION, criteriaDataCache), + laborOrCesareanEntry: mappingLabelObject( + legacyData.laborOrCesareanEntry, + CriteriaDataKey.LABOR_OR_CESAREAN_ENTRY, + criteriaDataCache + ), + pathologyDuringLabor: mappingLabelObject( + legacyData.pathologyDuringLabor, + CriteriaDataKey.PATHOLOGY_DURING_LABOR, + criteriaDataCache + ), + obstetricalGestureDuringLabor: mappingLabelObject( + legacyData.obstetricalGestureDuringLabor, + CriteriaDataKey.OBSTETRICAL_GESTURE_DURING_LABOR, + criteriaDataCache + ), + analgesieType: mappingLabelObject(legacyData.analgesieType, CriteriaDataKey.ANALGESIE_TYPE, criteriaDataCache), + birthDeliveryDate: { + start: legacyData.birthDeliveryStartDate || null, + end: legacyData.birthDeliveryEndDate || null, + includeNull: false + }, + birthDeliveryWeeks: { + value: legacyData.birthDeliveryWeeks, + comparator: legacyData.birthDeliveryWeeksComparator || Comparators.GREATER_OR_EQUAL + }, + birthDeliveryDays: { + value: legacyData.birthDeliveryDays, + comparator: legacyData.birthDeliveryDaysComparator || Comparators.GREATER_OR_EQUAL + }, + birthDeliveryWay: mappingLabelObject( + legacyData.birthDeliveryWay, + CriteriaDataKey.BIRTH_DELIVERY_WAY, + criteriaDataCache + ), + instrumentType: mappingLabelObject(legacyData.instrumentType, CriteriaDataKey.INSTRUMENT_TYPE, criteriaDataCache), + cSectionModality: mappingLabelObject( + legacyData.cSectionModality, + CriteriaDataKey.C_SECTION_MODALITY, + criteriaDataCache + ), + presentationAtDelivery: mappingLabelObject( + legacyData.presentationAtDelivery, + CriteriaDataKey.PRESENTATION_AT_DELIVERY, + criteriaDataCache + ), + birthMensurationsGrams: { + value: legacyData.birthMensurationsGrams, + comparator: legacyData.birthMensurationsGramsComparator || Comparators.GREATER_OR_EQUAL + }, + birthMensurationsPercentil: { + value: legacyData.birthMensurationsPercentil, + comparator: legacyData.birthMensurationsPercentilComparator || Comparators.GREATER_OR_EQUAL + }, + apgar1: { + value: legacyData.apgar1, + comparator: legacyData.apgar1Comparator || Comparators.GREATER_OR_EQUAL + }, + apgar3: { + value: legacyData.apgar3, + comparator: legacyData.apgar3Comparator || Comparators.GREATER_OR_EQUAL + }, + apgar5: { + value: legacyData.apgar5, + comparator: legacyData.apgar5Comparator || Comparators.GREATER_OR_EQUAL + }, + apgar10: { + value: legacyData.apgar10, + comparator: legacyData.apgar10Comparator || Comparators.GREATER_OR_EQUAL + }, + arterialPhCord: { + value: legacyData.arterialPhCord, + comparator: legacyData.arterialPhCordComparator || Comparators.GREATER_OR_EQUAL + }, + arterialCordLactates: { + value: legacyData.arterialCordLactates, + comparator: legacyData.arterialCordLactatesComparator || Comparators.GREATER_OR_EQUAL + }, + birthStatus: mappingLabelObject(legacyData.birthStatus, CriteriaDataKey.BIRTHSTATUS, criteriaDataCache), + postpartumHemorrhage: mappingLabelObject( + legacyData.postpartumHemorrhage, + CriteriaDataKey.POSTPARTUM_HEMORRHAGE, + criteriaDataCache + ), + conditionPerineum: mappingLabelObject( + legacyData.conditionPerineum, + CriteriaDataKey.CONDITION_PERINEUM, + criteriaDataCache + ), + exitPlaceType: mappingLabelObject(legacyData.exitPlaceType, CriteriaDataKey.EXIT_PLACE_TYPE, criteriaDataCache), + feedingType: mappingLabelObject(legacyData.feedingType, CriteriaDataKey.FEEDING_TYPE, criteriaDataCache), + complication: mappingLabelObject(legacyData.complication, CriteriaDataKey.COMPLICATION, criteriaDataCache), + exitFeedingMode: mappingLabelObject( + legacyData.exitFeedingMode, + CriteriaDataKey.EXIT_FEEDING_MODE, + criteriaDataCache + ), + exitDiagnostic: mappingLabelObject(legacyData.exitDiagnostic, CriteriaDataKey.EXIT_DIAGNOSTIC, criteriaDataCache) + }), + mapToLegacyDataType: (data: NewHospitDataType) => ({ + id: data.id, + type: CriteriaType.HOSPIT, + title: data.title, + isInclusive: data.isInclusive, + occurrence: data.occurrence.value, + startOccurrence: [data.startOccurrence?.start, data.startOccurrence?.end], + occurrenceComparator: data.occurrence.comparator, + encounterStatus: data.encounterStatus, + encounterService: data.encounterService || undefined, + hospitReason: data.hospitReason, + inUteroTransfer: data.inUteroTransfer, + pregnancyMonitoring: data.pregnancyMonitoring, + vme: data.vme, + maturationCorticotherapie: data.maturationCorticotherapie, + chirurgicalGesture: data.chirurgicalGesture, + childbirth: data.childbirth, + hospitalChildBirthPlace: data.hospitalChildBirthPlace, + otherHospitalChildBirthPlace: data.otherHospitalChildBirthPlace, + homeChildBirthPlace: data.homeChildBirthPlace, + childbirthMode: data.childbirthMode, + maturationReason: data.maturationReason, + maturationModality: data.maturationModality, + imgIndication: data.imgIndication, + laborOrCesareanEntry: data.laborOrCesareanEntry, + pathologyDuringLabor: data.pathologyDuringLabor, + obstetricalGestureDuringLabor: data.obstetricalGestureDuringLabor, + analgesieType: data.analgesieType, + birthDeliveryStartDate: data.birthDeliveryDate?.start, + birthDeliveryEndDate: data.birthDeliveryDate?.end, + birthDeliveryWeeks: data.birthDeliveryWeeks?.value, + birthDeliveryWeeksComparator: data.birthDeliveryWeeks?.comparator, + birthDeliveryDays: data.birthDeliveryDays?.value, + birthDeliveryDaysComparator: data.birthDeliveryDays?.comparator, + birthDeliveryWay: data.birthDeliveryWay, + instrumentType: data.instrumentType, + cSectionModality: data.cSectionModality, + presentationAtDelivery: data.presentationAtDelivery, + birthMensurationsGrams: data.birthMensurationsGrams?.value, + birthMensurationsGramsComparator: data.birthMensurationsGrams?.comparator, + birthMensurationsPercentil: data.birthMensurationsPercentil?.value, + birthMensurationsPercentilComparator: data.birthMensurationsPercentil?.comparator, + apgar1: data.apgar1?.value, + apgar1Comparator: data.apgar1?.comparator, + apgar3: data.apgar3?.value, + apgar3Comparator: data.apgar3?.comparator, + apgar5: data.apgar5?.value, + apgar5Comparator: data.apgar5?.comparator, + apgar10: data.apgar10?.value, + apgar10Comparator: data.apgar10?.comparator, + arterialPhCord: data.arterialPhCord?.value, + arterialPhCordComparator: data.arterialPhCord?.comparator, + arterialCordLactates: data.arterialCordLactates?.value, + arterialCordLactatesComparator: data.arterialCordLactates?.comparator, + birthStatus: data.birthStatus, + postpartumHemorrhage: data.postpartumHemorrhage, + conditionPerineum: data.conditionPerineum, + exitPlaceType: data.exitPlaceType, + feedingType: data.feedingType, + complication: data.complication, + exitFeedingMode: data.exitFeedingMode, + exitDiagnostic: data.exitDiagnostic + }) + } +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/index.tsx deleted file mode 100644 index e58df627a..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/index.tsx +++ /dev/null @@ -1,925 +0,0 @@ -/* eslint-disable max-statements */ -import React, { useEffect, useState } from 'react' - -import { Autocomplete, TextField } from '@mui/material' - -import useStyles from './styles' - -import { CriteriaDrawerComponentProps, ScopeElement } from 'types' -import { LabelObject } from 'types/searchCriterias' -import { Comparators, CriteriaDataKey, HospitDataType, CriteriaType, CriteriaTypeLabels } from 'types/requestCriterias' -import { BlockWrapper } from 'components/ui/Layout' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import Collapse from 'components/ui/Collapse' -import SearchbarWithCheck from 'components/ui/Inputs/SearchbarWithCheck' -import { mappingCriteria } from '../DemographicForm' -import CriteriaLayout from 'components/ui/CriteriaLayout' -import CalendarRange from 'components/ui/Inputs/CalendarRange' -import NumericConditionInput from 'components/ui/Inputs/OccurencesWithFloats' -import { SourceType } from 'types/scope' -import { Hierarchy } from 'types/hierarchy' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' -import ExecutiveUnitsInput from 'components/ui/Inputs/ExecutiveUnit' - -enum Error { - EMPTY_FORM, - EMPTY_DURATION_ERROR, - INCOHERENT_AGE_ERROR, - SEARCHINPUT_ERROR, - NO_ERROR -} - -const HospitForm = ({ - criteriaData, - selectedCriteria, - goBack, - onChangeSelectedCriteria -}: CriteriaDrawerComponentProps) => { - const criteria = selectedCriteria as HospitDataType - const [title, setTitle] = useState(criteria?.title || "Critère de Fiche d'hospitalisation") - const [hospitReason, setHospitReason] = useState(criteria?.hospitReason || '') - const [inUteroTransfer, setInUteroTransfer] = useState( - mappingCriteria(criteria?.inUteroTransfer, CriteriaDataKey.IN_UTERO_TRANSFER, criteriaData) || [] - ) - const [pregnancyMonitoring, setPregnancyMonitoring] = useState( - mappingCriteria(criteria?.pregnancyMonitoring, CriteriaDataKey.PREGNANCY_MONITORING, criteriaData) || [] - ) - const [vme, setVme] = useState(mappingCriteria(criteria?.vme, CriteriaDataKey.VME, criteriaData) || []) - const [maturationCorticotherapie, setMaturationCorticotherapie] = useState( - mappingCriteria(criteria?.maturationCorticotherapie, CriteriaDataKey.MATURATION_CORTICOTHERAPIE, criteriaData) || [] - ) - const [chirurgicalGesture, setChirurgicalGesture] = useState( - mappingCriteria(criteria?.chirurgicalGesture, CriteriaDataKey.CHIRURGICAL_GESTURE, criteriaData) || [] - ) - const [childbirth, setChildbirth] = useState( - mappingCriteria(criteria?.childbirth, CriteriaDataKey.CHILDBIRTH, criteriaData) || [] - ) - const [hospitalChildBirthPlace, setHospitalChildBirthPlace] = useState( - mappingCriteria(criteria?.hospitalChildBirthPlace, CriteriaDataKey.HOSPITALCHILDBIRTHPLACE, criteriaData) - ) - const [otherHospitalChildBirthPlace, setOtherHospitalChildBirthPlace] = useState( - mappingCriteria(criteria?.otherHospitalChildBirthPlace, CriteriaDataKey.OTHERHOSPITALCHILDBIRTHPLACE, criteriaData) - ) - const [homeChildBirthPlace, setHomeChildBirthPlace] = useState( - mappingCriteria(criteria?.homeChildBirthPlace, CriteriaDataKey.HOMECHILDBIRTHPLACE, criteriaData) - ) - const [childbirthMode, setChildbirthMode] = useState( - mappingCriteria(criteria?.childbirthMode, CriteriaDataKey.CHILDBIRTH_MODE, criteriaData) || [] - ) - const [maturationReason, setMaturationReason] = useState( - mappingCriteria(criteria?.maturationReason, CriteriaDataKey.MATURATION_REASON, criteriaData) || [] - ) - const [maturationModality, setMaturationModality] = useState( - mappingCriteria(criteria?.maturationModality, CriteriaDataKey.MATURATION_MODALITY, criteriaData) || [] - ) - const [imgIndication, setImgIndication] = useState( - mappingCriteria(criteria?.imgIndication, CriteriaDataKey.IMG_INDICATION, criteriaData) || [] - ) - const [laborOrCesareanEntry, setLaborOrCesareanEntry] = useState( - mappingCriteria(criteria?.laborOrCesareanEntry, CriteriaDataKey.LABOR_OR_CESAREAN_ENTRY, criteriaData) || [] - ) - const [pathologyDuringLabor, setPathologyDuringLabor] = useState( - mappingCriteria(criteria?.pathologyDuringLabor, CriteriaDataKey.PATHOLOGY_DURING_LABOR, criteriaData) || [] - ) - const [obstetricalGestureDuringLabor, setObstetricalGestureDuringLabor] = useState( - mappingCriteria( - criteria?.obstetricalGestureDuringLabor, - CriteriaDataKey.OBSTETRICAL_GESTURE_DURING_LABOR, - criteriaData - ) || [] - ) - const [analgesieType, setAnalgesieType] = useState( - mappingCriteria(criteria?.analgesieType, CriteriaDataKey.ANALGESIE_TYPE, criteriaData) || [] - ) - const [birthDeliveryStartDate, setBirthDeliveryStartDate] = useState( - criteria?.birthDeliveryStartDate || null - ) - const [birthDeliveryEndDate, setBirthDeliveryEndDate] = useState( - criteria?.birthDeliveryEndDate || null - ) - const [birthDeliveryWeeks, setBirthDeliveryWeeks] = useState(criteria?.birthDeliveryWeeks || 0) - const [birthDeliveryWeeksComparator, setBirthDeliveryWeeksComparator] = useState( - criteria?.birthDeliveryWeeksComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthDeliveryDays, setBirthDeliveryDays] = useState(criteria?.birthDeliveryDays || 0) - const [birthDeliveryDaysComparator, setBirthDeliveryDaysComparator] = useState( - criteria?.birthDeliveryDaysComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthDeliveryWay, setBirthDeliveryWay] = useState( - mappingCriteria(criteria?.birthDeliveryWay, CriteriaDataKey.BIRTH_DELIVERY_WAY, criteriaData) || [] - ) - const [instrumentType, setInstrumentType] = useState( - mappingCriteria(criteria?.instrumentType, CriteriaDataKey.INSTRUMENT_TYPE, criteriaData) || [] - ) - const [cSectionModality, setCSectionModality] = useState( - mappingCriteria(criteria?.cSectionModality, CriteriaDataKey.C_SECTION_MODALITY, criteriaData) || [] - ) - const [presentationAtDelivery, setPresentationAtDelivery] = useState( - mappingCriteria(criteria?.presentationAtDelivery, CriteriaDataKey.PRESENTATION_AT_DELIVERY, criteriaData) || [] - ) - const [birthMensurationsGrams, setBirthMensurationsGrams] = useState(criteria?.birthMensurationsGrams || 0) - const [birthMensurationsGramsComparator, setBirthMensurationsGramsComparator] = useState( - criteria?.birthMensurationsGramsComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthMensurationsPercentil, setBirthMensurationsPercentil] = useState( - criteria?.birthMensurationsPercentil || 0 - ) - const [birthMensurationsPercentilComparator, setBirthMensurationsPercentilComparator] = useState( - criteria?.birthMensurationsPercentilComparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar1, setApgar1] = useState(criteria?.apgar1 || 0) - const [apgar1Comparator, setApgar1Comparator] = useState( - criteria?.apgar1Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar3, setApgar3] = useState(criteria?.apgar3 || 0) - const [apgar3Comparator, setApgar3Comparator] = useState( - criteria?.apgar3Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar5, setApgar5] = useState(criteria?.apgar5 || 0) - const [apgar5Comparator, setApgar5Comparator] = useState( - criteria?.apgar5Comparator || Comparators.GREATER_OR_EQUAL - ) - const [apgar10, setApgar10] = useState(criteria?.apgar10 || 0) - const [apgar10Comparator, setApgar10Comparator] = useState( - criteria?.apgar10Comparator || Comparators.GREATER_OR_EQUAL - ) - const [arterialPhCord, setArterialPhCord] = useState(criteria?.arterialPhCord || 0) - const [arterialPhCordComparator, setArterialPhCordComparator] = useState( - criteria?.arterialPhCordComparator || Comparators.GREATER_OR_EQUAL - ) - const [arterialCordLactates, setArterialCordLactates] = useState(criteria?.arterialCordLactates || 0) - const [arterialCordLactatesComparator, setArterialCordLactatesComparator] = useState( - criteria?.arterialCordLactatesComparator || Comparators.GREATER_OR_EQUAL - ) - const [birthStatus, setBirthStatus] = useState( - mappingCriteria(criteria?.birthStatus, CriteriaDataKey.BIRTHSTATUS, criteriaData) || [] - ) - const [postpartumHemorrhage, setPostpartumHemorrhage] = useState( - mappingCriteria(criteria?.postpartumHemorrhage, CriteriaDataKey.POSTPARTUM_HEMORRHAGE, criteriaData) || [] - ) - const [conditionPerineum, setConditionPerineum] = useState( - mappingCriteria(criteria?.conditionPerineum, CriteriaDataKey.CONDITION_PERINEUM, criteriaData) || [] - ) - const [exitPlaceType, setExitPlaceType] = useState( - mappingCriteria(criteria?.exitPlaceType, CriteriaDataKey.EXIT_PLACE_TYPE, criteriaData) || [] - ) - const [feedingType, setFeedingType] = useState( - mappingCriteria(criteria?.feedingType, CriteriaDataKey.FEEDING_TYPE, criteriaData) || [] - ) - const [complication, setComplication] = useState( - mappingCriteria(criteria?.complication, CriteriaDataKey.COMPLICATION, criteriaData) || [] - ) - const [exitFeedingMode, setExitFeedingMode] = useState( - mappingCriteria(criteria?.exitFeedingMode, CriteriaDataKey.EXIT_FEEDING_MODE, criteriaData) || [] - ) - const [exitDiagnostic, setExitDiagnostic] = useState( - mappingCriteria(criteria?.exitDiagnostic, CriteriaDataKey.EXIT_DIAGNOSTIC, criteriaData) || [] - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(criteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - const [encounterService, setEncounterService] = useState[]>( - criteria?.encounterService || [] - ) - const [occurrence, setOccurrence] = useState(criteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - criteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [isInclusive, setIsInclusive] = useState(criteria?.isInclusive || true) - - const { classes } = useStyles() - const isEdition = selectedCriteria !== null ? true : false - const [error, setError] = useState(Error.NO_ERROR) - - useEffect(() => { - setError(Error.NO_ERROR) - if ( - (occurrence === 0 && occurrenceComparator === Comparators.EQUAL) || - (occurrence === 1 && occurrenceComparator === Comparators.LESS) || - (occurrence === 0 && occurrenceComparator === Comparators.LESS_OR_EQUAL) - ) { - setError(Error.EMPTY_FORM) - } - }, [occurrence, occurrenceComparator]) - - const onSubmit = () => { - onChangeSelectedCriteria({ - type: CriteriaType.HOSPIT, - id: criteria?.id, - hospitReason, - inUteroTransfer, - pregnancyMonitoring, - vme, - maturationCorticotherapie, - chirurgicalGesture, - childbirth, - hospitalChildBirthPlace, - otherHospitalChildBirthPlace, - homeChildBirthPlace, - childbirthMode, - maturationReason, - maturationModality, - imgIndication, - laborOrCesareanEntry, - pathologyDuringLabor, - obstetricalGestureDuringLabor, - analgesieType, - birthDeliveryStartDate, - birthDeliveryEndDate, - birthDeliveryWeeks, - birthDeliveryWeeksComparator, - birthDeliveryDays, - birthDeliveryDaysComparator, - birthDeliveryWay, - instrumentType, - cSectionModality, - presentationAtDelivery, - birthMensurationsGrams, - birthMensurationsGramsComparator, - birthMensurationsPercentil, - birthMensurationsPercentilComparator, - apgar1, - apgar1Comparator, - apgar3, - apgar3Comparator, - apgar5, - apgar5Comparator, - apgar10, - apgar10Comparator, - arterialPhCord, - arterialPhCordComparator, - arterialCordLactates, - arterialCordLactatesComparator, - birthStatus, - postpartumHemorrhage, - conditionPerineum, - exitPlaceType, - feedingType, - complication, - exitFeedingMode, - exitDiagnostic, - encounterService, - occurrence, - occurrenceComparator, - encounterStatus, - isInclusive, - startOccurrence: [null, null], - title - }) - } - return ( - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } - /> - - - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={inUteroTransfer} - onChange={(e, value) => setInUteroTransfer(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={pregnancyMonitoring} - onChange={(e, value) => setPregnancyMonitoring(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={vme} - onChange={(e, value) => setVme(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationCorticotherapie} - onChange={(e, value) => setMaturationCorticotherapie(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={chirurgicalGesture} - onChange={(e, value) => setChirurgicalGesture(value)} - renderInput={(params) => } - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={childbirth} - onChange={(e, value) => setChildbirth(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={hospitalChildBirthPlace} - onChange={(e, value) => setHospitalChildBirthPlace(value)} - renderInput={(params) => ( - - )} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={otherHospitalChildBirthPlace} - onChange={(e, value) => setOtherHospitalChildBirthPlace(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={homeChildBirthPlace} - onChange={(e, value) => setHomeChildBirthPlace(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={childbirthMode} - onChange={(e, value) => setChildbirthMode(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationReason} - onChange={(e, value) => setMaturationReason(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={maturationModality} - onChange={(e, value) => setMaturationModality(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={imgIndication} - onChange={(e, value) => setImgIndication(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={laborOrCesareanEntry} - onChange={(e, value) => setLaborOrCesareanEntry(value)} - renderInput={(params) => ( - - )} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={pathologyDuringLabor} - onChange={(e, value) => setPathologyDuringLabor(value)} - renderInput={(params) => } - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={obstetricalGestureDuringLabor} - onChange={(e, value) => setObstetricalGestureDuringLabor(value)} - renderInput={(params) => ( - - )} - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={analgesieType} - onChange={(e, value) => setAnalgesieType(value)} - renderInput={(params) => } - /> - - - - - - - - { - setBirthDeliveryStartDate(start) - setBirthDeliveryEndDate(end) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - - { - setBirthDeliveryWeeks(newBirthdaydeliveryWeeks) - setBirthDeliveryWeeksComparator(newComparator) - }} - /> - - - - { - setBirthDeliveryDays(newbirthDeliveryDays) - setBirthDeliveryDaysComparator(newComparator) - }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={birthDeliveryWay} - onChange={(e, value) => setBirthDeliveryWay(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={instrumentType} - onChange={(e, value) => setInstrumentType(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={cSectionModality} - onChange={(e, value) => setCSectionModality(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={presentationAtDelivery} - onChange={(e, value) => setPresentationAtDelivery(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - { - setBirthMensurationsGrams(newBirthMensurations) - setBirthMensurationsGramsComparator(newComparator) - }} - /> - - - - - { - setBirthMensurationsPercentil(newbirthMensurationsPercentil) - setBirthMensurationsPercentilComparator(newComparator) - }} - /> - - - - { - setApgar1(newapgar1) - setApgar1Comparator(newComparator) - }} - /> - - - - { - setApgar3(newapgar3) - setApgar3Comparator(newComparator) - }} - /> - - - - { - setApgar5(newapgar5) - setApgar5Comparator(newComparator) - }} - /> - - - - { - setApgar10(newapgar10) - setApgar10Comparator(newComparator) - }} - /> - - - - - { - setArterialPhCord(newarterialPhCord) - setArterialPhCordComparator(newComparator) - }} - /> - - - - - { - setArterialCordLactates(newarterialCordLactates) - setArterialCordLactatesComparator(newComparator) - }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={birthStatus} - onChange={(e, value) => setBirthStatus(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={postpartumHemorrhage} - onChange={(e, value) => setPostpartumHemorrhage(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={conditionPerineum} - onChange={(e, value) => setConditionPerineum(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitPlaceType} - onChange={(e, value) => setExitPlaceType(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={feedingType} - onChange={(e, value) => setFeedingType(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={complication} - onChange={(e, value) => setComplication(value)} - renderInput={(params) => } - /> - - - - - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitFeedingMode} - onChange={(e, value) => setExitFeedingMode(value)} - renderInput={(params) => } - /> - - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={exitDiagnostic} - onChange={(e, value) => setExitDiagnostic(value)} - renderInput={(params) => } - /> - - - - ) -} - -export default HospitForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts deleted file mode 100644 index 84150bde6..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm/styles.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - inputItem: { - margin: '1em' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm.tsx new file mode 100644 index 000000000..f58fec89a --- /dev/null +++ b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm.tsx @@ -0,0 +1,307 @@ +import { Comparators, CriteriaDataKey, CriteriaType, ImagingDataType } from 'types/requestCriterias' +import withLegacyAdapter from './CriteriaForm/legacyFormAdapter' +import { + CriteriaData, + CriteriaForm, + NewDurationRangeType, + NumberAndComparatorDataType, + WithEncounterDateDataType, + WithEncounterStatusDataType +} from './CriteriaForm/types' +import { DocumentAttachmentMethod, LabelObject } from 'types/searchCriterias' +import { getConfig } from 'config' +import { CriteriaItemDataCache } from 'types' +import { mapToNewDurationRange, mappingLabelObject } from './CriteriaForm/utils' +import { SourceType } from 'types/scope' + +export type NewImagingType = CriteriaData & + WithEncounterDateDataType & + WithEncounterStatusDataType & { + studyDate: NewDurationRangeType | null + studyModalities: LabelObject[] | null + studyDescription: string + studyProcedure: string + numberOfSeries: NumberAndComparatorDataType + numberOfIns: NumberAndComparatorDataType + withDocument: LabelObject[] + daysOfDelay: string | null + studyUid: string + seriesDate: NewDurationRangeType | null + seriesDescription: string + seriesProtocol: string + seriesModalities: LabelObject[] | null + seriesUid: string + } + +export const form: () => CriteriaForm = () => ({ + label: "d'Imagerie", + initialData: { + title: "Critère d'Imagerie", + type: CriteriaType.IMAGING, + isInclusive: true, + occurrence: { + value: 1, + comparator: Comparators.GREATER_OR_EQUAL + }, + encounterService: null, + encounterStatus: [], + encounterStartDate: null, + encounterEndDate: null, + studyDate: null, + studyModalities: [], + studyDescription: '', + studyProcedure: '', + numberOfSeries: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + numberOfIns: { value: 0, comparator: Comparators.GREATER_OR_EQUAL }, + withDocument: [{ id: 'NONE', label: 'Aucun' }], + daysOfDelay: null, + studyUid: '', + seriesDate: null, + seriesDescription: '', + seriesProtocol: '', + seriesModalities: [], + seriesUid: '' + }, + errorMessages: { + INCOHERENT_AGE_ERROR: "Erreur de cohérence d'âge", + SEARCHINPUT_ERROR: 'Erreur de saisie de recherche', + UID_ERROR: 'Erreur de UID', + ADVANCED_INPUTS_ERROR: "Erreur d'entrées avancées" + }, + itemSections: [ + { + items: [ + { + valueKey: 'occurrence', + type: 'numberAndComparator', + label: "Nombre d'occurrences", + withHierarchyInfo: true + }, + { + valueKey: 'encounterStatus', + type: 'autocomplete', + label: 'Statut de la visite associée', + valueSetId: getConfig().core.valueSets.encounterStatus.url, + noOptionsText: 'Veuillez entrer un statut de visite associée' + } + ] + }, + { + title: 'Critères liés à une étude', + defaulCollapsed: true, + items: [ + { + valueKey: 'studyDate', + type: 'duration', + label: "Date de l'étude", + errorType: 'INCOHERENT_AGE_ERROR' + }, + { + valueKey: 'studyModalities', + type: 'autocomplete', + label: 'Modalités', + valueSetId: getConfig().features.imaging.valueSets.imagingModalities.url, + noOptionsText: 'Veuillez entrer des modalités' + }, + { + valueKey: 'studyDescription', + type: 'textWithCheck', + label: 'Rechercher dans les descriptions', + placeholder: 'Rechercher dans les descriptions', + errorType: 'SEARCHINPUT_ERROR' + }, + { + valueKey: 'studyProcedure', + type: 'textWithCheck', + label: 'Rechercher dans les codes procédures', + placeholder: 'Rechercher dans les codes procédures', + errorType: 'SEARCHINPUT_ERROR' + }, + { + valueKey: 'numberOfSeries', + type: 'numberAndComparator', + label: 'Nombre de séries', + withHierarchyInfo: true + }, + { + valueKey: 'numberOfIns', + type: 'numberAndComparator', + label: "Nombre d'instances", + withHierarchyInfo: true + }, + { + valueKey: 'withDocument', + type: 'autocomplete', + label: 'Méthode de rattachement à un document', + valueSetId: 'documentAttachementMethod', + singleChoice: true, + valueSetData: getConfig().features.imaging.valueSets.documentAttachementMethod.data, + noOptionsText: 'Veuillez entrer une méthode de rattachement' + }, + { + valueKey: 'daysOfDelay', + type: 'number', + label: 'Plage de jours', + errorType: 'INCOHERENT_AGE_ERROR', + displayCondition: (data) => + ((data as NewImagingType).withDocument as LabelObject[])?.at(0)?.id === 'INFERENCE_TEMPOREL' + }, + { + valueKey: 'studyUid', + type: 'textWithRegex', + label: "Recherche par uid d'étude", + regex: '[^0-9.,]', + checkErrorMessage: 'Seuls les chiffres, points, ou les virgules sont autorisés.', + placeholder: "Ajouter une liste d'uid séparés par des virgules", + multiline: true + } + ] + }, + { + title: 'Critères liés à une série', + defaulCollapsed: true, + items: [ + { + valueKey: 'seriesDate', + type: 'duration', + label: 'Date de la série', + errorType: 'INCOHERENT_AGE_ERROR' + }, + { + valueKey: 'seriesDescription', + type: 'textWithCheck', + label: 'Rechercher dans les descriptions', + placeholder: 'Rechercher dans les descriptions', + errorType: 'SEARCHINPUT_ERROR' + }, + { + valueKey: 'seriesProtocol', + type: 'textWithCheck', + label: 'Rechercher dans les protocoles', + placeholder: 'Rechercher dans les protocoles', + errorType: 'SEARCHINPUT_ERROR' + }, + { + valueKey: 'seriesModalities', + type: 'autocomplete', + label: 'Modalités', + valueSetId: getConfig().features.imaging.valueSets.imagingModalities.url, + noOptionsText: 'Veuillez entrer des modalités' + }, + { + valueKey: 'seriesUid', + type: 'textWithRegex', + label: 'Recherche par uid de série', + regex: '[^0-9.,]', + checkErrorMessage: 'Seuls les chiffres, points, ou les virgules sont autorisés.', + placeholder: "Ajouter une liste d'uid séparés par des virgules", + multiline: true + } + ] + }, + { + title: 'Options avancées', + defaulCollapsed: true, + items: [ + { + valueKey: 'encounterService', + label: 'Unité exécutrice', + type: 'executiveUnit', + sourceType: SourceType.IMAGING + }, + { + valueKey: 'encounterStartDate', + type: 'duration', + errorType: 'ADVANCED_INPUTS_ERROR', + label: 'Début de prise en charge', + labelAltStyle: true, + extraLabel: 'Prise en charge', + withOptionIncludeNull: true + }, + { + valueKey: 'encounterEndDate', + type: 'duration', + label: 'Fin de prise en charge', + labelAltStyle: true, + info: 'Ne concerne pas les consultations', + errorType: 'ADVANCED_INPUTS_ERROR', + withOptionIncludeNull: true + } + ] + } + ] +}) + +export default withLegacyAdapter({ + form, + adapter: { + mapFromLegacyDataType: (legacyData: ImagingDataType, criteriaDataCache: CriteriaItemDataCache) => ({ + id: legacyData.id, + title: legacyData.title, + type: CriteriaType.IMAGING, + isInclusive: legacyData.isInclusive || true, + occurrence: { + value: legacyData.occurrence || 1, + comparator: legacyData.occurrenceComparator || Comparators.GREATER_OR_EQUAL + }, + encounterService: legacyData.encounterService || null, + encounterStatus: legacyData.encounterStatus, + encounterStartDate: mapToNewDurationRange( + legacyData.encounterStartDate, + legacyData.includeEncounterStartDateNull + ), + encounterEndDate: mapToNewDurationRange(legacyData.encounterEndDate, legacyData.includeEncounterEndDateNull), + startOccurrence: mapToNewDurationRange(legacyData.startOccurrence), + studyDate: { start: legacyData.studyStartDate || null, end: legacyData.studyEndDate || null, includeNull: false }, + studyModalities: mappingLabelObject(legacyData.studyModalities, CriteriaDataKey.MODALITIES, criteriaDataCache), + studyDescription: legacyData.studyDescription, + studyProcedure: legacyData.studyProcedure, + numberOfSeries: { value: legacyData.numberOfSeries, comparator: legacyData.seriesComparator }, + numberOfIns: { value: legacyData.numberOfIns, comparator: legacyData.instancesComparator }, + withDocument: [{ id: legacyData.withDocument, label: legacyData.withDocument }], + daysOfDelay: legacyData.daysOfDelay || null, + studyUid: legacyData.studyUid, + seriesDate: { + start: legacyData.seriesStartDate || null, + end: legacyData.seriesEndDate || null, + includeNull: false + }, + seriesDescription: legacyData.seriesDescription, + seriesProtocol: legacyData.seriesProtocol, + seriesModalities: mappingLabelObject(legacyData.seriesModalities, CriteriaDataKey.MODALITIES, criteriaDataCache), + seriesUid: legacyData.seriesUid + }), + mapToLegacyDataType: (data: NewImagingType) => ({ + type: CriteriaType.IMAGING, + title: data.title, + isInclusive: data.isInclusive, + occurrence: data.occurrence.value, + startOccurrence: [null, null], + occurrenceComparator: data.occurrence.comparator, + encounterStatus: data.encounterStatus, + encounterService: data.encounterService || undefined, + encounterStartDate: [data.encounterStartDate?.start, data.encounterStartDate?.end], + encounterEndDate: [data.encounterEndDate?.start, data.encounterEndDate?.end], + includeEncounterStartDateNull: data.encounterStartDate?.includeNull, + includeEncounterEndDateNull: data.encounterEndDate?.includeNull, + studyStartDate: data.studyDate?.start || null, + studyEndDate: data.studyDate?.end || null, + studyModalities: data.studyModalities, + studyDescription: data.studyDescription, + studyProcedure: data.studyProcedure, + numberOfSeries: data.numberOfSeries.value, + seriesComparator: data.numberOfSeries.comparator, + numberOfIns: data.numberOfIns.value, + instancesComparator: data.numberOfIns.comparator, + withDocument: data.withDocument.at(0)?.id as DocumentAttachmentMethod, + daysOfDelay: data.daysOfDelay, + studyUid: data.studyUid, + seriesStartDate: data.seriesDate?.start || null, + seriesEndDate: data.seriesDate?.end || null, + seriesDescription: data.seriesDescription, + seriesProtocol: data.seriesProtocol, + seriesModalities: data.seriesModalities, + seriesUid: data.seriesUid + }) + } +}) diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx deleted file mode 100644 index d228fcd45..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/index.tsx +++ /dev/null @@ -1,422 +0,0 @@ -import React, { useContext, useState } from 'react' -import { Autocomplete, Grid, MenuItem, Select, TextField } from '@mui/material' -import { BlockWrapper } from 'components/ui/Layout' -import CalendarRange from 'components/ui/Inputs/CalendarRange' -import Collapse from 'components/ui/Collapse' -import { DurationUnitWrapper } from 'components/ui/Inputs/DurationRange/styles' -import { TextFieldWrapper } from 'components/ui/Inputs/DurationRange/styles' -import OccurenceInput from 'components/ui/Inputs/Occurences' -import CriteriaLayout from 'components/ui/CriteriaLayout' -import AdvancedInputs from '../AdvancedInputs/AdvancedInputs' - -import { CriteriaDrawerComponentProps } from 'types' -import { CalendarRequestLabel } from 'types/dates' -import { Comparators, CriteriaDataKey, ImagingDataType, CriteriaType, CriteriaTypeLabels } from 'types/requestCriterias' -import { - DocumentAttachmentMethod, - DocumentAttachmentMethodLabel, - DurationRangeType, - LabelObject -} from 'types/searchCriterias' -import useStyles from './styles' -import { mappingCriteria } from '../DemographicForm' -import SearchbarWithCheck from 'components/ui/Inputs/SearchbarWithCheck' -import UidTextfield from 'components/ui/Inputs/UidTextfield' -import { SourceType } from 'types/scope' -import { CriteriaLabel } from 'components/ui/CriteriaLabel' -import { AppConfig } from 'config' - -enum Error { - INCOHERENT_AGE_ERROR, - SEARCHINPUT_ERROR, - UID_ERROR, - NO_ERROR, - ADVANCED_INPUTS_ERROR -} - -export const withDocumentOptions = [ - { - id: DocumentAttachmentMethod.NONE, - label: DocumentAttachmentMethodLabel.NONE - }, - { - id: DocumentAttachmentMethod.ACCESS_NUMBER, - label: DocumentAttachmentMethodLabel.ACCESS_NUMBER - }, - { - id: DocumentAttachmentMethod.INFERENCE_TEMPOREL, - label: DocumentAttachmentMethodLabel.INFERENCE_TEMPOREL - } -] - -const ImagingForm: React.FC = (props) => { - const { classes } = useStyles() - const appConfig = useContext(AppConfig) - const { criteriaData, onChangeSelectedCriteria, goBack } = props - const selectedCriteria = props.selectedCriteria as ImagingDataType - const isEdition = selectedCriteria !== null ? true : false - const [title, setTitle] = useState(selectedCriteria?.title || "Critère d'Imagerie") - const [occurrence, setOccurrence] = useState(selectedCriteria?.occurrence || 1) - const [occurrenceComparator, setOccurrenceComparator] = useState( - selectedCriteria?.occurrenceComparator || Comparators.GREATER_OR_EQUAL - ) - const [isInclusive, setIsInclusive] = useState( - selectedCriteria?.isInclusive === undefined ? true : selectedCriteria?.isInclusive - ) - const [studyStartDate, setStudyStartDate] = useState(selectedCriteria?.studyStartDate || null) - const [studyEndDate, setStudyEndDate] = useState(selectedCriteria?.studyEndDate || null) - const [studyModalities, setStudyModalities] = useState( - mappingCriteria(selectedCriteria?.studyModalities, CriteriaDataKey.MODALITIES, criteriaData) || [] - ) - const [studyDescription, setStudyDescription] = useState(selectedCriteria?.studyDescription || '') - const [studyProcedure, setStudyProcedure] = useState(selectedCriteria?.studyProcedure || '') - const [numberOfSeries, setNumberOfSeries] = useState(selectedCriteria?.numberOfSeries || 1) - const [seriesComparator, setSeriesComparator] = useState( - selectedCriteria?.seriesComparator || Comparators.GREATER_OR_EQUAL - ) - const [numberOfIns, setNumberOfIns] = useState(selectedCriteria?.numberOfIns || 1) - const [instancesComparator, setInstancesComparator] = useState( - selectedCriteria?.instancesComparator || Comparators.GREATER_OR_EQUAL - ) - const [withDocument, setWithDocument] = useState( - selectedCriteria?.withDocument || DocumentAttachmentMethod.NONE - ) - const [daysOfDelay, setDaysOfDelay] = useState(selectedCriteria?.daysOfDelay || null) - const [studyUid, setStudyUid] = useState(selectedCriteria?.studyUid || '') - - const [seriesStartDate, setSeriesStartDate] = useState(selectedCriteria?.seriesStartDate || null) - const [seriesEndDate, setSeriesEndDate] = useState(selectedCriteria?.seriesEndDate || null) - const [seriesDescription, setSeriesDescription] = useState(selectedCriteria?.seriesDescription || '') - const [seriesProtocol, setSeriesProtocol] = useState(selectedCriteria?.seriesProtocol || '') - const [seriesModalities, setSeriesModalities] = useState( - mappingCriteria(selectedCriteria?.seriesModalities, CriteriaDataKey.MODALITIES, criteriaData) || [] - ) - const [seriesUid, setSeriesUid] = useState(selectedCriteria?.seriesUid || '') - const [encounterService, setEncounterService] = useState(selectedCriteria?.encounterService || []) - const [encounterStartDate, setEncounterStartDate] = useState( - selectedCriteria?.encounterStartDate || [null, null] - ) - const [includeEncounterStartDateNull, setIncludeEncounterStartDateNull] = useState( - selectedCriteria?.includeEncounterStartDateNull - ) - const [encounterEndDate, setEncounterEndDate] = useState( - selectedCriteria?.encounterEndDate || [null, null] - ) - const [includeEncounterEndDateNull, setIncludeEncounterEndDateNull] = useState( - selectedCriteria?.includeEncounterEndDateNull - ) - const [encounterStatus, setEncounterStatus] = useState( - mappingCriteria(selectedCriteria?.encounterStatus, CriteriaDataKey.ENCOUNTER_STATUS, criteriaData) || [] - ) - - const [error, setError] = useState(Error.NO_ERROR) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _onChangeValue = (key: string, value: any) => { - switch (key) { - case 'encounterService': - setEncounterService(value) - break - case 'encounterStartDate': - setEncounterStartDate(value) - break - case 'encounterEndDate': - setEncounterEndDate(value) - break - case 'includeEncounterStartDateNull': - setIncludeEncounterStartDateNull(value) - break - case 'includeEncounterEndDateNull': - setIncludeEncounterEndDateNull(value) - break - default: - break - } - } - - const onSubmit = () => { - onChangeSelectedCriteria({ - id: selectedCriteria?.id, - type: CriteriaType.IMAGING, - title, - isInclusive, - occurrence, - occurrenceComparator, - studyStartDate, - studyEndDate, - studyModalities, - studyDescription, - studyProcedure, - numberOfSeries, - seriesComparator, - numberOfIns, - instancesComparator, - withDocument, - daysOfDelay, - studyUid, - seriesStartDate, - seriesEndDate, - seriesDescription, - seriesProtocol, - seriesModalities, - seriesUid, - encounterService, - encounterStartDate, - includeEncounterStartDateNull, - encounterEndDate, - includeEncounterEndDateNull, - encounterStatus, - startOccurrence: [null, null] - }) - } - - const isSeriesUsed = - !!seriesStartDate || - !!seriesEndDate || - !!seriesDescription || - !!seriesProtocol || - seriesModalities.length > 0 || - !!seriesUid - - return ( - - - { - setOccurrence(newOccurence) - setOccurrenceComparator(newComparator) - }} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={encounterStatus} - onChange={(e, value) => setEncounterStatus(value)} - renderInput={(params) => } - /> - - - {/* critères de study : */} - - - - - { - setStudyStartDate(start || null) - setStudyEndDate(end || null) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={studyModalities} - onChange={(e, value) => setStudyModalities(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - { - setNumberOfSeries(newOccurence) - setSeriesComparator(newComparator) - }} - /> - - - - { - setNumberOfIns(newOccurence) - setInstancesComparator(newComparator) - }} - /> - - - - - - - - - {withDocument === DocumentAttachmentMethod.INFERENCE_TEMPOREL && ( - - - - Plage de - - - setDaysOfDelay(event.target.value)} - type="number" - InputProps={{ - inputProps: { - min: 0 - } - }} - /> - - - {CalendarRequestLabel.DAY} - - - - )} - - - - - setError(isError ? Error.UID_ERROR : Error.NO_ERROR)} - /> - - - {/*critères de série : */} - - - - - { - setSeriesStartDate(start || null) - setSeriesEndDate(end || null) - }} - onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - - setError(isError ? Error.SEARCHINPUT_ERROR : Error.NO_ERROR)} - /> - - - option.label} - isOptionEqualToValue={(option, value) => option.id === value.id} - value={seriesModalities} - onChange={(e, value) => setSeriesModalities(value)} - renderInput={(params) => } - style={{ marginBottom: '1em' }} - /> - - - setError(isError ? Error.UID_ERROR : Error.NO_ERROR)} - /> - - - - - setError(isError ? Error.ADVANCED_INPUTS_ERROR : Error.NO_ERROR)} - onChangeValue={_onChangeValue} - /> - - ) -} - -export default ImagingForm diff --git a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts b/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts deleted file mode 100644 index efecb7e1c..000000000 --- a/src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm/styles.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { makeStyles } from 'tss-react/mui' - -const useStyles = makeStyles()(() => ({ - inputContainer: { - padding: '1em', - display: 'flex', - flex: '1 1 0%', - flexDirection: 'column' - }, - inputItem: { - margin: '1em', - width: 'calc(100% - 2em)' - } -})) - -export default useStyles diff --git a/src/components/CreationCohort/Requeteur.tsx b/src/components/CreationCohort/Requeteur.tsx index c1257e34c..74fd0f20d 100644 --- a/src/components/CreationCohort/Requeteur.tsx +++ b/src/components/CreationCohort/Requeteur.tsx @@ -16,12 +16,13 @@ import { CurrentSnapshot } from 'types' import criteriaList from './DataList_Criteria' -import { getDataFromFetch, cleanNominativeCriterias } from 'utils/cohortCreation' +import { getDataFromFetch, cleanNominativeCriterias, fetchCriteriasCodes } from 'utils/cohortCreation' import useStyles from './styles' import services from 'services/aphp' import { setCriteriaData } from 'state/criteria' import { AppConfig } from 'config' +import { initValueSets, updateCache } from 'state/valueSets' const Requeteur = () => { const { @@ -38,6 +39,7 @@ const Requeteur = () => { allowSearchIpp = false } = useAppSelector((state) => state.cohortCreation.request || {}) const criteriaData = useAppSelector((state) => state.cohortCreation.criteria || {}) + const valueSets = useAppSelector((state) => state.valueSets) const config = useContext(AppConfig) const params = useParams<{ requestId: string @@ -53,6 +55,7 @@ const Requeteur = () => { const [requestLoading, setRequestLoading] = useState(0) const [criteriaLoading, setCriteriaLoading] = useState(0) + const [valueSetsLoading, setValueSetsLoading] = useState(true) const isRendered = useRef(false) const _fetchRequest = useCallback(async () => { @@ -82,6 +85,8 @@ const Requeteur = () => { } try { const criteriaCache = await getDataFromFetch(criteriaList(), selectedCriteria, criteriaData.cache) + const criteriaCodesCache = await fetchCriteriasCodes(criteriaList(), selectedCriteria, valueSets.cache) + dispatch(updateCache(criteriaCodesCache)) const allowMaternityForms = selectedPopulation?.every((population) => population?.access === 'Nominatif') const questionnairesEnabled = config.features.questionnaires.enabled @@ -182,7 +187,17 @@ const Requeteur = () => { return true } - // Initial useEffect + // Initial useEffects + + useEffect(() => { + ;(async () => { + if (!valueSets.loading && !valueSets.loaded) { + console.log('fetching valuesets') + await dispatch(initValueSets(criteriaList())).unwrap() + } + setValueSetsLoading(false) + })() + }, [dispatch, valueSets]) useEffect(() => { _fetchRequest() @@ -198,6 +213,7 @@ const Requeteur = () => { if ( loading || + valueSetsLoading || criteriaLoading != 0 || requestLoading != 0 || (!!requestIdFromUrl && requestId !== requestIdFromUrl) diff --git a/src/components/Filters/DatesRangeFilter/index.tsx b/src/components/Filters/DatesRangeFilter/index.tsx index 5038500ce..634fe213a 100644 --- a/src/components/Filters/DatesRangeFilter/index.tsx +++ b/src/components/Filters/DatesRangeFilter/index.tsx @@ -1,4 +1,5 @@ import CalendarRange from 'components/ui/Inputs/CalendarRange' +import { BlockWrapper } from 'components/ui/Layout' import { FormContext } from 'components/ui/Modal' import React, { useContext, useEffect, useState } from 'react' import { DurationRangeType } from 'types/searchCriterias' @@ -27,16 +28,18 @@ const DatesRangeFilter = ({ names, values, disabled }: DatesRangeFilterProps) => }, [endDate]) return ( - { - setStartDate(value[0]) - setEndDate(value[1]) - }} - onError={onError} - /> + + { + setStartDate(value[0]) + setEndDate(value[1]) + }} + onError={onError} + /> + ) } diff --git a/src/components/Filters/DocStatusFilter/index.tsx b/src/components/Filters/DocStatusFilter/index.tsx index 40de56190..2786b2f72 100644 --- a/src/components/Filters/DocStatusFilter/index.tsx +++ b/src/components/Filters/DocStatusFilter/index.tsx @@ -16,7 +16,7 @@ const DocStatusFilter = ({ name, value, docStatusesList, disabled = false }: Doc useEffect(() => { if (context?.updateFormData) context.updateFormData(name, docStatuses) - }, [docStatuses]) + }, [docStatuses, context, name]) return ( diff --git a/src/components/ui/CriteriaLayout/index.tsx b/src/components/ui/CriteriaLayout/index.tsx index dedd59fc0..9d6915b48 100644 --- a/src/components/ui/CriteriaLayout/index.tsx +++ b/src/components/ui/CriteriaLayout/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { ReactNode } from 'react' import useStyles from './styles' import { Alert, Button, Divider, FormLabel, Grid, IconButton, Switch, TextField, Typography } from '@mui/material' import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace' @@ -14,9 +14,9 @@ type CriteriaLayoutProps = { onChangeTitle: (title: string) => void isInclusive: boolean onChangeIsInclusive: (isInclusive: boolean) => void - infoAlert?: string[] - warningAlert?: string[] - errorAlert?: string[] + infoAlert?: ReactNode[] + warningAlert?: ReactNode[] + errorAlert?: ReactNode[] } const CriteriaLayout: React.FC> = ({ diff --git a/src/components/ui/Inputs/CalendarRange/index.tsx b/src/components/ui/Inputs/CalendarRange/index.tsx index cad7b4f5d..30bfe5abe 100644 --- a/src/components/ui/Inputs/CalendarRange/index.tsx +++ b/src/components/ui/Inputs/CalendarRange/index.tsx @@ -15,7 +15,7 @@ interface CalendarRangeProps { label?: ReactNode inline?: boolean disabled?: boolean - onChange: (newDuration: DurationRangeType) => void + onChange: (newDuration: DurationRangeType, includeNullValues: boolean) => void onError: (isError: boolean) => void includeNullValues?: boolean onChangeIncludeNullValues?: (includeNullValues: boolean) => void @@ -44,7 +44,7 @@ const CalendarRange = ({ setError({ isError: true, errorMessage: 'La date maximale doit être supérieure à la date minimale.' }) onError(true) } else { - onChange([startDate, endDate]) + onChange([startDate, endDate], isNullValuesChecked) if (onChangeIncludeNullValues) { onChangeIncludeNullValues(isNullValuesChecked) } @@ -52,7 +52,7 @@ const CalendarRange = ({ }, [startDate, endDate, isNullValuesChecked]) return ( - + <> {isString(label) ? ( {label} : @@ -97,7 +97,7 @@ const CalendarRange = ({ {error.errorMessage} )} - + ) } diff --git a/src/components/ui/Inputs/UidTextfield/index.tsx b/src/components/ui/Inputs/CheckedTextfield/index.tsx similarity index 70% rename from src/components/ui/Inputs/UidTextfield/index.tsx rename to src/components/ui/Inputs/CheckedTextfield/index.tsx index 75b9174bd..396c63678 100644 --- a/src/components/ui/Inputs/UidTextfield/index.tsx +++ b/src/components/ui/Inputs/CheckedTextfield/index.tsx @@ -3,19 +3,31 @@ import { Grid, InputAdornment, TextField, Typography } from '@mui/material' import WarningIcon from '@mui/icons-material/Warning' import { ErrorWrapper } from 'components/ui/Searchbar/styles' -type UidTextfieldProps = { +type CheckedTextfieldProps = { value: string + regex: string + errorMessage?: string + placeholder?: string + multiline?: boolean onChange: (value: string) => void onError: (isError: boolean) => void } -const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { +const CheckedTextfield = ({ + value, + onChange, + onError, + errorMessage, + regex, + placeholder, + multiline = false +}: CheckedTextfieldProps) => { const [error, setError] = useState(false) useEffect(() => { - const regex = /[^0-9.,]/ //matches everything that isn't a number, a comma or a point + const regexp = new RegExp(regex) // //matches everything that isn't a number, a comma or a point - if (value.match(regex)) { + if (value.match(regexp)) { setError(true) onError(true) } else { @@ -27,11 +39,11 @@ const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { return ( <> onChange(event.target.value)} - multiline minRows={4} style={{ width: '100%' }} error={error} @@ -49,7 +61,7 @@ const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { Des caractères non autorisés ont été détectés dans votre recherche. - Seuls les chiffres, points, ou les virgules sont autorisés. + {errorMessage && {errorMessage}} )} @@ -57,4 +69,4 @@ const UidTextfield = ({ value, onChange, onError }: UidTextfieldProps) => { ) } -export default UidTextfield +export default CheckedTextfield diff --git a/src/components/ui/Inputs/Occurences/index.tsx b/src/components/ui/Inputs/Occurences/index.tsx index dc1dc8dec..08ac8d73e 100644 --- a/src/components/ui/Inputs/Occurences/index.tsx +++ b/src/components/ui/Inputs/Occurences/index.tsx @@ -9,6 +9,7 @@ type OccurenceInputProps = { comparator: Comparators onchange: (value: number, comparator: Comparators) => void enableNegativeValues?: boolean + floatValues?: boolean label?: string withHierarchyInfo?: boolean } @@ -17,26 +18,28 @@ const OccurenceInput = ({ value, comparator, onchange, + floatValues = false, enableNegativeValues = false, label, withHierarchyInfo = false }: OccurenceInputProps) => { - const [occurrenceValue, setOccurrenceValue] = useState(value) + const [occurrenceValue, setOccurrenceValue] = useState(value) const [comparatorValue, setComparatorValue] = useState(comparator) useEffect(() => { - if (!enableNegativeValues && comparatorValue === Comparators.LESS && occurrenceValue === 0) { - setOccurrenceValue(1) - onchange(1, comparatorValue) - } - }, [comparatorValue, occurrenceValue, enableNegativeValues, onchange]) + onchange(occurrenceValue, comparatorValue) + }, [comparatorValue, occurrenceValue]) const handleOccurrenceChange = (e: React.ChangeEvent) => { const newValue = e.target.value === '' ? '0' : e.target.value - if (newValue.match(/^\d+$/) || (enableNegativeValues && newValue === '0' && comparatorValue === Comparators.LESS)) { + if (floatValues && newValue.match(enableNegativeValues ? /^-?\d*\.?\d*$/ : /^\d*\.?\d*$/)) { + setOccurrenceValue(parseFloat(newValue)) + } else if ( + newValue.match(/^\d+$/) || + (enableNegativeValues && newValue === '0' && comparatorValue === Comparators.LESS) + ) { const numericValue = parseInt(newValue, 10) setOccurrenceValue(numericValue) - onchange(numericValue, comparatorValue) } } @@ -45,9 +48,6 @@ const OccurenceInput = ({ setComparatorValue(newComparator) if (!enableNegativeValues && newComparator === Comparators.LESS && occurrenceValue === 0) { setOccurrenceValue(1) - onchange(1, newComparator) - } else { - onchange(occurrenceValue, newComparator) } } diff --git a/src/components/ui/Inputs/OccurencesWithFloats/index.tsx b/src/components/ui/Inputs/OccurencesWithFloats/index.tsx deleted file mode 100644 index 2672c83bb..000000000 --- a/src/components/ui/Inputs/OccurencesWithFloats/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { MenuItem, Select, SelectChangeEvent, TextField } from '@mui/material' -import { Comparators } from 'types/requestCriterias' -import { OccurenceInputWrapper } from '../Occurences/styles' - -type NumericConditionInputProps = { - value: number - comparator: Comparators - onchange: (value: number, comparator: Comparators) => void - enableNegativeValues?: boolean -} - -const NumericConditionInput = ({ - value, - comparator, - onchange, - enableNegativeValues = false -}: NumericConditionInputProps) => { - const [occurrenceValue, setOccurrenceValue] = useState(value) - const [comparatorValue, setComparatorValue] = useState(comparator) - - useEffect(() => { - if ( - !enableNegativeValues && - comparatorValue === Comparators.LESS && - (occurrenceValue === 0 || occurrenceValue === '0') - ) { - setOccurrenceValue(1) - onchange(1, comparatorValue) - } - }, [comparatorValue, occurrenceValue, enableNegativeValues, onchange]) - - const handleOccurrenceChange = (e: React.ChangeEvent) => { - const newValue = e.target.value - - if (newValue === '' || newValue.match(enableNegativeValues ? /^-?\d*\.?\d*$/ : /^\d*\.?\d*$/)) { - setOccurrenceValue(newValue) - if (newValue !== '' && newValue !== '-' && newValue !== '.') { - onchange(parseFloat(newValue), comparatorValue) - } else { - setOccurrenceValue(newValue) - } - } - } - const handleComparatorChange = (event: SelectChangeEvent) => { - const newComparator = event.target.value as Comparators - setComparatorValue(newComparator) - if ( - !enableNegativeValues && - newComparator === Comparators.LESS && - (occurrenceValue === 0 || occurrenceValue === '0') - ) { - setOccurrenceValue(1) - onchange(1, newComparator) - } else { - onchange(typeof occurrenceValue === 'string' ? parseFloat(occurrenceValue) : occurrenceValue, newComparator) - } - } - - return ( - - - - - - ) -} - -export default NumericConditionInput diff --git a/src/config.tsx b/src/config.tsx index b83616022..5b90e5fc4 100644 --- a/src/config.tsx +++ b/src/config.tsx @@ -2,9 +2,12 @@ import React, { createContext, ReactNode } from 'react' import { Root } from 'react-dom/client' import * as R from 'ramda' import { CONFIG_URL } from 'constants.js' +import { LabelObject } from 'types/searchCriterias' +import { birthStatusData, booleanFieldsData, booleanOpenChoiceFieldsData, vmeData } from 'data/questionnaire_data' type ValueSetConfig = { url: string + data?: LabelObject[] } type AppConfig = { @@ -63,6 +66,7 @@ type AppConfig = { enabled: boolean valueSets: { imagingModalities: ValueSetConfig + documentAttachementMethod: ValueSetConfig } extensions: { imagingStudyUidUrl: string @@ -94,6 +98,10 @@ type AppConfig = { presentationAtDelivery: ValueSetConfig risksOrComplicationsOfPregnancy: ValueSetConfig risksRelatedToObstetricHistory: ValueSetConfig + booleanOpenChoiceFields: ValueSetConfig + booleanFields: ValueSetConfig + vme: ValueSetConfig + birthStatus: ValueSetConfig } } locationMap: { @@ -238,7 +246,24 @@ let config: AppConfig = { imaging: { enabled: true, valueSets: { - imagingModalities: { url: '' } + imagingModalities: { url: '' }, + documentAttachementMethod: { + url: 'documentAttachementMethod', + data: [ + { + id: 'NONE', + label: 'Aucun' + }, + { + id: 'ACCESS_NUMBER', + label: "Numéro d'accès" + }, + { + id: 'INFERENCE_TEMPOREL', + label: 'Inference temporelle' + } + ] + } }, extensions: { imagingStudyUidUrl: '' @@ -268,7 +293,23 @@ let config: AppConfig = { pregnancyMode: { url: '' }, presentationAtDelivery: { url: '' }, risksOrComplicationsOfPregnancy: { url: '' }, - risksRelatedToObstetricHistory: { url: '' } + risksRelatedToObstetricHistory: { url: '' }, + booleanOpenChoiceFields: { + url: 'booleanOpenChoiceFields', + data: booleanOpenChoiceFieldsData + }, + booleanFields: { + url: 'booleanFields', + data: booleanFieldsData + }, + vme: { + url: 'vme', + data: vmeData + }, + birthStatus: { + url: 'birthStatus', + data: birthStatusData + } } }, locationMap: { diff --git a/src/state/store.ts b/src/state/store.ts index a33278455..61682645d 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -24,6 +24,7 @@ import pmsi from './pmsi' import me from './me' import syncHierarchyTable from './syncHierarchyTable' import warningDialog from './warningDialog' +import valueSets from './valueSets' const cohortCreationReducer = combineReducers({ criteria, @@ -33,6 +34,7 @@ const cohortCreationReducer = combineReducers({ const rootReducer = combineReducers({ me, cohortCreation: cohortCreationReducer, + valueSets, exploredCohort, drawer, message, diff --git a/src/state/valueSets.ts b/src/state/valueSets.ts new file mode 100644 index 000000000..d82740485 --- /dev/null +++ b/src/state/valueSets.ts @@ -0,0 +1,112 @@ +import { createSlice, createEntityAdapter, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit' +import { fetchValueSet } from 'services/aphp/callApi' +import { CriteriaItemType, HierarchyElementWithSystem } from 'types' +import { logout } from './me' +import { LabelObject } from 'types/searchCriterias' + +type ValueSetOptions = { + id: string + options: HierarchyElementWithSystem[] +} + +export const getAllCriteriaItems = (criteria: readonly CriteriaItemType[]): CriteriaItemType[] => { + const allCriteriaItems: CriteriaItemType[] = [] + for (const criterion of criteria) { + allCriteriaItems.push(criterion) + if (criterion.subItems && criterion.subItems.length > 0) { + allCriteriaItems.push(...getAllCriteriaItems(criterion.subItems)) + } + } + return allCriteriaItems +} + +export const prefetchSmallValueSets = async (criteriaTree: CriteriaItemType[]): Promise> => { + const criteriaList = getAllCriteriaItems(criteriaTree) + + // fetch all unique valueSetIds from the criteriaList + const uniqueValueSetIds = criteriaList + .flatMap((criterion) => { + const criterionValuesets = criterion.formDefinition?.itemSections.flatMap((section) => { + const sectionValuesets = section.items + .map((item) => { + if (item.type === 'autocomplete') { + return { id: item.valueSetId, data: item.valueSetData } + } + return null + }) + .filter((item) => item !== null) + .map((item) => item as { id: string; data?: LabelObject[] }) + return sectionValuesets + }) + + return criterionValuesets || [] + }) + .reduce((acc, item) => { + if (!acc.some((existingItem) => existingItem.id === item.id)) { + acc.push(item) + } + return acc + }, [] as { id: string; data?: LabelObject[] }[]) + + // fetch them + return await Promise.all( + uniqueValueSetIds.map(async (vs) => { + if (vs.data) { + return { id: vs.id, options: vs.data } + } + const options = await fetchValueSet(vs.id, { + joinDisplayWithCode: false, + sortingKey: 'id' + }) + return { id: vs.id, options } + }) + ) +} + +export const initValueSets = createAsyncThunk('valueSets/initValueSets', async (criteriaList: CriteriaItemType[]) => { + const response = await prefetchSmallValueSets(criteriaList) + return response +}) + +const valueSetsAdapter = createEntityAdapter() + +const valueSetsSlice = createSlice({ + name: 'valueSets', + initialState: valueSetsAdapter.getInitialState({ + loading: false, + error: false, + loaded: false, + cache: {} as { [system: string]: LabelObject[] } + }), + reducers: { + addValueSets: valueSetsAdapter.addMany, + updateCache: (state, action: PayloadAction<{ [system: string]: LabelObject[] }>) => { + return { + ...state, + cache: action.payload + } + } + }, + extraReducers: (builder) => { + builder + .addCase(logout.fulfilled, () => + valueSetsAdapter.getInitialState({ loading: false, error: false, loaded: false, cache: {} }) + ) + .addCase(initValueSets.pending, (state) => { + state.loading = true + state.error = false + }) + .addCase(initValueSets.fulfilled, (state, action) => { + valueSetsAdapter.setAll(state, action.payload) + state.loading = false + state.loaded = true + }) + .addCase(initValueSets.rejected, (state) => { + state.loading = false + state.error = true + }) + } +}) + +export const { addValueSets, updateCache } = valueSetsSlice.actions +export default valueSetsSlice.reducer diff --git a/src/types.ts b/src/types.ts index 05a8fdf84..48008b254 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,6 +26,7 @@ import { Comparators, CriteriaDataKey, CriteriaType, ResourceType, SelectedCrite import { ExportTableType } from 'components/Dashboard/ExportModal/export_table' import { Hierarchy } from 'types/hierarchy' import { SearchByTypes } from 'types/searchCriterias' +import { CriteriaForm } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types' export enum JobStatus { new = 'new', @@ -371,6 +372,7 @@ export type CriteriaItemType = { disabled?: boolean fetch?: { [key in CriteriaDataKey]?: FetchFunctionVariant } subItems?: CriteriaItemType[] + formDefinition?: CriteriaForm } type FetchFunctionVariant = @@ -714,7 +716,7 @@ export type HierarchyTree = null | { loading?: number } -export type HierarchyElementWithSystem = Hierarchy & { system?: string } +export type HierarchyElementWithSystem = Hierarchy & { system?: string } export type ScopeElement = { id: string diff --git a/src/types/requestCriterias.ts b/src/types/requestCriterias.ts index f1aae1cef..7fd5369dc 100644 --- a/src/types/requestCriterias.ts +++ b/src/types/requestCriterias.ts @@ -1,6 +1,9 @@ import { ScopeElement, SimpleCodeType } from 'types' import { Hierarchy } from './hierarchy' import { DocumentAttachmentMethod, DurationRangeType, LabelObject, SearchByTypes } from './searchCriterias' +import { NewGhmDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/GHMForm' +import { NewHospitDataType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/HospitForm' +import { NewImagingType } from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/ImagingForm' export enum MedicationLabel { PRESCRIPTION = 'Prescription', @@ -122,6 +125,9 @@ export type SelectedCriteriaType = | ImagingDataType | PregnancyDataType | HospitDataType + // | NewGhmDataType + // | NewHospitDataType + // | NewImagingType export type SelectedCriteriaTypesWithAdvancedInputs = Exclude< SelectedCriteriaType, diff --git a/src/utils/cohortCreation.ts b/src/utils/cohortCreation.ts index f500d6b0e..9d90646f2 100644 --- a/src/utils/cohortCreation.ts +++ b/src/utils/cohortCreation.ts @@ -89,6 +89,14 @@ import { QuestionnaireResponseParamsKeys } from 'mappers/filters' import { getConfig } from 'config' +import { fetchValueSet } from 'services/aphp/callApi' +import { getAllCriteriaItems } from 'state/valueSets' +import { + CriteriaData, + CriteriaForm, + DataType +} from 'components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/types' +import { isFunction, isString } from 'lodash' const REQUETEUR_VERSION = 'v1.4.5' @@ -484,6 +492,64 @@ export const buildMedicationFilter = (criterion: MedicationDataType): string[] = ] } +export const buildCriteriaFilter = ( + criteriaDefinition: CriteriaForm, + criteria: T, + deidentified: boolean +): string[] => { + return criteriaDefinition.itemSections.flatMap((section) => + section.items.map((item) => { + const buildInfo = item.build + if (buildInfo === undefined) { + console.warn(`No build info for ${item.valueKey as string}`) + return '' + } + const buildMethod = buildInfo.buildMethod + const value = criteria[item.valueKey] + const filterValues = isString(buildMethod) ? eval(buildMethod) : buildMethod(value as DataType, deidentified) + if (filterValues === undefined) { + return '' + } + const filterValuesArray = Array.isArray(filterValues) ? filterValues : [filterValues] + return filterValuesArray.map((val) => `${item.build?.fhirKey}=${val}`).join('&') + }) + ) +} + +export const unbuildCriteriaData = async ( + element: RequeteurCriteriaType, + critieriaDefinitions: CriteriaItemType[] +): Promise => { + const criteriaType = element._type + const criteriaDefinition = critieriaDefinitions.find((item) => item.id === criteriaType) + if (!criteriaDefinition || !criteriaDefinition.formDefinition) { + throw new Error('Unknown criteria type') + } + const emptyCriterion = { ...criteriaDefinition.formDefinition.initialData } + const criteriaItems = criteriaDefinition.formDefinition.itemSections.flatMap((section) => section.items) + if (element.filterFhir) { + const filters = element.filterFhir.split('&').map((elem) => elem.split('=')) + try { + for (const filter of filters) { + const key = filter[0] ?? null + const value = filter[1] ?? null + if (key !== null) { + const item = criteriaItems.find((item) => item.build?.fhirKey === key) + if (item && item.build?.unbuildMethod) { + const unbuildMethod = item.build?.unbuildMethod + const dataValue = isFunction(unbuildMethod) ? await unbuildMethod(value) : eval(unbuildMethod) + emptyCriterion[item?.valueKey] = dataValue + } + } + } + } catch (error) { + console.error(error) + emptyCriterion.error = true + } + } + return emptyCriterion +} + export const buildObservationFilter = (criterion: ObservationDataType): string[] => { return [ `subject.active=true&${ObservationParamsKeys.VALIDATED_STATUS}=${BiologyStatus.VALIDATED}`, @@ -2076,6 +2142,40 @@ export async function unbuildRequest(_json: string): Promise { } } +export const fetchCriteriasCodes = async ( + criteriaList: readonly CriteriaItemType[], + selectedCriteria: SelectedCriteriaType[], + oldCriteriaCache?: { [system: string]: LabelObject[] } +): Promise<{ [system: string]: LabelObject[] }> => { + const updatedCriteriaData: { [system: string]: LabelObject[] } = { ...oldCriteriaCache } + const allCriterias = getAllCriteriaItems(criteriaList) + for (const criteria of allCriterias) { + const criteriaValues = selectedCriteria.filter((criterion) => { + criterion.type === criteria.id + }) + for (const section of criteria.formDefinition?.itemSections || []) { + for (const item of section.items || []) { + if (item.type === 'codeSearch') { + const valueSetCodeCache = updatedCriteriaData[item.valueSetId] || [] + for (const criterion of criteriaValues) { + const dataKey = item.valueKey as string + // TODO remove this type casting when using the proper entry type, also make sure that the dataKey is a valid key + const labelValues = criterion[dataKey as keyof SelectedCriteriaType] as unknown as LabelObject[] + for (const code of labelValues) { + if (!valueSetCodeCache.find((data) => data.id === code.id)) { + const fetchedCode = (await fetchValueSet(item.valueSetId, { code: code.id || '' })) as LabelObject[] + valueSetCodeCache.push(...fetchedCode) + } + } + } + updatedCriteriaData[item.valueSetId] = valueSetCodeCache + } + } + } + } + return updatedCriteriaData +} + /** * This function calls all functions to fetch data contained inside `src/components/CreationCohort/DataList_Criteria` list *