Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
pl-buiquang committed Sep 26, 2024
1 parent 950eee9 commit bccbaeb
Show file tree
Hide file tree
Showing 19 changed files with 1,214 additions and 23 deletions.
6 changes: 5 additions & 1 deletion src/components/CreationCohort/DataList_Criteria.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ 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/GHM/GHMForm2'

const criteriaList: () => CriteriaItemType[] = () => {
const ODD_QUESTIONNAIRE = getConfig().features.questionnaires.enabled
Expand Down Expand Up @@ -115,7 +118,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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { FormLabel, Tooltip } from '@mui/material'
import React, { PropsWithChildren } from 'react'
import {
CriteriaDataType,
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'

type CriteriaItemRuntimeProps<T extends CriteriaDataType> = {
setError: (error?: string) => void
updateData: (data: T) => void
data: T
getValueSetOptions: CriteriaFormItemViewProps<never>['getValueSetOptions']
searchCode: CriteriaFormItemViewProps<never>['searchCode']
viewRenderers: { [key in CriteriaItemType]: CriteriaFormItemView<key> }
}

type CritieraItemProps<T extends CriteriaDataType, U extends CriteriaItem<T>> = CriteriaItemRuntimeProps<T> & U

export const CFLabel = (props: { label: string; tooltip?: string; altStyle?: boolean }) => {
const { label, tooltip, altStyle } = props
if (altStyle) {
return (
<FormLabel
style={{ padding: '0 0 1em', fontWeight: 600, fontSize: 12, display: 'flex', alignItems: 'center' }}
component="legend"
>
Fin de prise en charge
{tooltip && (
<Tooltip title={tooltip}>
<InfoIcon fontSize="small" color="primary" style={{ marginLeft: 4 }} />
</Tooltip>
)}
</FormLabel>
)
}
return label
}

export const CFItem = <T extends CriteriaDataType, U extends CriteriaItem<T>>(props: CritieraItemProps<T, U>) => {
const { valueKey, updateData, data, setError, getValueSetOptions, searchCode, viewRenderers } = props
const View = viewRenderers[props.type] as CriteriaFormItemView<U['type']>
const fieldValue = data[valueKey] as DataTypeMapping[U['type']]['dataType']
return (
<View
value={fieldValue}
definition={props}
updateData={(value) => updateData({ ...data, [valueKey]: value })}
getValueSetOptions={getValueSetOptions}
searchCode={searchCode}
setError={setError}
/>
)
}

export const CFSection = <T extends CriteriaDataType>(
props: PropsWithChildren<Omit<CriteriaSection<T>, 'items'> & { collapsed?: boolean }>
) => {
const { classes } = useStyles()
return props.title ? (
<BlockWrapper className={classes.inputItem}>
<Collapse title={props.title} value={!props.defaulCollapsed || !props.collapsed} margin="0">
{props.children}
</Collapse>
</BlockWrapper>
) : (
<>{props.children}</>
)
}

export const CFItemWrapper = (props: PropsWithChildren<{ label?: string; info?: string }>) => {
const { classes } = useStyles()
return (
<BlockWrapper className={classes.inputItem}>
{props.label ? (
<CriteriaLabel label={props.label} style={{ padding: 0, marginTop: '1em' }}>
{props.info && (
<Tooltip title={props.info}>
<InfoIcon fontSize="small" color="primary" style={{ marginLeft: 4 }} />
</Tooltip>
)}
</CriteriaLabel>
) : (
''
)}
{props.children}
</BlockWrapper>
)
}

export default { CFItemWrapper, CFSection, CFLabel, CFItem }
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react'
import CriteriaLayout from 'components/ui/CriteriaLayout'
import {
CriteriaData,
CriteriaForm as CriteriaFormDefinition,
CriteriaDataType,
CriteriaFormItemViewProps
} from './types'
import { CFItem, CFItemWrapper, CFSection } from './components'
import FORM_ITEM_RENDERER from './renderers'

export type CriteriaFormRuntimeProps<T extends CriteriaDataType> = {
goBack: () => void
data?: CriteriaData<T>
updateData: (data: CriteriaData<T>) => void
getValueSetOptions: CriteriaFormItemViewProps<never>['getValueSetOptions']
searchCode: CriteriaFormItemViewProps<never>['searchCode']
}

type CriteriaFormProps<T extends CriteriaDataType> = CriteriaFormDefinition<T> & CriteriaFormRuntimeProps<T>

export default function CriteriaForm<T extends CriteriaDataType>(props: CriteriaFormProps<T>) {
const [criteriaData, setCriteriaData] = useState<CriteriaData<T>>(props.data || props.initialData)
const { goBack, updateData, label, warningAlert, getValueSetOptions, itemSections, errorMessages, onDataChange } =
props
const isEdition = !!props.data
const [error, setError] = useState<string>()

useEffect(() => {
onDataChange?.(criteriaData)
}, [criteriaData, onDataChange])

return (
<CriteriaLayout
criteriaLabel={`${label}`}
title={criteriaData.title}
onChangeTitle={(title) => 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) => (
<CFSection
key={index}
title={section.title}
defaulCollapsed={section.defaulCollapsed}
collapsed={section.items.every((item) => {
const value = criteriaData[item.valueKey]
return value === null || value === undefined || (Array.isArray(value) && value.length === 0)
})}
>
{section.items.map((item, index) => (
<CFItemWrapper key={index} label={item.extraLabel} info={item.extraInfo}>
<CFItem
{...{
viewRenderers: FORM_ITEM_RENDERER,
...item,
data: criteriaData,
getValueSetOptions,
searchCode: props.searchCode,
updateData: (newData: T) => {
setCriteriaData({ ...criteriaData, ...newData })
},
setError
}}
/>
</CFItemWrapper>
))}
</CFSection>
))}
</CriteriaLayout>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import { CriteriaDrawerComponentProps, CriteriaItemDataCache } from 'types'
import { CriteriaData, CriteriaDataType } from './types'
import CriteriaForm from '.'
import { CriteriaForm as CriteriaFormDefinition } from './types'
import { LabelObject } from 'types/searchCriterias'
import { CriteriaDataKey, SelectedCriteriaType } from 'types/requestCriterias'
import { fetchValueSet } from 'services/aphp/callApi'

export type LegacyAdapterProps<T extends CriteriaDataType, U extends SelectedCriteriaType> = {
form: CriteriaFormDefinition<T>
adapter: {
mapFromLegacyDataType: (legacyData: U, criteriaData: CriteriaItemDataCache) => CriteriaData<T>
mapToLegacyDataType: (data: CriteriaData<T>) => Omit<U, 'id'>
valueSetIdToKey: (valueSetId: string) => CriteriaDataKey | undefined
}
}

/**
* 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<T extends CriteriaDataType, U extends SelectedCriteriaType>(
props: LegacyAdapterProps<T, U>
) {
return (legacyProps: CriteriaDrawerComponentProps) => {
const { criteriaData, goBack, selectedCriteria, onChangeSelectedCriteria } = legacyProps
const runtimeProps = {
data: selectedCriteria
? props.adapter.mapFromLegacyDataType(selectedCriteria as U, criteriaData)
: props.form.initialData,
updateData: (data: CriteriaData<T>) => onChangeSelectedCriteria(props.adapter.mapToLegacyDataType(data) as U),
goBack,
getValueSetOptions: (valueSetId: string) => {
const dataKey = props.adapter.valueSetIdToKey(valueSetId)
if (dataKey === undefined) {
return [] as LabelObject[]
}
return (criteriaData.data[dataKey] as LabelObject[]) || []
}
}
return (
<CriteriaForm
{...props.form}
{...runtimeProps}
searchCode={(code: string, codeSystemUrl: string, abortSignal: AbortSignal) =>
fetchValueSet(
codeSystemUrl,
{ valueSetTitle: 'Toute la hiérarchie', search: code, noStar: false },
abortSignal
)
}
/>
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import { CriteriaFormItemView, CriteriaItemType } from './types'
import { CFLabel } from './components'
import CalendarRange from 'components/ui/Inputs/CalendarRange'
import { Autocomplete, 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'

const FORM_ITEM_RENDERER: { [key in CriteriaItemType]: CriteriaFormItemView<key> } = {
text: (props) => <TextField label={props.definition.label} />,
duration: (props) => (
<>
<CalendarRange
inline
label={
props.definition.label && (
<CFLabel

Check failure on line 19 in src/components/CreationCohort/DiagramView/components/LogicalOperator/components/CriteriaRightPanel/CriteriaForm/renderers.tsx

View workflow job for this annotation

GitHub Actions / test

'CFLabel' cannot be used as a JSX component.
label={props.definition.label}
tooltip={props.definition.info}
altStyle={props.definition.labelAltStyle}
/>
)
}
value={!!props.value ? [props.value.start, props.value.end] : [null, null]}
onChange={(range, includeNull) =>
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) => {
return (
<Autocomplete
multiple
options={props.getValueSetOptions(props.definition.valueSetId)}
noOptionsText={props.definition.noOptionsText}
getOptionLabel={(option) => option.label}
isOptionEqualToValue={(option, value) => option.id === value.id}
value={props.value}
onChange={(e, value) => props.updateData(value)}
renderInput={(params) => <TextField {...params} label={props.definition.label} />}
/>
)
},
number: (props) => <TextField label={props.definition.label} type="number" />,
executiveUnit: (props) => (
<ExecutiveUnitsInput
sourceType={props.definition.sourceType}
value={props.value || []}
onChange={(value) => props.updateData(value)}
/>
),
occurrence: (props) => (
<OccurenceInput
label={props.definition.label}
value={props.value.value}
comparator={props.value.comparator}
onchange={(newCount, newComparator) => {
props.updateData({ value: newCount, comparator: newComparator })
}}
withHierarchyInfo={props.definition.withHierarchyInfo}
/>
),
boolean: (props) => <TextField label={props.definition.label} type="checkbox" />,
textWithCheck: (props) => (
<SearchbarWithCheck
searchInput={props.value}
setSearchInput={(value) => props.updateData(value)}
placeholder={props.definition.placeholder}
onError={(isError) => props.setError(isError ? props.definition.errorType : undefined)}
/>
),
codeSearch: (props) => {
return (
<AsyncAutocomplete
label={props.definition.label || 'Code(s) sélectionné(s)'}
variant="outlined"
noOptionsText={props.definition.noOptionsText}
values={props.value || []}
onFetch={(search, signal) => props.searchCode(search, props.definition.valueSetId, signal)}
onChange={(value) => props.updateData(value)}
/>
)
}
}

export default FORM_ITEM_RENDERER
Loading

0 comments on commit bccbaeb

Please sign in to comment.