Skip to main content
Glama
DiagnosticReportDisplay.tsx10.1 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { Group, List, Stack, Text, Title } from '@mantine/core'; import { formatCodeableConcept, formatDateTime, formatObservationValue, isReference } from '@medplum/core'; import type { Annotation, DiagnosticReport, Observation, ObservationComponent, ObservationReferenceRange, Reference, Specimen, } from '@medplum/fhirtypes'; import { useMedplum, useResource } from '@medplum/react-hooks'; import cx from 'clsx'; import type { JSX } from 'react'; import { useEffect, useState } from 'react'; import { CodeableConceptDisplay } from '../CodeableConceptDisplay/CodeableConceptDisplay'; import { MedplumLink } from '../MedplumLink/MedplumLink'; import { NoteDisplay } from '../NoteDisplay/NoteDisplay'; import { RangeDisplay } from '../RangeDisplay/RangeDisplay'; import { ReferenceDisplay } from '../ReferenceDisplay/ReferenceDisplay'; import { ResourceBadge } from '../ResourceBadge/ResourceBadge'; import { StatusBadge } from '../StatusBadge/StatusBadge'; import classes from './DiagnosticReportDisplay.module.css'; export interface DiagnosticReportDisplayProps { readonly value?: DiagnosticReport | Reference<DiagnosticReport>; readonly hideObservationNotes?: boolean; readonly hideSpecimenInfo?: boolean; readonly hideSubject?: boolean; } DiagnosticReportDisplay.defaultProps = { hideObservationNotes: false, hideSpecimenInfo: false, hideSubject: false, } as DiagnosticReportDisplayProps; export function DiagnosticReportDisplay(props: DiagnosticReportDisplayProps): JSX.Element | null { const medplum = useMedplum(); const diagnosticReport = useResource(props.value); const [specimens, setSpecimens] = useState<Specimen[]>(); useEffect(() => { if (diagnosticReport?.specimen) { Promise.allSettled(diagnosticReport.specimen.map((ref) => medplum.readReference(ref))) .then((outcomes) => outcomes .filter((outcome) => outcome.status === 'fulfilled') .map((outcome) => (outcome as PromiseFulfilledResult<Specimen>).value) ) .then(setSpecimens) .catch(console.error); } }, [medplum, diagnosticReport]); if (!diagnosticReport) { return null; } const specimenNotes: Annotation[] = specimens?.flatMap((spec) => spec.note || []) || []; if (diagnosticReport.presentedForm && diagnosticReport.presentedForm.length > 0) { const pf = diagnosticReport.presentedForm[0]; if (pf.contentType?.startsWith('text/plain') && pf.data) { specimenNotes.push({ text: window.atob(pf.data) }); } } return ( <Stack> <Title>Diagnostic Report</Title> <DiagnosticReportHeader value={diagnosticReport} hideSubject={props.hideSubject} /> {specimens && !props.hideSpecimenInfo && SpecimenInfo(specimens)} {diagnosticReport.result && ( <ObservationTable hideObservationNotes={props.hideObservationNotes} value={diagnosticReport.result} /> )} {specimenNotes.length > 0 && <NoteDisplay value={specimenNotes} />} </Stack> ); } interface DiagnosticReportHeaderProps { readonly value: DiagnosticReport; readonly hideSubject?: boolean; } function DiagnosticReportHeader({ value, hideSubject = false }: DiagnosticReportHeaderProps): JSX.Element { return ( <Group mt="md" gap={30}> {value.subject && !hideSubject && ( <div> <Text size="xs" tt="uppercase" c="dimmed"> Subject </Text> <ResourceBadge value={value.subject} link={true} /> </div> )} {value.resultsInterpreter?.map((interpreter) => ( <div key={interpreter.reference}> <Text size="xs" tt="uppercase" c="dimmed"> Interpreter </Text> <ResourceBadge value={interpreter} link={true} /> </div> ))} {value.performer?.map((performer) => ( <div key={performer.reference}> <Text size="xs" tt="uppercase" c="dimmed"> Performer </Text> <ResourceBadge value={performer} link={true} /> </div> ))} {value.issued && ( <div> <Text size="xs" tt="uppercase" c="dimmed"> Issued </Text> <Text>{formatDateTime(value.issued)}</Text> </div> )} {value.status && ( <div> <Text size="xs" tt="uppercase" c="dimmed"> Status </Text> <StatusBadge status={value.status} /> </div> )} </Group> ); } function SpecimenInfo(specimens: Specimen[] | undefined): JSX.Element { return ( <Stack gap="xs"> <Title order={2} size="h6"> Specimens </Title> <List type="ordered"> {specimens?.map((specimen) => ( <List.Item ml="sm" key={`specimen-${specimen.id}`}> <Group gap={20}> <Group gap={5}> <Text fw={500}>Collected:</Text> {formatDateTime(specimen.collection?.collectedDateTime)} </Group> <Group gap={5}> <Text fw={500}>Received:</Text> {formatDateTime(specimen.receivedTime)} </Group> </Group> </List.Item> ))} </List> </Stack> ); } export interface ObservationTableProps { readonly value?: Observation[] | Reference<Observation>[]; readonly ancestorIds?: string[]; readonly hideObservationNotes?: boolean; } export function ObservationTable(props: ObservationTableProps): JSX.Element { return ( <table className={classes.table}> <thead> <tr> <th>Test</th> <th>Value</th> <th>Reference Range</th> <th>Interpretation</th> <th>Category</th> <th>Performer</th> <th>Status</th> </tr> </thead> <tbody> <ObservationRowGroup value={props.value} ancestorIds={props.ancestorIds} hideObservationNotes={props.hideObservationNotes} /> </tbody> </table> ); } interface ObservationRowGroupProps { readonly value?: Observation[] | Reference<Observation>[]; readonly ancestorIds?: string[]; readonly hideObservationNotes?: boolean; } function ObservationRowGroup(props: ObservationRowGroupProps): JSX.Element { return ( <> {props.value?.map((observation) => ( <ObservationRow key={`obs-${isReference(observation) ? observation.reference : observation.id}`} value={observation} ancestorIds={props.ancestorIds} hideObservationNotes={props.hideObservationNotes} /> ))} </> ); } interface ObservationRowProps { readonly value: Observation | Reference<Observation>; readonly ancestorIds?: string[]; readonly hideObservationNotes?: boolean; } function ObservationRow(props: ObservationRowProps): JSX.Element | null { const observation = useResource(props.value); if (!observation || props.ancestorIds?.includes(observation.id as string)) { return null; } const displayNotes = !props.hideObservationNotes && observation.note; const critical = isCritical(observation); return ( <> <tr className={cx({ [classes.criticalRow]: critical })}> <td rowSpan={displayNotes ? 2 : 1}> <MedplumLink to={observation}> <CodeableConceptDisplay value={observation.code} /> </MedplumLink> </td> <td> <ObservationValueDisplay value={observation} /> </td> <td> <ReferenceRangeDisplay value={observation.referenceRange} /> </td> <td> {observation.interpretation && observation.interpretation.length > 0 && ( <CodeableConceptDisplay value={observation.interpretation[0]} /> )} </td> <td> {observation.category && observation.category.length > 0 && ( <> {observation.category.map((concept) => ( <div key={`category-${formatCodeableConcept(concept)}`}> <CodeableConceptDisplay value={concept} /> </div> ))} </> )} </td> <td> {observation.performer?.map((performer) => ( <ReferenceDisplay key={performer.reference} value={performer} /> ))} </td> <td>{observation.status && <StatusBadge status={observation.status} />}</td> </tr> {observation.hasMember && ( <ObservationRowGroup value={observation.hasMember as Reference<Observation>[]} ancestorIds={ props.ancestorIds ? [...props.ancestorIds, observation.id as string] : [observation.id as string] } hideObservationNotes={props.hideObservationNotes} /> )} {displayNotes && ( <tr> <td colSpan={6}> <NoteDisplay value={observation.note} /> </td> </tr> )} </> ); } interface ObservationValueDisplayProps { readonly value?: Observation | ObservationComponent; } function ObservationValueDisplay(props: ObservationValueDisplayProps): JSX.Element | null { const obs = props.value; return <>{formatObservationValue(obs)}</>; } interface ReferenceRangeProps { readonly value?: ObservationReferenceRange[]; } function ReferenceRangeDisplay(props: ReferenceRangeProps): JSX.Element | null { const range = props.value && props.value.length > 0 && props.value[0]; if (!range) { return null; } if (range.text) { return <>{range.text}</>; } return <RangeDisplay value={range} />; } /** * Returns true if the observation is critical. * See: https://www.hl7.org/fhir/valueset-observation-interpretation.html * @param observation - The FHIR observation. * @returns True if the FHIR observation is a critical value. */ function isCritical(observation: Observation): boolean { const code = observation.interpretation?.[0]?.coding?.[0]?.code; return code === 'AA' || code === 'LL' || code === 'HH' || code === 'A'; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server