Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added biology view to dashboard - Ref gestion-de-projet#2678 #1049

Merged
merged 1 commit into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
496 changes: 496 additions & 0 deletions src/components/Dashboard/BiologyList/index.tsx

Large diffs are not rendered by default.

49 changes: 38 additions & 11 deletions src/components/DataTable/DataTableObservation.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -20,6 +22,8 @@ type DataTableObservationProps = {
page?: number
setPage?: (page: number) => void
total?: number
showIpp?: boolean
groupId?: string
}
const DataTableObservation: React.FC<DataTableObservationProps> = ({
loading,
Expand All @@ -29,32 +33,40 @@ const DataTableObservation: React.FC<DataTableObservationProps> = ({
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 (
<DataTable columns={columns} order={orderBy} setOrder={setOrderBy} page={page} setPage={setPage} total={total}>
{!loading && observationsList?.length > 0 && (
<>
{observationsList.map((observation) => (
<DataTableObservationLine key={observation.id} observation={observation} />
<DataTableObservationLine
key={observation.id}
observation={observation}
showIpp={showIpp}
groupId={groupId}
/>
))}
</>
)}
{!loading && observationsList?.length < 1 && (
<TableRow className={classes.emptyTableRow}>
<TableCellWrapper colSpan={7} align="left">
<TableCellWrapper colSpan={columns.length} align="left">
<Grid container justifyContent="center">
<Typography variant="button">Aucun résultat de biologie à afficher</Typography>
</Grid>
Expand All @@ -63,7 +75,7 @@ const DataTableObservation: React.FC<DataTableObservationProps> = ({
)}
{loading && (
<TableRow className={classes.emptyTableRow}>
<TableCellWrapper colSpan={7} align="left">
<TableCellWrapper colSpan={columns.length} align="left">
<Grid container justifyContent="center">
<CircularProgress />
</Grid>
Expand All @@ -76,17 +88,20 @@ const DataTableObservation: React.FC<DataTableObservationProps> = ({

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(
Expand All @@ -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
Expand All @@ -111,10 +126,22 @@ const DataTableObservationLine: React.FC<{
valueUnit
)}`
: '-'
const groupIdSearch = groupId ? `?groupId=${groupId}` : ''

return (
<TableRow className={classes.tableBodyRows} key={observation.id}>
<TableCellWrapper align="left">{nda ?? 'Inconnu'}</TableCellWrapper>
{showIpp && (
<TableCellWrapper style={{ minWidth: 150 }}>
{ipp}
<IconButton
onClick={() => window.open(`/patients/${observation.idPatient}${groupIdSearch}`, '_blank')}
className={classes.searchIcon}
>
<SearchIcon height="15px" fill="#ED6D91" className={classes.iconMargin} />
</IconButton>
</TableCellWrapper>
)}
<TableCellWrapper align={showIpp ? 'center' : 'left'}>{nda ?? 'Inconnu'}</TableCellWrapper>
<TableCellWrapper>{date ? new Date(date).toLocaleDateString('fr-FR') : 'Date inconnue'}</TableCellWrapper>
<TableCellWrapper>
<Typography className={classes.libelle}>
Expand Down
3 changes: 2 additions & 1 deletion src/mappers/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
13 changes: 10 additions & 3 deletions src/services/aphp/callApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Observation> => {
const {
Expand All @@ -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}`]
Expand All @@ -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)}`]

Expand Down
113 changes: 110 additions & 3 deletions src/services/aphp/serviceCohorts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import {
FHIR_Bundle_Response,
CohortPMSI,
MedicationData,
CohortMedication
CohortMedication,
BiologyData,
CohortObservation
} from 'types'
import {
getGenderRepartitionMapAphp,
Expand All @@ -40,7 +42,8 @@ import {
fetchProcedure,
fetchClaim,
fetchMedicationRequest,
fetchMedicationAdministration
fetchMedicationAdministration,
fetchObservation
} from './callApi'

import apiBackend from '../apiBackend'
Expand All @@ -52,6 +55,7 @@ import {
ImagingStudy,
MedicationAdministration,
MedicationRequest,
Observation,
ParametersParameter,
Patient,
Procedure
Expand All @@ -65,7 +69,8 @@ import {
SearchByTypes,
ImagingFilters,
PMSIFilters,
MedicationFilters
MedicationFilters,
BiologyFilters
} from 'types/searchCriterias'
import services from '.'
import { ErrorDetails, SearchInputError } from 'types/error'
Expand Down Expand Up @@ -212,6 +217,19 @@ export interface IServiceCohorts {
signal?: AbortSignal
) => Promise<MedicationData>

/**
* Retourne la liste d'objets de biologie liés à une cohorte
*/
fetchBiologyList: (
options: {
deidentified: boolean
page: number
searchCriterias: SearchCriterias<BiologyFilters>
},
groupId?: string,
signal?: AbortSignal
) => Promise<BiologyData>

/**
* Permet de vérifier si le champ de recherche textuelle est correct
*
Expand Down Expand Up @@ -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<FHIR_Bundle_Response<Observation>, 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,
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ export type MedicationData = {
medicationList: CohortMedication<MedicationRequest | MedicationAdministration>[]
}

export type BiologyData = {
totalBiology: number
totalAllBiology: number
totalPatientBiology: number
totalAllPatientsBiology: number
biologyList: CohortObservation[]
}

export type PatientData = {
patient?: CohortPatient
hospit?: (CohortEncounter | Encounter)[]
Expand Down Expand Up @@ -673,6 +681,8 @@ export type CohortQuestionnaireResponse = QuestionnaireResponse & {
export type CohortObservation = Observation & {
serviceProvider?: string
NDA?: string
IPP?: string
idPatient?: string
}

export type IPatientObservation<T extends CohortObservation> = {
Expand Down
1 change: 1 addition & 0 deletions src/types/searchCriterias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export type BiologyFilters = GenericFilter & {
loinc: LabelObject[]
anabio: LabelObject[]
validatedStatus: boolean
ipp?: string
}

export type ImagingFilters = GenericFilter & {
Expand Down
Loading
Loading