import React, { useCallback, useEffect, useRef, useMemo } from 'react'; import classNames from 'classnames'; import { showModal } from '@openmrs/esm-framework'; import { Grid } from './grid.component'; import { makeThrottled } from '../helpers'; import type { TimelineCellProps, DataRowsProps, NewRowStartCellProps, TimelineDataGroupProps, } from './grouped-timeline-types'; import { getMostRecentObservationWithRange, formatRangeWithUnits } from './reference-range-helpers'; import styles from './grouped-timeline.scss'; export const ShadowBox: React.FC = () =>
; const TimeSlots: React.FC<{ children?: React.ReactNode; style?: React.CSSProperties; className?: string; }> = ({ children = undefined, className, ...props }) => (
{children}
); function usePanelDates(subRows: any[]) { return useMemo(() => { const allTimes = [ ...new Set( subRows .filter((row) => row?.entries && Array.isArray(row.entries)) .map((row) => row.entries.filter((entry) => entry).map((entry) => entry.obsDatetime)) .flat(), ), ]; allTimes.sort((a, b) => new Date(b).getTime() - new Date(a).getTime()); const yearColumns: Array<{ year: string; size: number }> = []; const dayColumns: Array<{ year: string; day: string; size: number }> = []; const timeColumns: string[] = []; allTimes.forEach((datetime) => { const parsedDate = new Date(datetime); const year = parsedDate.getFullYear().toString(); const date = parsedDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', }); const time = parsedDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true, }); const yearColumn = yearColumns.find(({ year: innerYear }) => year === innerYear); if (yearColumn) yearColumn.size++; else yearColumns.push({ year, size: 1 }); const dayColumn = dayColumns.find( ({ year: innerYear, day: innerDay }) => date === innerDay && year === innerYear, ); if (dayColumn) dayColumn.size++; else dayColumns.push({ day: date, year, size: 1 }); timeColumns.push(time); }); return { yearColumns, dayColumns, timeColumns, sortedTimes: allTimes }; }, [subRows]); } const PanelHeader: React.FC<{ panelName: string; panelDates: ReturnType; inOverlay?: boolean; }> = ({ panelName, panelDates, inOverlay }) => { return (
{panelName} {panelDates.yearColumns.map(({ year, size }) => ( {year} ))} {panelDates.dayColumns.map(({ day, year, size }) => ( {day} ))} {panelDates.timeColumns.map((time, i) => ( {time} ))}
); }; const NewRowStartCell: React.FC = ({ title, range, units, conceptUuid, patientUuid, shadow = false, isString = false, zebra = false, }) => { const handleLaunchResultsModal = useCallback(() => { const dispose = showModal('timeline-results-modal', { closeDeleteModal: () => dispose(), patientUuid, testUuid: conceptUuid, title, }); }, [patientUuid, conceptUuid, title]); const rangeUnitsDisplay = formatRangeWithUnits(range, units); return (
{!isString ? ( {title} ) : ( {title} )} {rangeUnitsDisplay}
); }; const interpretationToCSS = { OFF_SCALE_HIGH: 'offScaleHigh', CRITICALLY_HIGH: 'criticallyHigh', HIGH: 'high', OFF_SCALE_LOW: 'offScaleLow', CRITICALLY_LOW: 'criticallyLow', LOW: 'low', NORMAL: '', }; const TimelineCell: React.FC = ({ text, interpretation = 'NORMAL', zebra }) => { const additionalClassname: string = interpretationToCSS[interpretation] ? styles[interpretationToCSS[interpretation]] : ''; return (

{text}

); }; const GridItems = React.memo<{ sortedTimes: Array; obs: any; zebra: boolean; }>(({ sortedTimes, obs, zebra }) => ( <> {sortedTimes.map((time, i) => { const entry = obs.find((o: any) => o?.obsDatetime === time); if (!entry) { return ; } return ; })} )); const DataRows: React.FC = ({ patientUuid, timeColumns, rowData, sortedTimes, showShadow }) => { return ( {rowData.map((row, index) => { const obs = row.entries; const { obs: values } = row; const isString = isNaN(parseFloat(values?.[0]?.value)); // Note: Units are only at the concept/node level, not observation-level const mostRecentObsWithRange = getMostRecentObservationWithRange(row.entries); const displayRange = mostRecentObsWithRange?.range ?? row.range ?? ''; const displayUnits = row.units ?? ''; return ( ); })} ); }; export default function TimelineDataGroup({ patientUuid, parent, subRows, xScroll, setXScroll, inOverlay, }: TimelineDataGroupProps) { const panelDates = usePanelDates(subRows); const ref = useRef(); const el: HTMLElement | null = ref.current; if (el) { el.scrollLeft = xScroll; } useEffect(() => { const handleScroll = makeThrottled((e) => { setXScroll(e.target.scrollLeft); }, 200); const div: HTMLElement | null = ref.current; if (div) { div.addEventListener('scroll', handleScroll); return () => div.removeEventListener('scroll', handleScroll); } }, [setXScroll]); return ( <>
); }