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 (
);
};
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 (
<>
>
);
}