From fb53555bfb1183b659834d08a1ffa8303e2defcf Mon Sep 17 00:00:00 2001 From: Mary McGrath Date: Mon, 18 Nov 2024 12:54:04 -0500 Subject: [PATCH] fix: lab value display --- .../src/app/services/formatService.tsx | 39 +++++++++++++++---- .../src/app/services/labsService.tsx | 28 +++++++------ .../ecr-viewer/src/app/tests/utils.test.tsx | 8 ++++ .../app/view-data/components/DataDisplay.tsx | 2 +- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/containers/ecr-viewer/src/app/services/formatService.tsx b/containers/ecr-viewer/src/app/services/formatService.tsx index 2a2edcb32f..ac81a1a090 100644 --- a/containers/ecr-viewer/src/app/services/formatService.tsx +++ b/containers/ecr-viewer/src/app/services/formatService.tsx @@ -2,6 +2,7 @@ import React from "react"; import { ToolTipElement } from "@/app/view-data/components/ToolTipElement"; import { ContactPoint } from "fhir/r4"; import { sanitizeAndMap } from "../view-data/utils/utils"; +import parse from "html-react-parser"; interface Metadata { [key: string]: string; @@ -26,6 +27,8 @@ export interface TableJson { tables?: TableRow[][]; } +export type TableValue = string | React.JSX.Element | React.JSX.Element[]; + /** * Formats a person's name using given name(s), family name, optional prefix(es), and optional suffix(es). * @param given - Optional array of given name(s). @@ -359,9 +362,7 @@ export function formatTablesToJSON(htmlString: string): TableJson[] { const tables: any[] = []; const resultId = getDataId(li); const firstChildNode = getFirstNonCommentChild(li); - const resultName = firstChildNode - ? getElementContent(firstChildNode) - : ""; + const resultName = firstChildNode ? getElementText(firstChildNode) : ""; li.querySelectorAll("table").forEach((table) => { tables.push(processTable(table)); }); @@ -375,7 +376,7 @@ export function formatTablesToJSON(htmlString: string): TableJson[] { const tableWithCaptionArray = doc.querySelectorAll("table:has(caption)"); if (tableWithCaptionArray.length > 0) { doc.querySelectorAll("table").forEach((table) => { - const resultName = getElementContent(table.caption as Node); + const resultName = getElementText(table.caption as Element); const resultId = getDataId(table) ?? undefined; jsonArray.push({ resultId, resultName, tables: [processTable(table)] }); }); @@ -387,7 +388,7 @@ export function formatTablesToJSON(htmlString: string): TableJson[] { const contentArray = doc.querySelectorAll("content"); if (contentArray.length > 0) { contentArray.forEach((content) => { - const resultName = getElementContent(content); + const resultName = getElementText(content); const tables: any[] = []; let sibling = content.nextElementSibling; @@ -457,7 +458,7 @@ function processTable(table: Element): TableRow[] { if (headers.length > 0) { hasHeaders = true; headers.forEach((header) => { - keys.push(getElementContent(header)); + keys.push(getElementText(header)); }); } @@ -489,8 +490,30 @@ function processTable(table: Element): TableRow[] { return jsonArray; } -function getElementContent(el: Node): string { - return sanitizeAndMap(el.textContent?.trim() ?? ""); +/** + * Extracts the html content from an element and sanitizes and maps it so it is safe to render. + * @param el - An HTML element or node. + * @returns A sanitized and parsed snippet of JSX. + * @example @param el - Values here + * @example @returns -

Values here

+ */ +function getElementContent(el: Element | Node): TableValue { + const rawValue = (el as Element)?.innerHTML ?? el.textContent; + const value = rawValue?.trim() ?? ""; + if (value === "") return value; + const res = parse(sanitizeAndMap(value)); + return res; +} + +/** + * Extracts the text content from an element and concatenates it. + * @param el - An HTML element or node. + * @returns A string with the text data. + * @example @param el - Values here + * @example @returns - 'Values here' + */ +function getElementText(el: Element | Node): string { + return el.textContent?.trim() ?? ""; } /** diff --git a/containers/ecr-viewer/src/app/services/labsService.tsx b/containers/ecr-viewer/src/app/services/labsService.tsx index 60c69a8851..eb4abd1809 100644 --- a/containers/ecr-viewer/src/app/services/labsService.tsx +++ b/containers/ecr-viewer/src/app/services/labsService.tsx @@ -15,6 +15,7 @@ import { formatAddress, formatPhoneNumber, TableJson, + TableValue, } from "@/app/services/formatService"; import { Coding, ObservationComponent } from "fhir/r4b"; import EvaluateTable, { @@ -119,15 +120,18 @@ export const checkAbnormalTag = (labReportJson: TableJson): boolean => { * @example result - JSON object that contains the tables for all lab reports * @example searchKey - Ex. "Analysis Time" or the field that we are searching data for. */ -export function searchResultRecord(result: any[], searchKey: string) { - let resultsArray: any[] = []; +export function searchResultRecord( + result: any[], + searchKey: string, +): TableValue[] { + let resultsArray: TableValue[] = []; // Loop through each table for (const table of result) { // For each table, recursively search through all nodes if (Array.isArray(table)) { - const nestedResult: string = searchResultRecord(table, searchKey); - if (nestedResult) { + const nestedResult = searchResultRecord(table, searchKey); + if (nestedResult.length > 0) { return nestedResult; } } else { @@ -145,7 +149,7 @@ export function searchResultRecord(result: any[], searchKey: string) { } } } - return [...new Set(resultsArray)].join(", "); + return [...new Set(resultsArray)]; } /** @@ -229,7 +233,7 @@ const returnReceivedTime = ( export const returnFieldValueFromLabHtmlString = ( labReportJson: TableJson, fieldName: string, -): React.ReactNode => { +): React.ReactNode | TableValue[] => { if (!labReportJson) { return noData; } @@ -259,11 +263,13 @@ const returnAnalysisTime = ( return noData; } - const analysisTimeArray = - typeof fieldVals === "string" ? fieldVals.split(", ") : []; - const analysisTimeArrayFormatted = analysisTimeArray.map((dateTime) => { - return formatDateTime(dateTime); - }); + const analysisTimeArrayFormatted = (fieldVals as TableValue[]).map( + (dateTime) => { + const dateTimeNode = parse(`

${dateTime}

`); + console.log({ dateTime, typeofdate: typeof dateTimeNode, dateTimeNode }); + return formatDateTime(dateTime); + }, + ); return [...new Set(analysisTimeArrayFormatted)].join(", "); }; diff --git a/containers/ecr-viewer/src/app/tests/utils.test.tsx b/containers/ecr-viewer/src/app/tests/utils.test.tsx index 6432306001..b38b9dc643 100644 --- a/containers/ecr-viewer/src/app/tests/utils.test.tsx +++ b/containers/ecr-viewer/src/app/tests/utils.test.tsx @@ -565,5 +565,13 @@ describe("Utils", () => { `

hi there

I'm content`, ); }); + + it("should remove comments", () => { + const html = `hi thereI'm contentonetwo`; + const actual = sanitizeAndMap(html); + expect(actual).toBe( + `

hi there

I'm content`, + ); + }); }); }); diff --git a/containers/ecr-viewer/src/app/view-data/components/DataDisplay.tsx b/containers/ecr-viewer/src/app/view-data/components/DataDisplay.tsx index 565a7a7aae..36d6b94493 100644 --- a/containers/ecr-viewer/src/app/view-data/components/DataDisplay.tsx +++ b/containers/ecr-viewer/src/app/view-data/components/DataDisplay.tsx @@ -41,7 +41,7 @@ export const DataDisplay: React.FC<{