From ac3dfee27f9097c20a27c6f25025ad8a82d067da Mon Sep 17 00:00:00 2001 From: "Manelle G." <39384110+ManelleG@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:05:32 +0200 Subject: [PATCH] feat: added biology view to dashboard - Ref gestion-de-projet#2678 (#1049) --- .../Dashboard/BiologyList/index.tsx | 496 ++++++++++++++++++ .../DataTable/DataTableObservation.tsx | 49 +- src/mappers/filters.ts | 3 +- src/services/aphp/callApi.ts | 13 +- src/services/aphp/serviceCohorts.ts | 113 +++- src/types.ts | 10 + src/types/searchCriterias.ts | 1 + src/utils/fillElement.ts | 18 +- src/views/Dashboard/Dashboard.tsx | 10 +- 9 files changed, 691 insertions(+), 22 deletions(-) create mode 100644 src/components/Dashboard/BiologyList/index.tsx diff --git a/src/components/Dashboard/BiologyList/index.tsx b/src/components/Dashboard/BiologyList/index.tsx new file mode 100644 index 000000000..8f473a376 --- /dev/null +++ b/src/components/Dashboard/BiologyList/index.tsx @@ -0,0 +1,496 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react' +import { useAppSelector } from 'state' + +import { Checkbox, Chip, CircularProgress, Grid, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material' +import { Save, SavedSearch, FilterList } from '@mui/icons-material' +import { AlertWrapper } from 'components/ui/Alert' +import { BlockWrapper } from 'components/ui/Layout' +import Button from 'components/ui/Button' +import DatesRangeFilter from 'components/Filters/DatesRangeFilter' +import DisplayDigits from 'components/ui/Display/DisplayDigits' +import EncounterStatusFilter from 'components/Filters/EncounterStatusFilter' +import ExecutiveUnitsFilter from 'components/Filters/ExecutiveUnitsFilter' +import IppFilter from 'components/Filters/IppFilter' +import List from 'components/ui/List' +import Modal from 'components/ui/Modal' +import NdaFilter from 'components/Filters/NdaFilter' +import SearchInput from 'components/ui/Searchbar/SearchInput' +import TextInput from 'components/Filters/TextInput' + +import { ResourceType } from 'types/requestCriterias' +import { Hierarchy } from 'types/hierarchy' +import { DTTB_ResultsType as ResultsType, LoadingStatus, CohortObservation } from 'types' +import { BiologyFilters, Direction, FilterKeys, Order } from 'types/searchCriterias' +import { CanceledError } from 'axios' +import { useSavedFilters } from 'hooks/filters/useSavedFilters' +import services from 'services/aphp' +import useSearchCriterias, { initBioSearchCriterias } from 'reducers/searchCriteriasReducer' +import { cancelPendingRequest } from 'utils/abortController' +import { selectFiltersAsArray } from 'utils/filters' +import DataTableObservation from 'components/DataTable/DataTableObservation' +import AnabioFilter from 'components/Filters/AnabioFilter' +import LoincFilter from 'components/Filters/LoincFilter' +import { SourceType } from 'types/scope' +import { + fetchLoincCodes as fetchLoincCodesApi, + fetchAnabioCodes as fetchAnabioCodesApi +} from 'services/aphp/serviceBiology' + +type BiologyListProps = { + groupId?: string + deidentified?: boolean +} + +const BiologyList = ({ groupId, deidentified }: BiologyListProps) => { + const theme = useTheme() + const isMd = useMediaQuery(theme.breakpoints.down('lg')) + + const [toggleFilterByModal, setToggleFilterByModal] = useState(false) + const [toggleSaveFiltersModal, setToggleSaveFiltersModal] = useState(false) + const [toggleSavedFiltersModal, setToggleSavedFiltersModal] = useState(false) + const [toggleFilterInfoModal, setToggleFilterInfoModal] = useState(false) + const [isReadonlyFilterInfoModal, setIsReadonlyFilterInfoModal] = useState(true) + const [encounterStatusList, setEncounterStatusList] = useState[]>([]) + + const [page, setPage] = useState(1) + const { + allSavedFilters, + savedFiltersErrors, + selectedSavedFilter, + allSavedFiltersAsListItems, + methods: { + getSavedFilters, + postSavedFilter, + deleteSavedFilters, + patchSavedFilter, + selectFilter, + resetSavedFilterError + } + } = useSavedFilters(ResourceType.OBSERVATION) + + const [ + { + orderBy, + searchInput, + filters, + filters: { validatedStatus, nda, ipp, loinc, anabio, startDate, endDate, executiveUnits, encounterStatus } + }, + { changeOrderBy, changeSearchInput, addFilters, removeFilter, addSearchCriterias } + ] = useSearchCriterias(initBioSearchCriterias) + const filtersAsArray = useMemo( + () => + selectFiltersAsArray({ + validatedStatus, + nda, + ipp, + loinc, + anabio, + startDate, + endDate, + executiveUnits, + encounterStatus + }), + [validatedStatus, nda, ipp, loinc, anabio, startDate, endDate, executiveUnits, encounterStatus] + ) + + const [loadingStatus, setLoadingStatus] = useState(LoadingStatus.FETCHING) + const [searchResults, setSearchResults] = useState({ + nb: 0, + total: 0, + label: 'résultat(s)' + }) + const [biologyList, setBiologyList] = useState([]) + const [patientsResults, setPatientsResults] = useState({ nb: 0, total: 0, label: 'patient(s)' }) + + const controllerRef = useRef(null) + const meState = useAppSelector((state) => state.me) + const maintenanceIsActive = meState?.maintenance?.active + + const _fetchBiology = async () => { + try { + setLoadingStatus(LoadingStatus.FETCHING) + const response = await services.cohorts.fetchBiologyList( + { + deidentified: !!deidentified, + page, + searchCriterias: { + orderBy, + searchInput, + filters: { validatedStatus, nda, ipp, loinc, anabio, startDate, endDate, executiveUnits, encounterStatus } + } + }, + groupId, + controllerRef.current?.signal + ) + + if (response) { + const { totalBiology, totalAllBiology, totalPatientBiology, totalAllPatientsBiology, biologyList } = response + setSearchResults((prevState) => ({ + ...prevState, + nb: totalBiology, + total: totalAllBiology + })) + setBiologyList(biologyList) + setPatientsResults((prevState) => ({ + ...prevState, + nb: totalPatientBiology, + total: totalAllPatientsBiology + })) + } + setLoadingStatus(LoadingStatus.SUCCESS) + } catch (error) { + if (error instanceof CanceledError) { + setLoadingStatus(LoadingStatus.FETCHING) + } else { + setLoadingStatus(LoadingStatus.SUCCESS) + setSearchResults((prevState) => ({ + ...prevState, + nb: 0, + total: 0 + })) + setBiologyList([]) + setPatientsResults((prevState) => ({ + ...prevState, + nb: 0, + total: 0 + })) + } + } + } + + useEffect(() => { + const fetch = async () => { + try { + const encounterStatus = await services.cohortCreation.fetchEncounterStatus() + setEncounterStatusList(encounterStatus) + } catch (e) { + /* empty */ + } + } + getSavedFilters() + fetch() + }, []) + + useEffect(() => { + setLoadingStatus(LoadingStatus.IDDLE) + setPage(1) + }, [ + searchInput, + orderBy, + validatedStatus, + nda, + ipp, + loinc, + anabio, + startDate, + endDate, + executiveUnits, + encounterStatus + ]) + + useEffect(() => { + setLoadingStatus(LoadingStatus.IDDLE) + }, [page]) + + useEffect(() => { + if (loadingStatus === LoadingStatus.IDDLE) { + controllerRef.current = cancelPendingRequest(controllerRef.current) + _fetchBiology() + } + }, [loadingStatus]) + + return ( + + + + Les mesures de biologie sont pour l'instant restreintes aux 3870 codes ANABIO correspondants aux analyses les + plus utilisées au niveau national et à l'AP-HP. De plus, les résultats concernent uniquement les analyses + quantitatives enregistrées sur GLIMS, qui ont été validées et mises à jour depuis mars 2020. + + + + {(filtersAsArray.length > 0 || searchInput) && ( + + + + + + + + )} + + {!!allSavedFilters?.count && ( + + )} + + + + + + + + N'afficher que les analyses dont les résultats ont été validés + + + + {(loadingStatus === LoadingStatus.FETCHING || loadingStatus === LoadingStatus.IDDLE) && } + {loadingStatus !== LoadingStatus.FETCHING && loadingStatus !== LoadingStatus.IDDLE && ( + + + + + + )} + + + changeSearchInput(newValue)} + /> + + + {filtersAsArray.length > 0 && ( + + {filtersAsArray.map((filter, index) => ( + removeFilter(filter.category, filter.value)} /> + ))} + + )} + + + changeOrderBy(orderBy)} + page={page} + setPage={(newPage) => setPage(newPage)} + total={searchResults.nb} + groupId={groupId} + showIpp + /> + + + setToggleFilterByModal(false)} + onSubmit={(newFilters) => addFilters({ ...filters, ...newFilters })} + > + {!deidentified && } + {!deidentified && } + + + + + + + { + setToggleSavedFiltersModal(false) + resetSavedFilterError() + }} + onSubmit={() => { + if (selectedSavedFilter) addSearchCriterias(selectedSavedFilter.filterParams) + }} + validationText="Appliquer le filtre" + > + { + setToggleFilterInfoModal(true) + setIsReadonlyFilterInfoModal(true) + }} + onEdit={ + maintenanceIsActive + ? undefined + : () => { + setToggleFilterInfoModal(true) + setIsReadonlyFilterInfoModal(false) + } + } + onDelete={maintenanceIsActive ? undefined : deleteSavedFilters} + onSelect={(filter) => selectFilter(filter)} + fetchPaginateData={() => getSavedFilters(allSavedFilters?.next)} + > + setToggleFilterInfoModal(false)} + onSubmit={({ + filterName, + searchInput, + nda, + ipp, + anabio, + loinc, + startDate, + endDate, + validatedStatus, + executiveUnits, + encounterStatus + }) => { + patchSavedFilter( + filterName, + { + searchInput, + orderBy: { orderBy: Order.FAMILY, orderDirection: Direction.ASC }, + filters: { + nda, + ipp, + anabio, + loinc, + startDate, + endDate, + validatedStatus, + executiveUnits, + encounterStatus + } + }, + deidentified ?? true + ) + }} + validationText="Sauvegarder" + > + + + + + + + + + {!deidentified && ( + + + + )} + {!deidentified && ( + + + + )} + + + + + + + + + + + + + + + + + + + + + { + setToggleSaveFiltersModal(false) + resetSavedFilterError() + }} + onSubmit={({ filtersName }) => + postSavedFilter(filtersName, { searchInput, filters, orderBy }, deidentified ?? true) + } + > + + + + ) +} + +export default BiologyList diff --git a/src/components/DataTable/DataTableObservation.tsx b/src/components/DataTable/DataTableObservation.tsx index da333d6a2..88e7fc1d1 100644 --- a/src/components/DataTable/DataTableObservation.tsx +++ b/src/components/DataTable/DataTableObservation.tsx @@ -1,6 +1,8 @@ import React, { useContext } from 'react' -import { CircularProgress, Grid, Typography, TableRow } from '@mui/material' +import { CircularProgress, Grid, Typography, TableRow, IconButton } from '@mui/material' +import SearchIcon from 'assets/icones/search.svg?react' + import { TableCellWrapper } from 'components/ui/TableCell/styles' import DataTable from 'components/DataTable/DataTable' @@ -20,6 +22,8 @@ type DataTableObservationProps = { page?: number setPage?: (page: number) => void total?: number + showIpp?: boolean + groupId?: string } const DataTableObservation: React.FC = ({ loading, @@ -29,32 +33,40 @@ const DataTableObservation: React.FC = ({ setOrderBy, page, setPage, - total + total, + showIpp, + groupId }) => { const { classes } = useStyles() const columns: Column[] = [ - { label: `NDA${deidentified ? ' chiffré' : ''}`, align: 'left' }, + ...(showIpp ? [{ label: `IPP${deidentified ? ' chiffré' : ''}`, align: 'left' }] : []), + { label: `NDA${deidentified ? ' chiffré' : ''}`, align: showIpp ? 'center' : 'left' }, { label: 'Date de prélèvement', code: Order.DATE }, { label: 'ANABIO', code: Order.ANABIO }, { label: 'LOINC', code: Order.LOINC }, { label: 'Résultat' }, { label: 'Valeurs de référence' }, { label: 'Unité exécutrice' } - ] + ].filter((elem) => elem !== null) as Column[] return ( {!loading && observationsList?.length > 0 && ( <> {observationsList.map((observation) => ( - + ))} )} {!loading && observationsList?.length < 1 && ( - + Aucun résultat de biologie à afficher @@ -63,7 +75,7 @@ const DataTableObservation: React.FC = ({ )} {loading && ( - + @@ -76,17 +88,20 @@ const DataTableObservation: React.FC = ({ const formatValueRange = (value?: string | number, valueUnit?: string): string => { if (value) { - return `${value} ${valueUnit || ''}` + return `${value} ${valueUnit ?? ''}` } return '_' } const DataTableObservationLine: React.FC<{ observation: CohortObservation -}> = ({ observation }) => { + showIpp?: boolean + groupId?: string +}> = ({ observation, showIpp, groupId }) => { const { classes } = useStyles() const appConfig = useContext(AppConfig) + const ipp = observation.IPP const nda = observation.NDA const date = observation.effectiveDateTime const libelleANABIO = observation.code?.coding?.find( @@ -100,7 +115,7 @@ const DataTableObservationLine: React.FC<{ )?.display const result = observation.valueQuantity?.value !== null - ? `${observation.valueQuantity?.value} ${observation.valueQuantity?.unit || ''}` + ? `${observation.valueQuantity?.value} ${observation.valueQuantity?.unit ?? ''}` : '-' const valueUnit = observation.valueQuantity?.unit ?? '' const serviceProvider = observation.serviceProvider @@ -111,10 +126,22 @@ const DataTableObservationLine: React.FC<{ valueUnit )}` : '-' + const groupIdSearch = groupId ? `?groupId=${groupId}` : '' return ( - {nda ?? 'Inconnu'} + {showIpp && ( + + {ipp} + window.open(`/patients/${observation.idPatient}${groupIdSearch}`, '_blank')} + className={classes.searchIcon} + > + + + + )} + {nda ?? 'Inconnu'} {date ? new Date(date).toLocaleDateString('fr-FR') : 'Date inconnue'} diff --git a/src/mappers/filters.ts b/src/mappers/filters.ts index 55ce103b5..6a43e936a 100644 --- a/src/mappers/filters.ts +++ b/src/mappers/filters.ts @@ -132,7 +132,8 @@ export enum ObservationParamsKeys { DATE = 'date', VALUE = 'value-quantity', EXECUTIVE_UNITS = 'encounter.encounter-care-site', - ENCOUNTER_STATUS = 'encounter.status' + ENCOUNTER_STATUS = 'encounter.status', + IPP = 'subject.identifier' } export enum ImagingParamsKeys { diff --git a/src/services/aphp/callApi.ts b/src/services/aphp/callApi.ts index 9d3365443..afdf30241 100644 --- a/src/services/aphp/callApi.ts +++ b/src/services/aphp/callApi.ts @@ -672,6 +672,8 @@ type fetchObservationProps = { signal?: AbortSignal executiveUnits?: string[] encounterStatus?: string[] + uniqueFacet?: 'subject'[] + 'patient-identifier'?: string } export const fetchObservation = async (args: fetchObservationProps): FHIR_Bundle_Promise_Response => { const { @@ -693,12 +695,14 @@ export const fetchObservation = async (args: fetchObservationProps): FHIR_Bundle encounterStatus } = args const _sortDirection = sortDirection === Direction.DESC ? '-' : '' - let { _list } = args + let { _list, uniqueFacet } = args + const patientIdentifier = args['patient-identifier'] _list = _list ? _list.filter(uniq) : [] + uniqueFacet = uniqueFacet ? uniqueFacet.filter(uniq) : [] // By default, all the calls to `/Observation` will have 'value-quantity-value=ge0,le0' and 'patient.active=true' in the parameters - let options: string[] = ['value-quantity=ge0,le0', 'subject.active=true'] + let options: string[] = [`${ObservationParamsKeys.VALUE}=ge0,le0`, 'subject.active=true'] if (id) options = [...options, `_id=${id}`] if (size !== undefined) options = [...options, `_count=${size}`] if (offset) options = [...options, `_offset=${offset}`] @@ -715,9 +719,12 @@ export const fetchObservation = async (args: fetchObservationProps): FHIR_Bundle if (maxDate) options = [...options, `${ObservationParamsKeys.DATE}=le${maxDate}`] // eslint-disable-line if (rowStatus) options = [...options, `${ObservationParamsKeys.VALIDATED_STATUS}=${BiologyStatus.VALIDATED}`] // eslint-disable-line if (executiveUnits && executiveUnits.length > 0) - options = [...options, `encounter.encounter-care-site=${executiveUnits}`] + options = [...options, `${ObservationParamsKeys.EXECUTIVE_UNITS}=${executiveUnits}`] if (encounterStatus && encounterStatus.length > 0) options = [...options, `${ObservationParamsKeys.ENCOUNTER_STATUS}=${encounterStatus}`] + if (patientIdentifier) options = [...options, `${ObservationParamsKeys.IPP}=${patientIdentifier}`] + if (uniqueFacet && uniqueFacet.length > 0) + options = [...options, `unique-facet=${uniqueFacet.reduce(paramValuesReducer, '')}`] if (_list && _list.length > 0) options = [...options, `_list=${_list.reduce(paramValuesReducer)}`] diff --git a/src/services/aphp/serviceCohorts.ts b/src/services/aphp/serviceCohorts.ts index d8e51a9b7..fa8369540 100644 --- a/src/services/aphp/serviceCohorts.ts +++ b/src/services/aphp/serviceCohorts.ts @@ -16,7 +16,9 @@ import { FHIR_Bundle_Response, CohortPMSI, MedicationData, - CohortMedication + CohortMedication, + BiologyData, + CohortObservation } from 'types' import { getGenderRepartitionMapAphp, @@ -40,7 +42,8 @@ import { fetchProcedure, fetchClaim, fetchMedicationRequest, - fetchMedicationAdministration + fetchMedicationAdministration, + fetchObservation } from './callApi' import apiBackend from '../apiBackend' @@ -52,6 +55,7 @@ import { ImagingStudy, MedicationAdministration, MedicationRequest, + Observation, ParametersParameter, Patient, Procedure @@ -65,7 +69,8 @@ import { SearchByTypes, ImagingFilters, PMSIFilters, - MedicationFilters + MedicationFilters, + BiologyFilters } from 'types/searchCriterias' import services from '.' import { ErrorDetails, SearchInputError } from 'types/error' @@ -212,6 +217,19 @@ export interface IServiceCohorts { signal?: AbortSignal ) => Promise + /** + * Retourne la liste d'objets de biologie liés à une cohorte + */ + fetchBiologyList: ( + options: { + deidentified: boolean + page: number + searchCriterias: SearchCriterias + }, + groupId?: string, + signal?: AbortSignal + ) => Promise + /** * Permet de vérifier si le champ de recherche textuelle est correct * @@ -689,6 +707,95 @@ const servicesCohorts: IServiceCohorts = { } }, + fetchBiologyList: async (options, groupId, signal) => { + const { + deidentified, + page, + searchCriterias: { + orderBy, + searchInput, + filters: { validatedStatus, nda, ipp, loinc, anabio, startDate, endDate, executiveUnits, encounterStatus } + } + } = options + try { + const atLeastAFilter = + !!searchInput || + !!ipp || + !!nda || + !!startDate || + !!endDate || + executiveUnits.length > 0 || + encounterStatus.length > 0 || + (loinc && loinc.length > 0) || + (anabio && anabio.length > 0) + + const [biologyList, allBiologyList] = await Promise.all([ + fetchObservation({ + _list: groupId ? [groupId] : [], + size: 20, + offset: page ? (page - 1) * 20 : 0, + _sort: orderBy.orderBy, + sortDirection: orderBy.orderDirection, + _text: searchInput === '' ? '' : searchInput, + encounter: nda, + 'patient-identifier': ipp, + signal: signal, + executiveUnits: executiveUnits.map((unit) => unit.id), + encounterStatus: encounterStatus.map(({ id }) => id), + uniqueFacet: ['subject'], + minDate: startDate ?? '', + maxDate: endDate ?? '', + loinc: loinc.map((code) => code.id).join(), + anabio: anabio.map((code) => code.id).join(), + rowStatus: validatedStatus + }), + atLeastAFilter + ? fetchObservation({ + size: 0, + signal: signal, + _list: groupId ? [groupId] : [], + uniqueFacet: ['subject'], + rowStatus: validatedStatus + }) + : null + ]) + + const _biologyList = + getApiResponseResources(biologyList as AxiosResponse, any>) ?? [] + const filledBiologyList = await getResourceInfos(_biologyList, deidentified, groupId, signal) + + const totalBiology = biologyList?.data?.resourceType === 'Bundle' ? biologyList.data.total : 0 + const totalAllBiology = + allBiologyList?.data?.resourceType === 'Bundle' ? allBiologyList.data?.total ?? totalBiology : totalBiology + + const totalPatientBiology = + biologyList?.data?.resourceType === 'Bundle' + ? (getExtension(biologyList?.data?.meta, 'unique-subject') || { valueDecimal: 0 }).valueDecimal + : 0 + const totalAllPatientsBiology = + allBiologyList !== null + ? (getExtension(allBiologyList?.data?.meta, 'unique-subject') || { valueDecimal: 0 }).valueDecimal + : totalPatientBiology + + return { + totalBiology: totalBiology ?? 0, + totalAllBiology: totalAllBiology ?? 0, + totalPatientBiology: totalPatientBiology ?? 0, + totalAllPatientsBiology: totalAllPatientsBiology ?? 0, + biologyList: filledBiologyList as CohortObservation[] + } + } catch (error) { + console.error('Erreur lors de la récupération de la liste de Biologie :', error) + return { + totalBiology: 0, + totalAllBiology: 0, + totalPatientBiology: 0, + totalAllPatientsBiology: 0, + biologyList: [] + } + } + }, + fetchImagingList: async (options, groupId, signal) => { const { deidentified, diff --git a/src/types.ts b/src/types.ts index 618d0f738..b7a01169b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -308,6 +308,14 @@ export type MedicationData = { medicationList: CohortMedication[] } +export type BiologyData = { + totalBiology: number + totalAllBiology: number + totalPatientBiology: number + totalAllPatientsBiology: number + biologyList: CohortObservation[] +} + export type PatientData = { patient?: CohortPatient hospit?: (CohortEncounter | Encounter)[] @@ -673,6 +681,8 @@ export type CohortQuestionnaireResponse = QuestionnaireResponse & { export type CohortObservation = Observation & { serviceProvider?: string NDA?: string + IPP?: string + idPatient?: string } export type IPatientObservation = { diff --git a/src/types/searchCriterias.ts b/src/types/searchCriterias.ts index 909d04091..7e9b66b36 100644 --- a/src/types/searchCriterias.ts +++ b/src/types/searchCriterias.ts @@ -246,6 +246,7 @@ export type BiologyFilters = GenericFilter & { loinc: LabelObject[] anabio: LabelObject[] validatedStatus: boolean + ipp?: string } export type ImagingFilters = GenericFilter & { diff --git a/src/utils/fillElement.ts b/src/utils/fillElement.ts index d0d7a1491..f8fe1146c 100644 --- a/src/utils/fillElement.ts +++ b/src/utils/fillElement.ts @@ -9,11 +9,19 @@ import { ImagingStudy, MedicationAdministration, MedicationRequest, + Observation, Patient, Procedure } from 'fhir/r4' import { fetchPatient, fetchEncounter } from 'services/aphp/callApi' -import { CohortComposition, CohortImaging, CohortMedication, CohortPMSI, FHIR_API_Response } from 'types' +import { + CohortComposition, + CohortImaging, + CohortMedication, + CohortObservation, + CohortPMSI, + FHIR_API_Response +} from 'types' import { ResourceType } from 'types/requestCriterias' import { getApiResponseResources } from './apiHelpers' @@ -25,6 +33,7 @@ type ResourceToFill = | Claim | MedicationRequest | MedicationAdministration + | Observation const getPatientIdPath = (element: ResourceToFill) => { const patientIdPath = { @@ -37,7 +46,8 @@ const getPatientIdPath = (element: ResourceToFill) => { [ResourceType.MEDICATION_ADMINISTRATION]: (element as MedicationAdministration).subject?.reference?.replace( /^Patient\//, '' - ) + ), + [ResourceType.OBSERVATION]: (element as Observation).subject?.reference?.replace(/^Patient\//, '') } return patientIdPath[element.resourceType] @@ -57,7 +67,8 @@ const getEncounterIdPath = (element: ResourceToFill) => { [ResourceType.MEDICATION_ADMINISTRATION]: (element as MedicationAdministration).context?.reference?.replace( /^Encounter\//, '' - ) + ), + [ResourceType.OBSERVATION]: (element as Observation).encounter?.reference?.replace(/^Encounter\//, '') } return encounterIdPath[element.resourceType] @@ -94,6 +105,7 @@ export const getResourceInfos = async < | CohortImaging | CohortPMSI | CohortMedication + | CohortObservation >( elementEntries: T[], deidentifiedBoolean: boolean, diff --git a/src/views/Dashboard/Dashboard.tsx b/src/views/Dashboard/Dashboard.tsx index d6928b05b..ae550c076 100644 --- a/src/views/Dashboard/Dashboard.tsx +++ b/src/views/Dashboard/Dashboard.tsx @@ -20,6 +20,7 @@ import ImagingList from 'components/Dashboard/ImagingList' import { AppConfig } from 'config' import PMSIList from 'components/Dashboard/PMSIList' import MedicationList from 'components/Dashboard/MedicationList' +import BiologyList from 'components/Dashboard/BiologyList' type Tabs = { label: string; value: string; to: string; disabled: boolean | undefined } | undefined @@ -56,6 +57,7 @@ const Dashboard: React.FC<{ { label: 'Documents', value: 'documents', to: '/my-patients/documents', disabled: false }, { label: 'PMSI', value: 'pmsi', to: '/my-patients/pmsi', disabled: false }, { label: 'Médicaments', value: 'medication', to: '/my-patients/medication', disabled: false }, + { label: 'Biologie', value: 'biology', to: '/my-patients/biology', disabled: false }, ...(ODD_IMAGING ? [{ label: 'Imagerie', value: 'imaging', to: '/my-patients/imaging', disabled: false }] : []) ]) break @@ -72,6 +74,7 @@ const Dashboard: React.FC<{ { label: 'Documents cliniques', value: 'documents', to: `/cohort/${cohortId}/documents`, disabled: false }, { label: 'PMSI', value: 'pmsi', to: `/cohort/${cohortId}/pmsi`, disabled: false }, { label: 'Médicaments', value: 'medication', to: `/cohort/${cohortId}/medication`, disabled: false }, + { label: 'Biologie', value: 'biology', to: `/cohort/${cohortId}/biology`, disabled: false }, ...(ODD_IMAGING ? [{ label: 'Imagerie', value: 'imaging', to: `/cohort/${cohortId}/imaging`, disabled: false }] : []) @@ -85,6 +88,7 @@ const Dashboard: React.FC<{ { label: 'Documents cliniques', value: 'documents', to: `/cohort/new/documents`, disabled: true }, { label: 'PMSI', value: 'pmsi', to: `/cohort/new/pmsi`, disabled: false }, { label: 'Médicaments', value: 'medication', to: `/cohort/new/medication`, disabled: false }, + { label: 'Biologie', value: 'biology', to: `/cohort/new/biology`, disabled: false }, ...(ODD_IMAGING ? [{ label: 'Imagerie', value: 'imaging', to: `/cohort/new/imaging`, disabled: true }] : []) ]) break @@ -106,6 +110,7 @@ const Dashboard: React.FC<{ }, { label: 'PMSI', value: 'pmsi', to: `/perimeters/pmsi${location.search}`, disabled: false }, { label: 'Médicaments', value: 'medication', to: `/perimeters/new/medication`, disabled: false }, + { label: 'Biologie', value: 'biology', to: `/perimeters/biology${location.search}`, disabled: false }, ...(ODD_IMAGING ? [{ label: 'Imagerie', value: 'imaging', to: `/perimeters/imaging${location.search}`, disabled: false }] : []) @@ -223,11 +228,14 @@ const Dashboard: React.FC<{ )} {selectedTab === 'pmsi' && ( - + )} {selectedTab === 'medication' && ( )} + {selectedTab === 'biology' && ( + + )} {selectedTab === 'imaging' && ( )}