@eka-care/medical-records-ui 1.0.43 → 1.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DocumentViewer-OM6D4CSI.mjs → DocumentViewer-5AXKL75U.mjs} +2 -2
- package/dist/{DocumentViewer-D7OJVVZM.js → DocumentViewer-Y3SQ2BMS.js} +2 -2
- package/dist/{DocumentViewer-D7OJVVZM.js.map → DocumentViewer-Y3SQ2BMS.js.map} +1 -1
- package/dist/{OriginalRecordPanel-UHNIWIT3.js → OriginalRecordPanel-SJIBGHYZ.js} +2 -2
- package/dist/{OriginalRecordPanel-UHNIWIT3.js.map → OriginalRecordPanel-SJIBGHYZ.js.map} +1 -1
- package/dist/{OriginalRecordPanel-XU6ZYBPB.mjs → OriginalRecordPanel-UM74PRBO.mjs} +2 -2
- package/dist/{PdfViewer-WIV5AUJB.mjs → PdfViewer-CG2577E4.mjs} +10 -1
- package/dist/PdfViewer-CG2577E4.mjs.map +1 -0
- package/dist/{PdfViewer-EAN6EQAB.js → PdfViewer-GIWRL6FQ.js} +12 -3
- package/dist/PdfViewer-GIWRL6FQ.js.map +1 -0
- package/dist/index.js +19 -18
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/PdfViewer-EAN6EQAB.js.map +0 -1
- package/dist/PdfViewer-WIV5AUJB.mjs.map +0 -1
- /package/dist/{DocumentViewer-OM6D4CSI.mjs.map → DocumentViewer-5AXKL75U.mjs.map} +0 -0
- /package/dist/{OriginalRecordPanel-XU6ZYBPB.mjs.map → OriginalRecordPanel-UM74PRBO.mjs.map} +0 -0
|
@@ -59,7 +59,7 @@ function TextViewer({ url }) {
|
|
|
59
59
|
|
|
60
60
|
// src/views/RecordPreview/DocumentViewer.tsx
|
|
61
61
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
62
|
-
var PdfViewer = lazy(() => import("./PdfViewer-
|
|
62
|
+
var PdfViewer = lazy(() => import("./PdfViewer-CG2577E4.mjs").then((m) => ({ default: m.PdfViewer })));
|
|
63
63
|
var PAGE_WIDTH = 595;
|
|
64
64
|
var ZOOM_STEP = 0.25;
|
|
65
65
|
var ZOOM_MIN = 0.5;
|
|
@@ -195,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
195
195
|
export {
|
|
196
196
|
DocumentViewer
|
|
197
197
|
};
|
|
198
|
-
//# sourceMappingURL=DocumentViewer-
|
|
198
|
+
//# sourceMappingURL=DocumentViewer-5AXKL75U.mjs.map
|
|
@@ -59,7 +59,7 @@ function TextViewer({ url }) {
|
|
|
59
59
|
|
|
60
60
|
// src/views/RecordPreview/DocumentViewer.tsx
|
|
61
61
|
|
|
62
|
-
var PdfViewer = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./PdfViewer-
|
|
62
|
+
var PdfViewer = _react.lazy.call(void 0, () => Promise.resolve().then(() => _interopRequireWildcard(require("./PdfViewer-GIWRL6FQ.js"))).then((m) => ({ default: m.PdfViewer })));
|
|
63
63
|
var PAGE_WIDTH = 595;
|
|
64
64
|
var ZOOM_STEP = 0.25;
|
|
65
65
|
var ZOOM_MIN = 0.5;
|
|
@@ -195,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
exports.DocumentViewer = DocumentViewer;
|
|
198
|
-
//# sourceMappingURL=DocumentViewer-
|
|
198
|
+
//# sourceMappingURL=DocumentViewer-Y3SQ2BMS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-D7OJVVZM.js","../src/views/RecordPreview/DocumentViewer.tsx","../src/views/RecordPreview/TextViewer.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-D7OJVVZM.js","sourcesContent":[null,"import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';\nimport {\n ChevronLeft,\n ChevronRight,\n Minus,\n Plus,\n Download,\n Maximize,\n Minimize,\n Globe,\n} from 'lucide-react';\nimport { TextViewer } from './TextViewer';\nimport type { PreviewFile } from '../../connection';\n\nconst PdfViewer = lazy(() => import('./PdfViewer').then((m) => ({ default: m.PdfViewer })));\n\nexport interface DocumentViewerProps {\n files: PreviewFile[];\n title: string;\n isHtml?: boolean;\n}\n\nconst PAGE_WIDTH = 595; \nconst ZOOM_STEP = 0.25;\nconst ZOOM_MIN = 0.5;\nconst ZOOM_MAX = 3;\n\nexport function DocumentViewer({ files, title, isHtml }: DocumentViewerProps) {\n const [zoom, setZoom] = useState(1);\n const [pageInfo, setPageInfo] = useState({ current: 1, total: 0 });\n const [scrollToPage, setScrollToPage] = useState(1);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onChange = () => setIsFullscreen(document.fullscreenElement === rootRef.current);\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n }, []);\n\n const toggleFullscreen = () => {\n if (document.fullscreenElement) void document.exitFullscreen();\n else void rootRef.current?.requestFullscreen?.();\n };\n\n const primary = files[0] ?? null;\n const isPdf = primary?.kind === 'pdf';\n const images = files.filter((f) => f.kind === 'image');\n const isZoomable = isPdf || images.length > 0;\n\n const zoomOut = () => setZoom((z) => Math.max(ZOOM_MIN, +(z - ZOOM_STEP).toFixed(2)));\n const zoomIn = () => setZoom((z) => Math.min(ZOOM_MAX, +(z + ZOOM_STEP).toFixed(2)));\n\n const prevPage = () => setScrollToPage(Math.max(1, pageInfo.current - 1));\n const nextPage = () => setScrollToPage(Math.min(pageInfo.total, pageInfo.current + 1));\n\n // Download / print fetch the file into a blob URL rather than re-opening the\n // signed URL directly, so it works even when the document is already loaded.\n const withBlobUrl = useCallback(async (fn: (objUrl: string) => void) => {\n if (!primary) return;\n try {\n const res = await fetch(primary.url);\n const blob = await res.blob();\n const objUrl = URL.createObjectURL(blob);\n fn(objUrl);\n // Revoke after the consumer has had a chance to use it.\n setTimeout(() => URL.revokeObjectURL(objUrl), 120_000);\n } catch {\n // Fall back to opening the original URL.\n fn(primary.url);\n }\n }, [primary]);\n\n const handleDownload = () =>\n withBlobUrl((objUrl) => {\n const a = document.createElement('a');\n a.href = objUrl;\n a.download = title || 'document';\n a.click();\n });\n\n return (\n <div className={`mr-doc-viewer${isHtml ? ' mr-doc-viewer--html' : ''}`} ref={rootRef}>\n {!isHtml && <div className=\"mr-doc-viewer__toolbar\">\n <span className=\"mr-doc-viewer__filename\">{title}</span>\n {isPdf && (\n <div className=\"mr-doc-viewer__pages\">\n <button\n type=\"button\"\n aria-label=\"Previous page\"\n onClick={prevPage}\n disabled={pageInfo.current <= 1}\n >\n <ChevronLeft size={18} aria-hidden />\n </button>\n <span className=\"mr-doc-viewer__page-badge\">{pageInfo.current}</span>\n <span>/</span>\n <span>{pageInfo.total || '—'}</span>\n <button\n type=\"button\"\n aria-label=\"Next page\"\n onClick={nextPage}\n disabled={pageInfo.current >= pageInfo.total}\n >\n <ChevronRight size={18} aria-hidden />\n </button>\n </div>\n )}\n {isZoomable && (\n <div className=\"mr-doc-viewer__zoom\">\n <button type=\"button\" aria-label=\"Zoom out\" onClick={zoomOut} disabled={zoom <= ZOOM_MIN}>\n <Minus size={20} aria-hidden />\n </button>\n <span className=\"mr-doc-viewer__zoom-badge\">{Math.round(zoom * 100)}%</span>\n <button type=\"button\" aria-label=\"Zoom in\" onClick={zoomIn} disabled={zoom >= ZOOM_MAX}>\n <Plus size={20} aria-hidden />\n </button>\n </div>\n )}\n <div className=\"mr-doc-viewer__actions\">\n <button type=\"button\" aria-label=\"Download\" onClick={handleDownload} disabled={!primary}>\n <Download size={20} aria-hidden />\n </button>\n <button\n type=\"button\"\n aria-label={isFullscreen ? 'Exit full screen' : 'Full screen'}\n onClick={toggleFullscreen}\n >\n {isFullscreen ? <Minimize size={20} aria-hidden /> : <Maximize size={20} aria-hidden />}\n </button>\n </div>\n </div>}\n\n {isHtml && (\n <div className=\"mr-doc-viewer__html-bar\">\n <div className=\"mr-doc-viewer__html-bar-left\">\n <Globe size={12} aria-hidden />\n <span>Previewing HTML file – you can copy to a note and edit this file. The original record that you are viewing now will not be affected.</span>\n </div>\n <button\n type=\"button\"\n className=\"mr-doc-viewer__html-download\"\n aria-label=\"Download\"\n onClick={handleDownload}\n disabled={!primary}\n >\n <Download size={16} aria-hidden />\n </button>\n </div>\n )}\n\n <div className=\"mr-doc-viewer__canvas\">\n {!primary ? (\n <div className=\"mr-doc-viewer__status\">No preview available for this record.</div>\n ) : isPdf ? (\n <Suspense fallback={<div className=\"mr-doc-viewer__status\">Loading viewer…</div>}>\n <PdfViewer\n url={primary.url}\n baseWidth={PAGE_WIDTH}\n scale={zoom}\n scrollToPage={scrollToPage}\n onPageInfo={setPageInfo}\n />\n </Suspense>\n ) : images.length > 0 ? (\n images.map((img, i) => (\n <img\n key={i}\n className=\"mr-doc-viewer__image\"\n src={img.url}\n alt={`${title} ${i + 1}`}\n style={{ transform: `scale(${zoom})`, transformOrigin: 'top center' }}\n />\n ))\n ) : (\n <TextViewer url={primary.url} />\n )}\n </div>\n </div>\n );\n}\n","import { useEffect, useState } from 'react';\n\nexport interface TextViewerProps {\n url: string;\n}\n\nexport function TextViewer({ url }: TextViewerProps) {\n const [state, setState] = useState<{ status: 'loading' | 'loaded' | 'error'; text: string }>({\n status: 'loading',\n text: '',\n });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: 'loading', text: '' });\n (async () => {\n try {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`fetch ${res.status}`);\n const text = await res.text();\n if (text.trimStart().startsWith('%PDF')) throw new Error('binary');\n if (!cancelled) setState({ status: 'loaded', text });\n } catch {\n if (!cancelled) setState({ status: 'error', text: '' });\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [url]);\n\n if (state.status === 'loading') {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>Loading document...</div>;\n }\n if (state.status === 'error') {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>Couldn't load this document.</div>;\n }\n if (!state.text.trim()) {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>This record appears to be empty.</div>;\n }\n return (\n <iframe\n className=\"mr-doc-viewer__html-frame\"\n srcDoc={state.text}\n sandbox=\"allow-same-origin\"\n title=\"HTML preview\"\n />\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-Y3SQ2BMS.js","../src/views/RecordPreview/DocumentViewer.tsx","../src/views/RecordPreview/TextViewer.tsx"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-Y3SQ2BMS.js","sourcesContent":[null,"import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react';\nimport {\n ChevronLeft,\n ChevronRight,\n Minus,\n Plus,\n Download,\n Maximize,\n Minimize,\n Globe,\n} from 'lucide-react';\nimport { TextViewer } from './TextViewer';\nimport type { PreviewFile } from '../../connection';\n\nconst PdfViewer = lazy(() => import('./PdfViewer').then((m) => ({ default: m.PdfViewer })));\n\nexport interface DocumentViewerProps {\n files: PreviewFile[];\n title: string;\n isHtml?: boolean;\n}\n\nconst PAGE_WIDTH = 595; \nconst ZOOM_STEP = 0.25;\nconst ZOOM_MIN = 0.5;\nconst ZOOM_MAX = 3;\n\nexport function DocumentViewer({ files, title, isHtml }: DocumentViewerProps) {\n const [zoom, setZoom] = useState(1);\n const [pageInfo, setPageInfo] = useState({ current: 1, total: 0 });\n const [scrollToPage, setScrollToPage] = useState(1);\n const [isFullscreen, setIsFullscreen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onChange = () => setIsFullscreen(document.fullscreenElement === rootRef.current);\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n }, []);\n\n const toggleFullscreen = () => {\n if (document.fullscreenElement) void document.exitFullscreen();\n else void rootRef.current?.requestFullscreen?.();\n };\n\n const primary = files[0] ?? null;\n const isPdf = primary?.kind === 'pdf';\n const images = files.filter((f) => f.kind === 'image');\n const isZoomable = isPdf || images.length > 0;\n\n const zoomOut = () => setZoom((z) => Math.max(ZOOM_MIN, +(z - ZOOM_STEP).toFixed(2)));\n const zoomIn = () => setZoom((z) => Math.min(ZOOM_MAX, +(z + ZOOM_STEP).toFixed(2)));\n\n const prevPage = () => setScrollToPage(Math.max(1, pageInfo.current - 1));\n const nextPage = () => setScrollToPage(Math.min(pageInfo.total, pageInfo.current + 1));\n\n // Download / print fetch the file into a blob URL rather than re-opening the\n // signed URL directly, so it works even when the document is already loaded.\n const withBlobUrl = useCallback(async (fn: (objUrl: string) => void) => {\n if (!primary) return;\n try {\n const res = await fetch(primary.url);\n const blob = await res.blob();\n const objUrl = URL.createObjectURL(blob);\n fn(objUrl);\n // Revoke after the consumer has had a chance to use it.\n setTimeout(() => URL.revokeObjectURL(objUrl), 120_000);\n } catch {\n // Fall back to opening the original URL.\n fn(primary.url);\n }\n }, [primary]);\n\n const handleDownload = () =>\n withBlobUrl((objUrl) => {\n const a = document.createElement('a');\n a.href = objUrl;\n a.download = title || 'document';\n a.click();\n });\n\n return (\n <div className={`mr-doc-viewer${isHtml ? ' mr-doc-viewer--html' : ''}`} ref={rootRef}>\n {!isHtml && <div className=\"mr-doc-viewer__toolbar\">\n <span className=\"mr-doc-viewer__filename\">{title}</span>\n {isPdf && (\n <div className=\"mr-doc-viewer__pages\">\n <button\n type=\"button\"\n aria-label=\"Previous page\"\n onClick={prevPage}\n disabled={pageInfo.current <= 1}\n >\n <ChevronLeft size={18} aria-hidden />\n </button>\n <span className=\"mr-doc-viewer__page-badge\">{pageInfo.current}</span>\n <span>/</span>\n <span>{pageInfo.total || '—'}</span>\n <button\n type=\"button\"\n aria-label=\"Next page\"\n onClick={nextPage}\n disabled={pageInfo.current >= pageInfo.total}\n >\n <ChevronRight size={18} aria-hidden />\n </button>\n </div>\n )}\n {isZoomable && (\n <div className=\"mr-doc-viewer__zoom\">\n <button type=\"button\" aria-label=\"Zoom out\" onClick={zoomOut} disabled={zoom <= ZOOM_MIN}>\n <Minus size={20} aria-hidden />\n </button>\n <span className=\"mr-doc-viewer__zoom-badge\">{Math.round(zoom * 100)}%</span>\n <button type=\"button\" aria-label=\"Zoom in\" onClick={zoomIn} disabled={zoom >= ZOOM_MAX}>\n <Plus size={20} aria-hidden />\n </button>\n </div>\n )}\n <div className=\"mr-doc-viewer__actions\">\n <button type=\"button\" aria-label=\"Download\" onClick={handleDownload} disabled={!primary}>\n <Download size={20} aria-hidden />\n </button>\n <button\n type=\"button\"\n aria-label={isFullscreen ? 'Exit full screen' : 'Full screen'}\n onClick={toggleFullscreen}\n >\n {isFullscreen ? <Minimize size={20} aria-hidden /> : <Maximize size={20} aria-hidden />}\n </button>\n </div>\n </div>}\n\n {isHtml && (\n <div className=\"mr-doc-viewer__html-bar\">\n <div className=\"mr-doc-viewer__html-bar-left\">\n <Globe size={12} aria-hidden />\n <span>Previewing HTML file – you can copy to a note and edit this file. The original record that you are viewing now will not be affected.</span>\n </div>\n <button\n type=\"button\"\n className=\"mr-doc-viewer__html-download\"\n aria-label=\"Download\"\n onClick={handleDownload}\n disabled={!primary}\n >\n <Download size={16} aria-hidden />\n </button>\n </div>\n )}\n\n <div className=\"mr-doc-viewer__canvas\">\n {!primary ? (\n <div className=\"mr-doc-viewer__status\">No preview available for this record.</div>\n ) : isPdf ? (\n <Suspense fallback={<div className=\"mr-doc-viewer__status\">Loading viewer…</div>}>\n <PdfViewer\n url={primary.url}\n baseWidth={PAGE_WIDTH}\n scale={zoom}\n scrollToPage={scrollToPage}\n onPageInfo={setPageInfo}\n />\n </Suspense>\n ) : images.length > 0 ? (\n images.map((img, i) => (\n <img\n key={i}\n className=\"mr-doc-viewer__image\"\n src={img.url}\n alt={`${title} ${i + 1}`}\n style={{ transform: `scale(${zoom})`, transformOrigin: 'top center' }}\n />\n ))\n ) : (\n <TextViewer url={primary.url} />\n )}\n </div>\n </div>\n );\n}\n","import { useEffect, useState } from 'react';\n\nexport interface TextViewerProps {\n url: string;\n}\n\nexport function TextViewer({ url }: TextViewerProps) {\n const [state, setState] = useState<{ status: 'loading' | 'loaded' | 'error'; text: string }>({\n status: 'loading',\n text: '',\n });\n\n useEffect(() => {\n let cancelled = false;\n setState({ status: 'loading', text: '' });\n (async () => {\n try {\n const res = await fetch(url);\n if (!res.ok) throw new Error(`fetch ${res.status}`);\n const text = await res.text();\n if (text.trimStart().startsWith('%PDF')) throw new Error('binary');\n if (!cancelled) setState({ status: 'loaded', text });\n } catch {\n if (!cancelled) setState({ status: 'error', text: '' });\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [url]);\n\n if (state.status === 'loading') {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>Loading document...</div>;\n }\n if (state.status === 'error') {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>Couldn't load this document.</div>;\n }\n if (!state.text.trim()) {\n return <div className=\"mr-doc-viewer__status\" style={{ color: 'var(--mr-muted-foreground)' }}>This record appears to be empty.</div>;\n }\n return (\n <iframe\n className=\"mr-doc-viewer__html-frame\"\n srcDoc={state.text}\n sandbox=\"allow-same-origin\"\n title=\"HTML preview\"\n />\n );\n}\n"]}
|
|
@@ -23,7 +23,7 @@ var _lucidereact = require('lucide-react');
|
|
|
23
23
|
var _zustand = require('zustand');
|
|
24
24
|
var _jsxruntime = require('react/jsx-runtime');
|
|
25
25
|
var DocumentViewer = _react.lazy.call(void 0,
|
|
26
|
-
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-
|
|
26
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-Y3SQ2BMS.js"))).then((m) => ({
|
|
27
27
|
default: m.DocumentViewer
|
|
28
28
|
}))
|
|
29
29
|
);
|
|
@@ -607,4 +607,4 @@ function VitalReadingsExpanded({ vitalName, ekaId }) {
|
|
|
607
607
|
|
|
608
608
|
|
|
609
609
|
exports.OriginalRecordPanel = OriginalRecordPanel;
|
|
610
|
-
//# sourceMappingURL=OriginalRecordPanel-
|
|
610
|
+
//# sourceMappingURL=OriginalRecordPanel-SJIBGHYZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/OriginalRecordPanel-UHNIWIT3.js","../src/views/components/shared/OriginalRecordPanel.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/medical-records-ui/medical-records-ui/dist/OriginalRecordPanel-UHNIWIT3.js","sourcesContent":[null,"\"use client\";\n\nimport { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ChevronLeft, ChevronRight, ChevronDown, ExternalLink, Check, File, LoaderCircle } from \"lucide-react\";\nimport { useStore } from \"zustand\";\nimport { useSdk } from \"../../../connection/useSdk\";\nimport { useRecordPreview } from \"../../../connection/useRecordPreview\";\nimport { useRecordPanelTable } from \"../../hooks/care-panel/useRecordPanelTable\";\nimport { useParameterTrend } from \"../../hooks/care-panel/useParameterTrend\";\nimport { epochToDisplay } from \"../../../helpers/formatters\";\nimport type { ParameterTrendResponse } from \"@eka-care/medical-records-ts-sdk\";\nimport { useSmartRecordsActions } from \"../../context/SmartRecordsActionsContext\";\nimport { IconButton, Typography, SearchInput, Tooltip } from \"widgets\";\nimport type { RecordItem } from \"../../../helpers\";\nimport type { PanelRow } from \"../../../helpers/vitalsPanel\";\nimport SmartSparkles from \"icons/SmartSparkles\";\n\nconst DocumentViewer = lazy(() =>\n import(\"../../RecordPreview/DocumentViewer\").then((m) => ({\n default: m.DocumentViewer,\n })),\n);\n\ninterface OriginalRecordPanelProps {\n recordId: string;\n caseId?: string;\n onBack: () => void;\n}\n\ntype SrTab = \"all\" | \"oor\" | \"none\";\n\nfunction buildVitalsText(rows: PanelRow[]): string {\n return rows\n .map((r) => {\n const cell = r.cells[0];\n const ref = cell?.refRange ? ` (Ref: ${cell.refRange})` : \"\";\n return `${r.name}: ${cell?.value ?? \"—\"} ${r.unit}${ref}`;\n })\n .join(\"\\n\");\n}\n\nexport function OriginalRecordPanel({\n recordId,\n caseId,\n onBack,\n}: OriginalRecordPanelProps) {\n const { store } = useSdk();\n const record = useStore(store, (s) => s.records.byId[recordId]);\n const recordsById = useStore(store, (s) => s.records.byId);\n const preview = useRecordPreview(recordId);\n const { replaceOriginal, onCopyToNote, onCopyVitalsToNote, hasCopyToNote, onToast } =\n useSmartRecordsActions();\n\n // Smart report vitals (only fetched when record.isSmart)\n const { status: vitalsStatus, table } = useRecordPanelTable(\n record?.isSmart ? recordId : null,\n );\n const [smartPanelOpen, setSmartPanelOpen] = useState(true);\n const [srSearch, setSrSearch] = useState(\"\");\n const [srTab, setSrTab] = useState<SrTab>(\"all\");\n const [expandedVital, setExpandedVital] = useState<string | null>(null);\n const toggleExpand = useCallback((key: string) => {\n setExpandedVital((prev) => (prev === key ? null : key));\n }, []);\n const [srSelected, setSrSelected] = useState<Set<string>>(new Set());\n const srCopyBtnRef = useRef<HTMLButtonElement>(null);\n\n // Dropdown state\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [dropdownOpen]);\n\n const caseRecords = useMemo<RecordItem[]>(() => {\n if (!caseId) return [];\n return Object.values(recordsById)\n .filter(\n (r): r is RecordItem =>\n r != null &&\n (caseId === \"__ungrouped__\" ? r.cases.length === 0 : r.cases.includes(caseId)),\n )\n .sort((a, b) => b.createdAtEpoch - a.createdAtEpoch);\n }, [recordsById, caseId]);\n\n const currentIndex = caseRecords.findIndex((r) => r.id === recordId);\n const total = caseRecords.length;\n const isUngrouped = caseId === \"__ungrouped__\";\n\n const goTo = useCallback(\n (r: RecordItem) => {\n setDropdownOpen(false);\n replaceOriginal(r.id, caseId);\n },\n [replaceOriginal, caseId],\n );\n\n const goPrev = useCallback(() => {\n if (currentIndex > 0) goTo(caseRecords[currentIndex - 1]);\n }, [currentIndex, caseRecords, goTo]);\n\n const goNext = useCallback(() => {\n if (currentIndex < total - 1) goTo(caseRecords[currentIndex + 1]);\n }, [currentIndex, total, caseRecords, goTo]);\n\n const showNavPicker = caseId && total > 0;\n\n // Smart report vitals filtering\n const allRows = table.rows;\n const oorRows = useMemo(\n () => allRows.filter((r) => r.latestStatus === \"high\" || r.latestStatus === \"low\"),\n [allRows],\n );\n const noInterpRows = useMemo(\n () => allRows.filter((r) => r.latestStatus === \"unknown\"),\n [allRows],\n );\n\n const baseRows = srTab === \"oor\" ? oorRows : srTab === \"none\" ? noInterpRows : allRows;\n\n const filteredRows = useMemo(() => {\n if (!srSearch) return baseRows;\n const q = srSearch.toLowerCase();\n return baseRows.filter((r) => r.name.toLowerCase().includes(q));\n }, [baseRows, srSearch]);\n\n const tabCounts = useMemo(() => {\n if (!srSearch) return { all: allRows.length, oor: oorRows.length, none: noInterpRows.length };\n const q = srSearch.toLowerCase();\n return {\n all: allRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n oor: oorRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n none: noInterpRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n };\n }, [srSearch, allRows, oorRows, noInterpRows]);\n\n useEffect(() => {\n setSrSelected(new Set());\n }, [srTab, srSearch]);\n\n useEffect(() => {\n if (srTab === \"oor\" && oorRows.length === 0) setSrTab(\"all\");\n if (srTab === \"none\" && noInterpRows.length === 0) setSrTab(\"all\");\n }, [srTab, oorRows.length, noInterpRows.length]);\n\n const srAllSelected =\n filteredRows.length > 0 && filteredRows.every((r) => srSelected.has(r.key));\n const srSomeSelected = srSelected.size > 0;\n\n const toggleSrRow = useCallback((key: string) => {\n setSrSelected((prev) => {\n const next = new Set(prev);\n next.has(key) ? next.delete(key) : next.add(key);\n return next;\n });\n }, []);\n\n const toggleSrAll = useCallback(() => {\n setSrSelected(\n srAllSelected ? new Set() : new Set(filteredRows.map((r) => r.key)),\n );\n }, [srAllSelected, filteredRows]);\n\n const handleSrCopyToNote = useCallback(() => {\n const selectedRows = filteredRows.filter((r) => srSelected.has(r.key));\n if (!selectedRows.length) return;\n const text = buildVitalsText(selectedRows);\n if (onCopyVitalsToNote) {\n void onCopyVitalsToNote(text);\n } else if (onCopyToNote) {\n const rect = srCopyBtnRef.current?.getBoundingClientRect();\n void onCopyToNote(\n text,\n rect ? { x: rect.left + rect.width / 2, y: rect.bottom } : undefined,\n );\n }\n }, [filteredRows, srSelected, onCopyVitalsToNote, onCopyToNote]);\n\n const htmlCopyBtnRef = useRef<HTMLButtonElement>(null);\n const [isHtmlCopying, setIsHtmlCopying] = useState(false);\n\n const handleHtmlCopyToNote = useCallback(async () => {\n const url = preview.files[0]?.url;\n if (!url) return;\n setIsHtmlCopying(true);\n try {\n const res = await fetch(url);\n const html = await res.text();\n const doc = new DOMParser().parseFromString(html, \"text/html\");\n const text = doc.body.textContent?.trim() ?? \"\";\n if (!text) return;\n if (onCopyVitalsToNote) {\n void onCopyVitalsToNote(text);\n } else if (onCopyToNote) {\n const rect = htmlCopyBtnRef.current?.getBoundingClientRect();\n void onCopyToNote(\n text,\n rect ? { x: rect.left, y: rect.bottom } : undefined,\n );\n }\n } catch {\n onToast?.(\"Failed to copy document text\", \"error\");\n } finally {\n setIsHtmlCopying(false);\n }\n }, [preview.files, onCopyVitalsToNote, onCopyToNote, onToast]);\n\n const isSmart = record?.isSmart ?? false;\n const hasPreview = preview.status === \"loaded\" && preview.files.length > 0;\n\n return (\n <div className=\"mr-sv__panel\">\n <div className=\"mr-sv__panel-header\">\n <button type=\"button\" className=\"mr-sv__back-btn\" onClick={onBack}>\n <ChevronLeft size={16} aria-hidden />\n Back\n </button>\n\n {showNavPicker ? (\n <div className=\"mr-orig__nav-wrap\" ref={dropdownRef}>\n <div\n className={`mr-orig__nav-pill${dropdownOpen ? \" mr-orig__nav-pill--open\" : \"\"}`}\n role=\"button\"\n tabIndex={0}\n onClick={() => setDropdownOpen((v) => !v)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setDropdownOpen((v) => !v);\n }\n }}\n >\n <button\n type=\"button\"\n className=\"mr-orig__nav-chevron\"\n onClick={(e) => { e.stopPropagation(); goPrev(); }}\n aria-label=\"Previous record\"\n disabled={currentIndex <= 0}\n >\n <ChevronLeft size={14} aria-hidden />\n </button>\n <div className=\"mr-orig__nav-text\">\n <span className=\"mr-orig__nav-title\">{record?.title ?? \"Record\"}</span>\n <span className=\"mr-orig__nav-counter\">\n {currentIndex + 1} of {total}\n </span>\n </div>\n <button\n type=\"button\"\n className=\"mr-orig__nav-chevron\"\n onClick={(e) => { e.stopPropagation(); goNext(); }}\n aria-label=\"Next record\"\n disabled={currentIndex >= total - 1}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n\n {dropdownOpen && (\n <div className=\"mr-orig__dropdown\">\n <div className=\"mr-orig__dropdown-eyebrow\">\n {total} RECORD{total !== 1 ? \"S\" : \"\"}{\" \"}\n {isUngrouped ? \"WITH NO CARE CONTEXT\" : \"FOUND IN THIS ENCOUNTER\"}\n </div>\n <div className=\"mr-orig__dropdown-list\">\n {caseRecords.map((r) => {\n const isActive = r.id === recordId;\n return (\n <button\n key={r.id}\n type=\"button\"\n className={`mr-orig__dropdown-item${isActive ? \" mr-orig__dropdown-item--active\" : \"\"}`}\n onClick={() => goTo(r)}\n >\n <div className=\"mr-orig__dropdown-icon\">\n <File size={16} aria-hidden />\n {r.isSmart && (\n <SmartSparkles\n className=\"mr-orig__dropdown-smart-dot\"\n aria-label=\"Smart report\"\n />\n )}\n </div>\n <div className=\"mr-orig__dropdown-info\">\n <div className=\"mr-orig__dropdown-title-row\">\n <span className=\"mr-orig__dropdown-title\">{r.title}</span>\n {r.isSmart && (\n <span className=\"mr-orig__dropdown-smart-badge\">\n <SmartSparkles aria-hidden />\n Smart Report\n </span>\n )}\n {r.fileType?.toUpperCase() === \"HTML\" && (\n <span className=\"mr-tag-chip mr-tag-chip--html\">\n <span className=\"mr-tag-chip__label\">HTML</span>\n </span>\n )}\n </div>\n <div className=\"mr-orig__dropdown-meta\">\n <span>{epochToDisplay(r.createdAtEpoch)}</span>\n {r.isSmart && r.totalVitals > 0 && (\n <>\n <span className=\"mr-orig__dropdown-dot\">·</span>\n <span>{r.totalVitals} vitals</span>\n </>\n )}\n {r.isSmart && r.outOfRangeVitals > 0 && (\n <>\n <span className=\"mr-orig__dropdown-dot\">·</span>\n <span className=\"mr-orig__dropdown-oor\">\n {r.outOfRangeVitals} out of range\n </span>\n </>\n )}\n </div>\n </div>\n {isActive && (\n <Check\n size={14}\n className=\"mr-orig__dropdown-check\"\n aria-hidden\n />\n )}\n </button>\n );\n })}\n </div>\n </div>\n )}\n </div>\n ) : record ? (\n <div className=\"mr-sv__panel-info\">\n <Typography variant=\"label\" as=\"span\" className=\"mr-sv__panel-name\">\n {record.title}\n </Typography>\n <Typography variant=\"hint\" as=\"span\" className=\"mr-sv__panel-meta-text\">\n {epochToDisplay(record.createdAtEpoch)}\n </Typography>\n </div>\n ) : null}\n\n <div className=\"mr-orig__header-actions\">\n {preview.isHtml && hasCopyToNote && (\n <button\n ref={htmlCopyBtnRef}\n type=\"button\"\n className=\"mr-orig__html-copy-btn\"\n onClick={handleHtmlCopyToNote}\n disabled={isHtmlCopying}\n >\n {isHtmlCopying && <LoaderCircle size={12} className=\"mr-sv__loading-spinner\" aria-hidden />}\n Copy to note\n </button>\n )}\n {hasPreview && (\n <IconButton\n icon={<ExternalLink size={16} aria-hidden />}\n label=\"Open in new tab\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() =>\n window.open(preview.files[0].url, \"_blank\", \"noopener,noreferrer\")\n }\n />\n )}\n </div>\n </div>\n\n <div\n className=\"mr-sv__panel-body\"\n style={{ flex: 1, display: \"flex\", flexDirection: \"column\", overflow: \"hidden\" }}\n >\n {preview.status === \"loading\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"100%\",\n }}\n >\n <LoaderCircle size={44} className=\"mr-sv__loading-spinner\" aria-hidden />\n </div>\n )}\n {preview.status === \"error\" && (\n <Typography variant=\"muted\" className=\"mr-sv__panel-empty\">\n Failed to load document preview.\n </Typography>\n )}\n {preview.status === \"loaded\" && preview.files.length === 0 && (\n <Typography variant=\"muted\" className=\"mr-sv__panel-empty\">\n No preview available for this record.\n </Typography>\n )}\n\n {/* Non-smart: full-width PDF */}\n {hasPreview && !isSmart && (\n <div\n className=\"mr-record-preview__body\"\n style={{ flex: 1, display: \"flex\", overflow: \"hidden\" }}\n >\n <Suspense fallback={null}>\n <DocumentViewer\n files={preview.files}\n title={record?.title ?? \"\"}\n isHtml={preview.isHtml}\n />\n </Suspense>\n </div>\n )}\n\n {/* Smart: split view — PDF left, smart report panel right */}\n {hasPreview && isSmart && (\n <div className=\"mr-orig__split\">\n <div className=\"mr-orig__pdf-side\">\n <Suspense fallback={null}>\n <DocumentViewer\n files={preview.files}\n title={record?.title ?? \"\"}\n isHtml={preview.isHtml}\n />\n </Suspense>\n </div>\n\n {smartPanelOpen ? (\n <div className=\"mr-orig__smart-side\">\n {/* Header */}\n <div className=\"mr-orig__smart-hdr\">\n <span className=\"mr-orig__smart-title-row\">\n <span className=\"mr-orig__smart-title\">Smart Report</span>\n <SmartSparkles className=\"mr-orig__smart-title-icon\" aria-hidden />\n </span>\n <button\n type=\"button\"\n className=\"mr-sv__sidebar-collapse-btn mr-orig__smart-expanded-btn\"\n aria-label=\"Collapse smart report panel\"\n aria-expanded={true}\n onClick={() => setSmartPanelOpen(false)}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n\n {/* Search */}\n <SearchInput\n className=\"mr-orig__smart-search\"\n iconSize={16}\n placeholder=\"Search vitals\"\n value={srSearch}\n onChange={setSrSearch}\n />\n\n {/* Filter tabs — only when at least one sub-category has data */}\n {(oorRows.length > 0 || noInterpRows.length > 0) && (\n <div className=\"mr-orig__smart-tabs-wrap\">\n <div className=\"mr-orig__smart-tabs\">\n {([\"all\", \"oor\", \"none\"] as SrTab[]).map((tab) => {\n if (tab === \"oor\" && oorRows.length === 0) return null;\n if (tab === \"none\" && noInterpRows.length === 0) return null;\n const count =\n tab === \"all\"\n ? tabCounts.all\n : tab === \"oor\"\n ? tabCounts.oor\n : tabCounts.none;\n const label =\n tab === \"all\"\n ? \"All\"\n : tab === \"oor\"\n ? \"Out of range\"\n : \"No interpretation\";\n return (\n <button\n key={tab}\n type=\"button\"\n className={`mr-orig__smart-tab${srTab === tab ? \" mr-orig__smart-tab--active\" : \"\"}`}\n onClick={() => setSrTab(tab)}\n >\n {label} ({count})\n </button>\n );\n })}\n </div>\n </div>\n )}\n\n {/* Vital list */}\n <div className=\"mr-orig__smart-list\">\n {vitalsStatus === \"loading\" && (\n <div className=\"mr-orig__smart-loading\">\n <LoaderCircle\n size={20}\n className=\"mr-sv__loading-spinner\"\n aria-hidden\n />\n </div>\n )}\n {vitalsStatus === \"loaded\" && filteredRows.length === 0 && (\n <Typography variant=\"muted\" className=\"mr-orig__smart-empty\">\n No vitals match.\n </Typography>\n )}\n {vitalsStatus === \"loaded\" &&\n filteredRows.map((row, idx) => {\n const cell = row.cells[0];\n const isOor =\n row.latestStatus === \"high\" || row.latestStatus === \"low\";\n const isSelected = srSelected.has(row.key);\n const isLast = idx === filteredRows.length - 1;\n return (\n <div\n key={row.key}\n className={`mr-orig__vital-item${isLast ? \" mr-orig__vital-item--last\" : \"\"}`}\n >\n <div\n className={`mr-orig__vital-row mr-orig__vital-row--selectable${isSelected ? \" mr-orig__vital-row--selected\" : \"\"}`}\n onClick={() => toggleSrRow(row.key)}\n >\n <span\n className={`mr-sv__checkbox mr-cc-table__row-check${isSelected ? \" mr-sv__checkbox--checked\" : \"\"}`}\n role=\"checkbox\"\n aria-checked={isSelected}\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleSrRow(row.key);\n }}\n onKeyDown={(e) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n toggleSrRow(row.key);\n }\n }}\n >\n {isSelected && <Check size={10} strokeWidth={3} />}\n </span>\n <div className=\"mr-orig__vital-info\">\n <span className=\"mr-orig__vital-name\">{row.name}</span>\n {row.refRange && (\n <span className=\"mr-orig__vital-ref\">\n Ref {row.refRange}\n </span>\n )}\n </div>\n {cell && (\n <div className=\"mr-orig__vital-value\">\n <span\n className={`mr-orig__vital-val${isOor ? \" mr-orig__vital-val--oor\" : \"\"}`}\n >\n {cell.value}\n </span>\n {cell.unit && (\n <span className=\"mr-orig__vital-unit\">{cell.unit}</span>\n )}\n </div>\n )}\n {row.ekaId && (\n <button\n type=\"button\"\n className={`mr-orig__vital-expand-btn${expandedVital === row.key ? \" mr-orig__vital-expand-btn--open\" : \"\"}`}\n onClick={(e) => { e.stopPropagation(); toggleExpand(row.key); }}\n aria-label={expandedVital === row.key ? \"Collapse readings\" : \"Show latest readings\"}\n >\n <ChevronDown size={13} aria-hidden />\n </button>\n )}\n </div>\n {expandedVital === row.key && row.ekaId && (\n <VitalReadingsExpanded vitalName={row.name} ekaId={row.ekaId} />\n )}\n </div>\n );\n })}\n </div>\n\n {/* Footer */}\n <div className=\"mr-cc-table__footer\">\n <span\n className={`mr-sv__checkbox mr-cc-table__footer-check${srAllSelected ? \" mr-sv__checkbox--checked\" : \"\"}`}\n role=\"checkbox\"\n aria-checked={srAllSelected}\n tabIndex={0}\n onClick={toggleSrAll}\n onKeyDown={(e) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n toggleSrAll();\n }\n }}\n >\n {srAllSelected && <Check size={10} strokeWidth={3} />}\n </span>\n <span className=\"mr-cc-table__footer-label\" onClick={toggleSrAll}>Select all</span>\n <span className=\"mr-cc-table__footer-count\">\n {srSelected.size} of {filteredRows.length} selected\n </span>\n <button\n ref={srCopyBtnRef}\n type=\"button\"\n className=\"mr-cc-table__copy-note-btn\"\n disabled={!srSomeSelected || !hasCopyToNote}\n onClick={handleSrCopyToNote}\n >\n Copy to note\n </button>\n </div>\n </div>\n ) : (\n <div className=\"mr-orig__smart-side mr-orig__smart-side--collapsed\">\n <Tooltip content=\"Show smart report\">\n <button\n type=\"button\"\n className=\"mr-sv__sidebar-collapse-btn mr-orig__smart-collapsed-btn\"\n aria-label=\"Expand smart report panel\"\n aria-expanded={false}\n onClick={() => setSmartPanelOpen(true)}\n >\n <span className=\"mr-orig__smart-collapsed-icon-wrap\">\n <SmartSparkles className=\"mr-orig__smart-collapsed-icon\" aria-hidden />\n </span>\n </button>\n </Tooltip>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction VitalReadingsExpanded({ vitalName, ekaId }: { vitalName: string; ekaId: string }) {\n const { data: trendData, status } = useParameterTrend(ekaId);\n\n const readings = useMemo(() => {\n const trend = trendData as ParameterTrendResponse | null;\n if (!trend?.vitals_info?.length) return [];\n return [...trend.vitals_info]\n .sort((a, b) => (b.date ?? 0) - (a.date ?? 0))\n .slice(0, 3)\n .map((info) => {\n const dr = (info.display_result ?? \"\").toLowerCase();\n const isOor = dr.includes(\"high\") || dr.includes(\"above\") || dr.includes(\"abnormal_high\") ||\n dr.includes(\"low\") || dr.includes(\"below\") || dr.includes(\"abnormal_low\");\n return {\n value: info.val != null ? String(info.val) : ((info as any).val_string ?? \"—\"),\n date: info.display_time || \"\",\n isOor,\n };\n });\n }, [trendData]);\n\n if (status === \"loading\") {\n return (\n <div className=\"mr-orig__vital-readings mr-orig__vital-readings--loading\">\n <LoaderCircle size={16} className=\"mr-sv__loading-spinner\" aria-hidden />\n </div>\n );\n }\n\n if (status === \"error\" || !readings.length) {\n return (\n <div className=\"mr-orig__vital-readings\">\n <Typography variant=\"muted\" className=\"mr-orig__vital-readings-empty\">\n No readings available\n </Typography>\n </div>\n );\n }\n\n return (\n <div className=\"mr-orig__vital-readings\">\n <div className=\"mr-orig__vital-readings-title\">\n LATEST READINGS FOR {vitalName.toUpperCase()}\n </div>\n <div className=\"mr-orig__vital-readings-grid\">\n {readings.map((r, i) => (\n <div key={i} className=\"mr-orig__vital-reading-item\">\n <span className={`mr-orig__vital-reading-value${r.isOor ? \" mr-orig__vital-reading-value--oor\" : \"\"}`}>{r.value}</span>\n <span className=\"mr-orig__vital-reading-date\">{r.date}</span>\n </div>\n ))}\n </div>\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/OriginalRecordPanel-SJIBGHYZ.js","../src/views/components/shared/OriginalRecordPanel.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/medical-records-ui/medical-records-ui/dist/OriginalRecordPanel-SJIBGHYZ.js","sourcesContent":[null,"\"use client\";\n\nimport { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ChevronLeft, ChevronRight, ChevronDown, ExternalLink, Check, File, LoaderCircle } from \"lucide-react\";\nimport { useStore } from \"zustand\";\nimport { useSdk } from \"../../../connection/useSdk\";\nimport { useRecordPreview } from \"../../../connection/useRecordPreview\";\nimport { useRecordPanelTable } from \"../../hooks/care-panel/useRecordPanelTable\";\nimport { useParameterTrend } from \"../../hooks/care-panel/useParameterTrend\";\nimport { epochToDisplay } from \"../../../helpers/formatters\";\nimport type { ParameterTrendResponse } from \"@eka-care/medical-records-ts-sdk\";\nimport { useSmartRecordsActions } from \"../../context/SmartRecordsActionsContext\";\nimport { IconButton, Typography, SearchInput, Tooltip } from \"widgets\";\nimport type { RecordItem } from \"../../../helpers\";\nimport type { PanelRow } from \"../../../helpers/vitalsPanel\";\nimport SmartSparkles from \"icons/SmartSparkles\";\n\nconst DocumentViewer = lazy(() =>\n import(\"../../RecordPreview/DocumentViewer\").then((m) => ({\n default: m.DocumentViewer,\n })),\n);\n\ninterface OriginalRecordPanelProps {\n recordId: string;\n caseId?: string;\n onBack: () => void;\n}\n\ntype SrTab = \"all\" | \"oor\" | \"none\";\n\nfunction buildVitalsText(rows: PanelRow[]): string {\n return rows\n .map((r) => {\n const cell = r.cells[0];\n const ref = cell?.refRange ? ` (Ref: ${cell.refRange})` : \"\";\n return `${r.name}: ${cell?.value ?? \"—\"} ${r.unit}${ref}`;\n })\n .join(\"\\n\");\n}\n\nexport function OriginalRecordPanel({\n recordId,\n caseId,\n onBack,\n}: OriginalRecordPanelProps) {\n const { store } = useSdk();\n const record = useStore(store, (s) => s.records.byId[recordId]);\n const recordsById = useStore(store, (s) => s.records.byId);\n const preview = useRecordPreview(recordId);\n const { replaceOriginal, onCopyToNote, onCopyVitalsToNote, hasCopyToNote, onToast } =\n useSmartRecordsActions();\n\n // Smart report vitals (only fetched when record.isSmart)\n const { status: vitalsStatus, table } = useRecordPanelTable(\n record?.isSmart ? recordId : null,\n );\n const [smartPanelOpen, setSmartPanelOpen] = useState(true);\n const [srSearch, setSrSearch] = useState(\"\");\n const [srTab, setSrTab] = useState<SrTab>(\"all\");\n const [expandedVital, setExpandedVital] = useState<string | null>(null);\n const toggleExpand = useCallback((key: string) => {\n setExpandedVital((prev) => (prev === key ? null : key));\n }, []);\n const [srSelected, setSrSelected] = useState<Set<string>>(new Set());\n const srCopyBtnRef = useRef<HTMLButtonElement>(null);\n\n // Dropdown state\n const [dropdownOpen, setDropdownOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(false);\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [dropdownOpen]);\n\n const caseRecords = useMemo<RecordItem[]>(() => {\n if (!caseId) return [];\n return Object.values(recordsById)\n .filter(\n (r): r is RecordItem =>\n r != null &&\n (caseId === \"__ungrouped__\" ? r.cases.length === 0 : r.cases.includes(caseId)),\n )\n .sort((a, b) => b.createdAtEpoch - a.createdAtEpoch);\n }, [recordsById, caseId]);\n\n const currentIndex = caseRecords.findIndex((r) => r.id === recordId);\n const total = caseRecords.length;\n const isUngrouped = caseId === \"__ungrouped__\";\n\n const goTo = useCallback(\n (r: RecordItem) => {\n setDropdownOpen(false);\n replaceOriginal(r.id, caseId);\n },\n [replaceOriginal, caseId],\n );\n\n const goPrev = useCallback(() => {\n if (currentIndex > 0) goTo(caseRecords[currentIndex - 1]);\n }, [currentIndex, caseRecords, goTo]);\n\n const goNext = useCallback(() => {\n if (currentIndex < total - 1) goTo(caseRecords[currentIndex + 1]);\n }, [currentIndex, total, caseRecords, goTo]);\n\n const showNavPicker = caseId && total > 0;\n\n // Smart report vitals filtering\n const allRows = table.rows;\n const oorRows = useMemo(\n () => allRows.filter((r) => r.latestStatus === \"high\" || r.latestStatus === \"low\"),\n [allRows],\n );\n const noInterpRows = useMemo(\n () => allRows.filter((r) => r.latestStatus === \"unknown\"),\n [allRows],\n );\n\n const baseRows = srTab === \"oor\" ? oorRows : srTab === \"none\" ? noInterpRows : allRows;\n\n const filteredRows = useMemo(() => {\n if (!srSearch) return baseRows;\n const q = srSearch.toLowerCase();\n return baseRows.filter((r) => r.name.toLowerCase().includes(q));\n }, [baseRows, srSearch]);\n\n const tabCounts = useMemo(() => {\n if (!srSearch) return { all: allRows.length, oor: oorRows.length, none: noInterpRows.length };\n const q = srSearch.toLowerCase();\n return {\n all: allRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n oor: oorRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n none: noInterpRows.filter((r) => r.name.toLowerCase().includes(q)).length,\n };\n }, [srSearch, allRows, oorRows, noInterpRows]);\n\n useEffect(() => {\n setSrSelected(new Set());\n }, [srTab, srSearch]);\n\n useEffect(() => {\n if (srTab === \"oor\" && oorRows.length === 0) setSrTab(\"all\");\n if (srTab === \"none\" && noInterpRows.length === 0) setSrTab(\"all\");\n }, [srTab, oorRows.length, noInterpRows.length]);\n\n const srAllSelected =\n filteredRows.length > 0 && filteredRows.every((r) => srSelected.has(r.key));\n const srSomeSelected = srSelected.size > 0;\n\n const toggleSrRow = useCallback((key: string) => {\n setSrSelected((prev) => {\n const next = new Set(prev);\n next.has(key) ? next.delete(key) : next.add(key);\n return next;\n });\n }, []);\n\n const toggleSrAll = useCallback(() => {\n setSrSelected(\n srAllSelected ? new Set() : new Set(filteredRows.map((r) => r.key)),\n );\n }, [srAllSelected, filteredRows]);\n\n const handleSrCopyToNote = useCallback(() => {\n const selectedRows = filteredRows.filter((r) => srSelected.has(r.key));\n if (!selectedRows.length) return;\n const text = buildVitalsText(selectedRows);\n if (onCopyVitalsToNote) {\n void onCopyVitalsToNote(text);\n } else if (onCopyToNote) {\n const rect = srCopyBtnRef.current?.getBoundingClientRect();\n void onCopyToNote(\n text,\n rect ? { x: rect.left + rect.width / 2, y: rect.bottom } : undefined,\n );\n }\n }, [filteredRows, srSelected, onCopyVitalsToNote, onCopyToNote]);\n\n const htmlCopyBtnRef = useRef<HTMLButtonElement>(null);\n const [isHtmlCopying, setIsHtmlCopying] = useState(false);\n\n const handleHtmlCopyToNote = useCallback(async () => {\n const url = preview.files[0]?.url;\n if (!url) return;\n setIsHtmlCopying(true);\n try {\n const res = await fetch(url);\n const html = await res.text();\n const doc = new DOMParser().parseFromString(html, \"text/html\");\n const text = doc.body.textContent?.trim() ?? \"\";\n if (!text) return;\n if (onCopyVitalsToNote) {\n void onCopyVitalsToNote(text);\n } else if (onCopyToNote) {\n const rect = htmlCopyBtnRef.current?.getBoundingClientRect();\n void onCopyToNote(\n text,\n rect ? { x: rect.left, y: rect.bottom } : undefined,\n );\n }\n } catch {\n onToast?.(\"Failed to copy document text\", \"error\");\n } finally {\n setIsHtmlCopying(false);\n }\n }, [preview.files, onCopyVitalsToNote, onCopyToNote, onToast]);\n\n const isSmart = record?.isSmart ?? false;\n const hasPreview = preview.status === \"loaded\" && preview.files.length > 0;\n\n return (\n <div className=\"mr-sv__panel\">\n <div className=\"mr-sv__panel-header\">\n <button type=\"button\" className=\"mr-sv__back-btn\" onClick={onBack}>\n <ChevronLeft size={16} aria-hidden />\n Back\n </button>\n\n {showNavPicker ? (\n <div className=\"mr-orig__nav-wrap\" ref={dropdownRef}>\n <div\n className={`mr-orig__nav-pill${dropdownOpen ? \" mr-orig__nav-pill--open\" : \"\"}`}\n role=\"button\"\n tabIndex={0}\n onClick={() => setDropdownOpen((v) => !v)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n setDropdownOpen((v) => !v);\n }\n }}\n >\n <button\n type=\"button\"\n className=\"mr-orig__nav-chevron\"\n onClick={(e) => { e.stopPropagation(); goPrev(); }}\n aria-label=\"Previous record\"\n disabled={currentIndex <= 0}\n >\n <ChevronLeft size={14} aria-hidden />\n </button>\n <div className=\"mr-orig__nav-text\">\n <span className=\"mr-orig__nav-title\">{record?.title ?? \"Record\"}</span>\n <span className=\"mr-orig__nav-counter\">\n {currentIndex + 1} of {total}\n </span>\n </div>\n <button\n type=\"button\"\n className=\"mr-orig__nav-chevron\"\n onClick={(e) => { e.stopPropagation(); goNext(); }}\n aria-label=\"Next record\"\n disabled={currentIndex >= total - 1}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n\n {dropdownOpen && (\n <div className=\"mr-orig__dropdown\">\n <div className=\"mr-orig__dropdown-eyebrow\">\n {total} RECORD{total !== 1 ? \"S\" : \"\"}{\" \"}\n {isUngrouped ? \"WITH NO CARE CONTEXT\" : \"FOUND IN THIS ENCOUNTER\"}\n </div>\n <div className=\"mr-orig__dropdown-list\">\n {caseRecords.map((r) => {\n const isActive = r.id === recordId;\n return (\n <button\n key={r.id}\n type=\"button\"\n className={`mr-orig__dropdown-item${isActive ? \" mr-orig__dropdown-item--active\" : \"\"}`}\n onClick={() => goTo(r)}\n >\n <div className=\"mr-orig__dropdown-icon\">\n <File size={16} aria-hidden />\n {r.isSmart && (\n <SmartSparkles\n className=\"mr-orig__dropdown-smart-dot\"\n aria-label=\"Smart report\"\n />\n )}\n </div>\n <div className=\"mr-orig__dropdown-info\">\n <div className=\"mr-orig__dropdown-title-row\">\n <span className=\"mr-orig__dropdown-title\">{r.title}</span>\n {r.isSmart && (\n <span className=\"mr-orig__dropdown-smart-badge\">\n <SmartSparkles aria-hidden />\n Smart Report\n </span>\n )}\n {r.fileType?.toUpperCase() === \"HTML\" && (\n <span className=\"mr-tag-chip mr-tag-chip--html\">\n <span className=\"mr-tag-chip__label\">HTML</span>\n </span>\n )}\n </div>\n <div className=\"mr-orig__dropdown-meta\">\n <span>{epochToDisplay(r.createdAtEpoch)}</span>\n {r.isSmart && r.totalVitals > 0 && (\n <>\n <span className=\"mr-orig__dropdown-dot\">·</span>\n <span>{r.totalVitals} vitals</span>\n </>\n )}\n {r.isSmart && r.outOfRangeVitals > 0 && (\n <>\n <span className=\"mr-orig__dropdown-dot\">·</span>\n <span className=\"mr-orig__dropdown-oor\">\n {r.outOfRangeVitals} out of range\n </span>\n </>\n )}\n </div>\n </div>\n {isActive && (\n <Check\n size={14}\n className=\"mr-orig__dropdown-check\"\n aria-hidden\n />\n )}\n </button>\n );\n })}\n </div>\n </div>\n )}\n </div>\n ) : record ? (\n <div className=\"mr-sv__panel-info\">\n <Typography variant=\"label\" as=\"span\" className=\"mr-sv__panel-name\">\n {record.title}\n </Typography>\n <Typography variant=\"hint\" as=\"span\" className=\"mr-sv__panel-meta-text\">\n {epochToDisplay(record.createdAtEpoch)}\n </Typography>\n </div>\n ) : null}\n\n <div className=\"mr-orig__header-actions\">\n {preview.isHtml && hasCopyToNote && (\n <button\n ref={htmlCopyBtnRef}\n type=\"button\"\n className=\"mr-orig__html-copy-btn\"\n onClick={handleHtmlCopyToNote}\n disabled={isHtmlCopying}\n >\n {isHtmlCopying && <LoaderCircle size={12} className=\"mr-sv__loading-spinner\" aria-hidden />}\n Copy to note\n </button>\n )}\n {hasPreview && (\n <IconButton\n icon={<ExternalLink size={16} aria-hidden />}\n label=\"Open in new tab\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() =>\n window.open(preview.files[0].url, \"_blank\", \"noopener,noreferrer\")\n }\n />\n )}\n </div>\n </div>\n\n <div\n className=\"mr-sv__panel-body\"\n style={{ flex: 1, display: \"flex\", flexDirection: \"column\", overflow: \"hidden\" }}\n >\n {preview.status === \"loading\" && (\n <div\n style={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"100%\",\n }}\n >\n <LoaderCircle size={44} className=\"mr-sv__loading-spinner\" aria-hidden />\n </div>\n )}\n {preview.status === \"error\" && (\n <Typography variant=\"muted\" className=\"mr-sv__panel-empty\">\n Failed to load document preview.\n </Typography>\n )}\n {preview.status === \"loaded\" && preview.files.length === 0 && (\n <Typography variant=\"muted\" className=\"mr-sv__panel-empty\">\n No preview available for this record.\n </Typography>\n )}\n\n {/* Non-smart: full-width PDF */}\n {hasPreview && !isSmart && (\n <div\n className=\"mr-record-preview__body\"\n style={{ flex: 1, display: \"flex\", overflow: \"hidden\" }}\n >\n <Suspense fallback={null}>\n <DocumentViewer\n files={preview.files}\n title={record?.title ?? \"\"}\n isHtml={preview.isHtml}\n />\n </Suspense>\n </div>\n )}\n\n {/* Smart: split view — PDF left, smart report panel right */}\n {hasPreview && isSmart && (\n <div className=\"mr-orig__split\">\n <div className=\"mr-orig__pdf-side\">\n <Suspense fallback={null}>\n <DocumentViewer\n files={preview.files}\n title={record?.title ?? \"\"}\n isHtml={preview.isHtml}\n />\n </Suspense>\n </div>\n\n {smartPanelOpen ? (\n <div className=\"mr-orig__smart-side\">\n {/* Header */}\n <div className=\"mr-orig__smart-hdr\">\n <span className=\"mr-orig__smart-title-row\">\n <span className=\"mr-orig__smart-title\">Smart Report</span>\n <SmartSparkles className=\"mr-orig__smart-title-icon\" aria-hidden />\n </span>\n <button\n type=\"button\"\n className=\"mr-sv__sidebar-collapse-btn mr-orig__smart-expanded-btn\"\n aria-label=\"Collapse smart report panel\"\n aria-expanded={true}\n onClick={() => setSmartPanelOpen(false)}\n >\n <ChevronRight size={14} aria-hidden />\n </button>\n </div>\n\n {/* Search */}\n <SearchInput\n className=\"mr-orig__smart-search\"\n iconSize={16}\n placeholder=\"Search vitals\"\n value={srSearch}\n onChange={setSrSearch}\n />\n\n {/* Filter tabs — only when at least one sub-category has data */}\n {(oorRows.length > 0 || noInterpRows.length > 0) && (\n <div className=\"mr-orig__smart-tabs-wrap\">\n <div className=\"mr-orig__smart-tabs\">\n {([\"all\", \"oor\", \"none\"] as SrTab[]).map((tab) => {\n if (tab === \"oor\" && oorRows.length === 0) return null;\n if (tab === \"none\" && noInterpRows.length === 0) return null;\n const count =\n tab === \"all\"\n ? tabCounts.all\n : tab === \"oor\"\n ? tabCounts.oor\n : tabCounts.none;\n const label =\n tab === \"all\"\n ? \"All\"\n : tab === \"oor\"\n ? \"Out of range\"\n : \"No interpretation\";\n return (\n <button\n key={tab}\n type=\"button\"\n className={`mr-orig__smart-tab${srTab === tab ? \" mr-orig__smart-tab--active\" : \"\"}`}\n onClick={() => setSrTab(tab)}\n >\n {label} ({count})\n </button>\n );\n })}\n </div>\n </div>\n )}\n\n {/* Vital list */}\n <div className=\"mr-orig__smart-list\">\n {vitalsStatus === \"loading\" && (\n <div className=\"mr-orig__smart-loading\">\n <LoaderCircle\n size={20}\n className=\"mr-sv__loading-spinner\"\n aria-hidden\n />\n </div>\n )}\n {vitalsStatus === \"loaded\" && filteredRows.length === 0 && (\n <Typography variant=\"muted\" className=\"mr-orig__smart-empty\">\n No vitals match.\n </Typography>\n )}\n {vitalsStatus === \"loaded\" &&\n filteredRows.map((row, idx) => {\n const cell = row.cells[0];\n const isOor =\n row.latestStatus === \"high\" || row.latestStatus === \"low\";\n const isSelected = srSelected.has(row.key);\n const isLast = idx === filteredRows.length - 1;\n return (\n <div\n key={row.key}\n className={`mr-orig__vital-item${isLast ? \" mr-orig__vital-item--last\" : \"\"}`}\n >\n <div\n className={`mr-orig__vital-row mr-orig__vital-row--selectable${isSelected ? \" mr-orig__vital-row--selected\" : \"\"}`}\n onClick={() => toggleSrRow(row.key)}\n >\n <span\n className={`mr-sv__checkbox mr-cc-table__row-check${isSelected ? \" mr-sv__checkbox--checked\" : \"\"}`}\n role=\"checkbox\"\n aria-checked={isSelected}\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n toggleSrRow(row.key);\n }}\n onKeyDown={(e) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n e.stopPropagation();\n toggleSrRow(row.key);\n }\n }}\n >\n {isSelected && <Check size={10} strokeWidth={3} />}\n </span>\n <div className=\"mr-orig__vital-info\">\n <span className=\"mr-orig__vital-name\">{row.name}</span>\n {row.refRange && (\n <span className=\"mr-orig__vital-ref\">\n Ref {row.refRange}\n </span>\n )}\n </div>\n {cell && (\n <div className=\"mr-orig__vital-value\">\n <span\n className={`mr-orig__vital-val${isOor ? \" mr-orig__vital-val--oor\" : \"\"}`}\n >\n {cell.value}\n </span>\n {cell.unit && (\n <span className=\"mr-orig__vital-unit\">{cell.unit}</span>\n )}\n </div>\n )}\n {row.ekaId && (\n <button\n type=\"button\"\n className={`mr-orig__vital-expand-btn${expandedVital === row.key ? \" mr-orig__vital-expand-btn--open\" : \"\"}`}\n onClick={(e) => { e.stopPropagation(); toggleExpand(row.key); }}\n aria-label={expandedVital === row.key ? \"Collapse readings\" : \"Show latest readings\"}\n >\n <ChevronDown size={13} aria-hidden />\n </button>\n )}\n </div>\n {expandedVital === row.key && row.ekaId && (\n <VitalReadingsExpanded vitalName={row.name} ekaId={row.ekaId} />\n )}\n </div>\n );\n })}\n </div>\n\n {/* Footer */}\n <div className=\"mr-cc-table__footer\">\n <span\n className={`mr-sv__checkbox mr-cc-table__footer-check${srAllSelected ? \" mr-sv__checkbox--checked\" : \"\"}`}\n role=\"checkbox\"\n aria-checked={srAllSelected}\n tabIndex={0}\n onClick={toggleSrAll}\n onKeyDown={(e) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n toggleSrAll();\n }\n }}\n >\n {srAllSelected && <Check size={10} strokeWidth={3} />}\n </span>\n <span className=\"mr-cc-table__footer-label\" onClick={toggleSrAll}>Select all</span>\n <span className=\"mr-cc-table__footer-count\">\n {srSelected.size} of {filteredRows.length} selected\n </span>\n <button\n ref={srCopyBtnRef}\n type=\"button\"\n className=\"mr-cc-table__copy-note-btn\"\n disabled={!srSomeSelected || !hasCopyToNote}\n onClick={handleSrCopyToNote}\n >\n Copy to note\n </button>\n </div>\n </div>\n ) : (\n <div className=\"mr-orig__smart-side mr-orig__smart-side--collapsed\">\n <Tooltip content=\"Show smart report\">\n <button\n type=\"button\"\n className=\"mr-sv__sidebar-collapse-btn mr-orig__smart-collapsed-btn\"\n aria-label=\"Expand smart report panel\"\n aria-expanded={false}\n onClick={() => setSmartPanelOpen(true)}\n >\n <span className=\"mr-orig__smart-collapsed-icon-wrap\">\n <SmartSparkles className=\"mr-orig__smart-collapsed-icon\" aria-hidden />\n </span>\n </button>\n </Tooltip>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction VitalReadingsExpanded({ vitalName, ekaId }: { vitalName: string; ekaId: string }) {\n const { data: trendData, status } = useParameterTrend(ekaId);\n\n const readings = useMemo(() => {\n const trend = trendData as ParameterTrendResponse | null;\n if (!trend?.vitals_info?.length) return [];\n return [...trend.vitals_info]\n .sort((a, b) => (b.date ?? 0) - (a.date ?? 0))\n .slice(0, 3)\n .map((info) => {\n const dr = (info.display_result ?? \"\").toLowerCase();\n const isOor = dr.includes(\"high\") || dr.includes(\"above\") || dr.includes(\"abnormal_high\") ||\n dr.includes(\"low\") || dr.includes(\"below\") || dr.includes(\"abnormal_low\");\n return {\n value: info.val != null ? String(info.val) : ((info as any).val_string ?? \"—\"),\n date: info.display_time || \"\",\n isOor,\n };\n });\n }, [trendData]);\n\n if (status === \"loading\") {\n return (\n <div className=\"mr-orig__vital-readings mr-orig__vital-readings--loading\">\n <LoaderCircle size={16} className=\"mr-sv__loading-spinner\" aria-hidden />\n </div>\n );\n }\n\n if (status === \"error\" || !readings.length) {\n return (\n <div className=\"mr-orig__vital-readings\">\n <Typography variant=\"muted\" className=\"mr-orig__vital-readings-empty\">\n No readings available\n </Typography>\n </div>\n );\n }\n\n return (\n <div className=\"mr-orig__vital-readings\">\n <div className=\"mr-orig__vital-readings-title\">\n LATEST READINGS FOR {vitalName.toUpperCase()}\n </div>\n <div className=\"mr-orig__vital-readings-grid\">\n {readings.map((r, i) => (\n <div key={i} className=\"mr-orig__vital-reading-item\">\n <span className={`mr-orig__vital-reading-value${r.isOor ? \" mr-orig__vital-reading-value--oor\" : \"\"}`}>{r.value}</span>\n <span className=\"mr-orig__vital-reading-date\">{r.date}</span>\n </div>\n ))}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -23,7 +23,7 @@ import { ChevronLeft, ChevronRight, ChevronDown, ExternalLink, Check, File, Load
|
|
|
23
23
|
import { useStore } from "zustand";
|
|
24
24
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
25
25
|
var DocumentViewer = lazy(
|
|
26
|
-
() => import("./DocumentViewer-
|
|
26
|
+
() => import("./DocumentViewer-5AXKL75U.mjs").then((m) => ({
|
|
27
27
|
default: m.DocumentViewer
|
|
28
28
|
}))
|
|
29
29
|
);
|
|
@@ -607,4 +607,4 @@ function VitalReadingsExpanded({ vitalName, ekaId }) {
|
|
|
607
607
|
export {
|
|
608
608
|
OriginalRecordPanel
|
|
609
609
|
};
|
|
610
|
-
//# sourceMappingURL=OriginalRecordPanel-
|
|
610
|
+
//# sourceMappingURL=OriginalRecordPanel-UM74PRBO.mjs.map
|
|
@@ -33,6 +33,7 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
33
33
|
const [current, setCurrent] = useState(1);
|
|
34
34
|
const containerRef = useRef(null);
|
|
35
35
|
const pageRefs = useRef([]);
|
|
36
|
+
const pdfRef = useRef(null);
|
|
36
37
|
useEffect(() => {
|
|
37
38
|
let cancelled = false;
|
|
38
39
|
setStatus("loading");
|
|
@@ -42,6 +43,11 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
42
43
|
await ensurePdfjs();
|
|
43
44
|
const pdfjs = window.pdfjsLib;
|
|
44
45
|
const pdf = await pdfjs.getDocument(url).promise;
|
|
46
|
+
if (cancelled) {
|
|
47
|
+
pdf.destroy();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
pdfRef.current = pdf;
|
|
45
51
|
const numPages = pdf.numPages;
|
|
46
52
|
const rendered = [];
|
|
47
53
|
for (let i = 1; i <= numPages; i++) {
|
|
@@ -56,6 +62,7 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
56
62
|
ctx.scale(dpr, dpr);
|
|
57
63
|
await page.render({ canvasContext: ctx, canvas, viewport }).promise;
|
|
58
64
|
rendered.push({ pageNum: i, dataUrl: canvas.toDataURL(), width: viewport.width, height: viewport.height });
|
|
65
|
+
page.cleanup();
|
|
59
66
|
if (!cancelled) setPages([...rendered]);
|
|
60
67
|
}
|
|
61
68
|
if (!cancelled) setStatus("done");
|
|
@@ -65,6 +72,8 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
65
72
|
})();
|
|
66
73
|
return () => {
|
|
67
74
|
cancelled = true;
|
|
75
|
+
pdfRef.current?.destroy();
|
|
76
|
+
pdfRef.current = null;
|
|
68
77
|
};
|
|
69
78
|
}, [url, baseWidth]);
|
|
70
79
|
useEffect(() => {
|
|
@@ -138,4 +147,4 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
138
147
|
export {
|
|
139
148
|
PdfViewer
|
|
140
149
|
};
|
|
141
|
-
//# sourceMappingURL=PdfViewer-
|
|
150
|
+
//# sourceMappingURL=PdfViewer-CG2577E4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/RecordPreview/PdfViewer.tsx","../src/views/RecordPreview/ensurePdfjs.ts"],"sourcesContent":["import { useState, useEffect, useRef, useCallback } from 'react';\nimport { ensurePdfjs } from './ensurePdfjs';\n\ndeclare global {\n interface Window {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n pdfjsLib: any;\n }\n}\n\nexport interface PdfViewerProps {\n url: string;\n baseWidth: number;\n scale: number;\n scrollToPage: number;\n onPageInfo?: (info: { current: number; total: number }) => void;\n}\n\ninterface RenderedPage {\n pageNum: number;\n dataUrl: string;\n width: number;\n height: number;\n}\n\nexport function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }: PdfViewerProps) {\n const [pages, setPages] = useState<RenderedPage[]>([]);\n const [status, setStatus] = useState<'loading' | 'error' | 'done'>('loading');\n const [current, setCurrent] = useState(1);\n const containerRef = useRef<HTMLDivElement>(null);\n const pageRefs = useRef<(HTMLDivElement | null)[]>([]);\n const pdfRef = useRef<any>(null);\n\n useEffect(() => {\n let cancelled = false;\n setStatus('loading');\n setPages([]);\n\n (async () => {\n try {\n await ensurePdfjs();\n const pdfjs = window.pdfjsLib;\n const pdf = await pdfjs.getDocument(url).promise;\n if (cancelled) {\n pdf.destroy();\n return;\n }\n pdfRef.current = pdf;\n const numPages = pdf.numPages;\n const rendered: RenderedPage[] = [];\n\n for (let i = 1; i <= numPages; i++) {\n if (cancelled) return;\n const page = await pdf.getPage(i);\n const viewport = page.getViewport({ scale: baseWidth / page.getViewport({ scale: 1 }).width });\n const canvas = document.createElement('canvas');\n const dpr = Math.min(window.devicePixelRatio * 2, 4);\n canvas.width = viewport.width * dpr;\n canvas.height = viewport.height * dpr;\n const ctx = canvas.getContext('2d')!;\n ctx.scale(dpr, dpr);\n await page.render({ canvasContext: ctx, canvas, viewport }).promise;\n rendered.push({ pageNum: i, dataUrl: canvas.toDataURL(), width: viewport.width, height: viewport.height });\n page.cleanup();\n if (!cancelled) setPages([...rendered]);\n }\n\n if (!cancelled) setStatus('done');\n } catch {\n if (!cancelled) setStatus('error');\n }\n })();\n\n return () => {\n cancelled = true;\n pdfRef.current?.destroy();\n pdfRef.current = null;\n };\n }, [url, baseWidth]);\n\n useEffect(() => {\n onPageInfo?.({ current, total: pages.length });\n }, [current, pages.length, onPageInfo]);\n\n useEffect(() => {\n const el = pageRefs.current[scrollToPage - 1];\n if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }, [scrollToPage]);\n\n useEffect(() => {\n const root = containerRef.current;\n if (!root || pages.length === 0) return;\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((e) => e.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];\n if (visible) {\n const idx = Number((visible.target as HTMLElement).dataset.pageIndex);\n if (!Number.isNaN(idx)) setCurrent(idx + 1);\n }\n },\n { root, rootMargin: '0px 0px -70% 0px', threshold: 0 },\n );\n pageRefs.current.forEach((el) => el && observer.observe(el));\n return () => observer.disconnect();\n }, [pages.length]);\n\n const setPageRef = useCallback((i: number) => (el: HTMLDivElement | null) => {\n pageRefs.current[i] = el;\n }, []);\n\n if (status === 'error') {\n return <div className=\"mr-doc-viewer__status\">Couldn't load this document.</div>;\n }\n\n const totalWidth = (pages[0]?.width ?? baseWidth) * scale;\n const totalHeight = pages.reduce((sum, p) => sum + p.height, 0) * scale;\n\n return (\n <div ref={containerRef} className=\"mr-doc-viewer__pdf-scroll\">\n <div\n className=\"mr-doc-viewer__pdf-sizer\"\n style={{ width: totalWidth, height: totalHeight || undefined }}\n >\n <div\n className=\"mr-doc-viewer__pdf-stack\"\n style={{ transform: `scale(${scale})`, transformOrigin: 'top left' }}\n >\n {status === 'loading' && pages.length === 0 && (\n <div className=\"mr-doc-viewer__status\">Loading document…</div>\n )}\n {pages.map((p, i) => (\n <div\n key={p.pageNum}\n data-page-index={i}\n ref={setPageRef(i)}\n >\n <img\n src={p.dataUrl}\n width={p.width}\n height={p.height}\n alt={`Page ${p.pageNum}`}\n className=\"mr-doc-viewer__page\"\n draggable={false}\n />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * Loads pdf.js from the CDN at runtime and exposes it as `window.pdfjsLib`.\n *\n * pdfjs-dist v5 is externalized from the webpack bundle (see next.config.ts) to\n * avoid the \"Object.defineProperty called on non-object\" ESM error. So the\n * library must exist as a global *before* react-pdf (which imports `pdfjs-dist`,\n * resolved to `window.pdfjsLib`) evaluates. Call and await this before lazily\n * importing the PDF viewer. The legacy ESM build assigns `globalThis.pdfjsLib`\n * on load and also configures the matching worker.\n */\nconst PDFJS_VERSION = '5.4.296'; // must match the installed pdfjs-dist\n// jsDelivr mirrors the npm package exactly (cdnjs doesn't host the .mjs build).\n// The legacy build is the most browser-compatible.\nconst CDN_BASE = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}/legacy/build`;\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function ensurePdfjs(): Promise<void> {\n if (typeof window === 'undefined') return Promise.resolve();\n // Already loaded.\n if ((window as unknown as { pdfjsLib?: unknown }).pdfjsLib) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.type = 'module';\n // Import the legacy ESM build (sets globalThis.pdfjsLib), then point its\n // worker at the matching CDN worker.\n script.textContent = `\n import * as pdfjsLib from '${CDN_BASE}/pdf.min.mjs';\n pdfjsLib.GlobalWorkerOptions.workerSrc = '${CDN_BASE}/pdf.worker.min.mjs';\n window.pdfjsLib = pdfjsLib;\n window.dispatchEvent(new Event('pdfjs-ready'));\n `;\n script.onerror = () => reject(new Error('Failed to load pdf.js from CDN'));\n window.addEventListener('pdfjs-ready', () => resolve(), { once: true });\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -33,6 +33,7 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
33
33
|
const [current, setCurrent] = _react.useState.call(void 0, 1);
|
|
34
34
|
const containerRef = _react.useRef.call(void 0, null);
|
|
35
35
|
const pageRefs = _react.useRef.call(void 0, []);
|
|
36
|
+
const pdfRef = _react.useRef.call(void 0, null);
|
|
36
37
|
_react.useEffect.call(void 0, () => {
|
|
37
38
|
let cancelled = false;
|
|
38
39
|
setStatus("loading");
|
|
@@ -42,6 +43,11 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
42
43
|
await ensurePdfjs();
|
|
43
44
|
const pdfjs = window.pdfjsLib;
|
|
44
45
|
const pdf = await pdfjs.getDocument(url).promise;
|
|
46
|
+
if (cancelled) {
|
|
47
|
+
pdf.destroy();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
pdfRef.current = pdf;
|
|
45
51
|
const numPages = pdf.numPages;
|
|
46
52
|
const rendered = [];
|
|
47
53
|
for (let i = 1; i <= numPages; i++) {
|
|
@@ -56,6 +62,7 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
56
62
|
ctx.scale(dpr, dpr);
|
|
57
63
|
await page.render({ canvasContext: ctx, canvas, viewport }).promise;
|
|
58
64
|
rendered.push({ pageNum: i, dataUrl: canvas.toDataURL(), width: viewport.width, height: viewport.height });
|
|
65
|
+
page.cleanup();
|
|
59
66
|
if (!cancelled) setPages([...rendered]);
|
|
60
67
|
}
|
|
61
68
|
if (!cancelled) setStatus("done");
|
|
@@ -65,10 +72,12 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
65
72
|
})();
|
|
66
73
|
return () => {
|
|
67
74
|
cancelled = true;
|
|
75
|
+
_optionalChain([pdfRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.destroy, 'call', _3 => _3()]);
|
|
76
|
+
pdfRef.current = null;
|
|
68
77
|
};
|
|
69
78
|
}, [url, baseWidth]);
|
|
70
79
|
_react.useEffect.call(void 0, () => {
|
|
71
|
-
_optionalChain([onPageInfo, 'optionalCall',
|
|
80
|
+
_optionalChain([onPageInfo, 'optionalCall', _4 => _4({ current, total: pages.length })]);
|
|
72
81
|
}, [current, pages.length, onPageInfo]);
|
|
73
82
|
_react.useEffect.call(void 0, () => {
|
|
74
83
|
const el = pageRefs.current[scrollToPage - 1];
|
|
@@ -96,7 +105,7 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
96
105
|
if (status === "error") {
|
|
97
106
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", children: "Couldn't load this document." });
|
|
98
107
|
}
|
|
99
|
-
const totalWidth = (_nullishCoalesce(_optionalChain([pages, 'access',
|
|
108
|
+
const totalWidth = (_nullishCoalesce(_optionalChain([pages, 'access', _5 => _5[0], 'optionalAccess', _6 => _6.width]), () => ( baseWidth))) * scale;
|
|
100
109
|
const totalHeight = pages.reduce((sum, p) => sum + p.height, 0) * scale;
|
|
101
110
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { ref: containerRef, className: "mr-doc-viewer__pdf-scroll", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
102
111
|
"div",
|
|
@@ -138,4 +147,4 @@ function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }) {
|
|
|
138
147
|
|
|
139
148
|
|
|
140
149
|
exports.PdfViewer = PdfViewer;
|
|
141
|
-
//# sourceMappingURL=PdfViewer-
|
|
150
|
+
//# sourceMappingURL=PdfViewer-GIWRL6FQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/PdfViewer-GIWRL6FQ.js","../src/views/RecordPreview/PdfViewer.tsx","../src/views/RecordPreview/ensurePdfjs.ts"],"names":[],"mappings":"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","file":"/home/runner/work/medical-records-ui/medical-records-ui/dist/PdfViewer-GIWRL6FQ.js","sourcesContent":[null,"import { useState, useEffect, useRef, useCallback } from 'react';\nimport { ensurePdfjs } from './ensurePdfjs';\n\ndeclare global {\n interface Window {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n pdfjsLib: any;\n }\n}\n\nexport interface PdfViewerProps {\n url: string;\n baseWidth: number;\n scale: number;\n scrollToPage: number;\n onPageInfo?: (info: { current: number; total: number }) => void;\n}\n\ninterface RenderedPage {\n pageNum: number;\n dataUrl: string;\n width: number;\n height: number;\n}\n\nexport function PdfViewer({ url, baseWidth, scale, scrollToPage, onPageInfo }: PdfViewerProps) {\n const [pages, setPages] = useState<RenderedPage[]>([]);\n const [status, setStatus] = useState<'loading' | 'error' | 'done'>('loading');\n const [current, setCurrent] = useState(1);\n const containerRef = useRef<HTMLDivElement>(null);\n const pageRefs = useRef<(HTMLDivElement | null)[]>([]);\n const pdfRef = useRef<any>(null);\n\n useEffect(() => {\n let cancelled = false;\n setStatus('loading');\n setPages([]);\n\n (async () => {\n try {\n await ensurePdfjs();\n const pdfjs = window.pdfjsLib;\n const pdf = await pdfjs.getDocument(url).promise;\n if (cancelled) {\n pdf.destroy();\n return;\n }\n pdfRef.current = pdf;\n const numPages = pdf.numPages;\n const rendered: RenderedPage[] = [];\n\n for (let i = 1; i <= numPages; i++) {\n if (cancelled) return;\n const page = await pdf.getPage(i);\n const viewport = page.getViewport({ scale: baseWidth / page.getViewport({ scale: 1 }).width });\n const canvas = document.createElement('canvas');\n const dpr = Math.min(window.devicePixelRatio * 2, 4);\n canvas.width = viewport.width * dpr;\n canvas.height = viewport.height * dpr;\n const ctx = canvas.getContext('2d')!;\n ctx.scale(dpr, dpr);\n await page.render({ canvasContext: ctx, canvas, viewport }).promise;\n rendered.push({ pageNum: i, dataUrl: canvas.toDataURL(), width: viewport.width, height: viewport.height });\n page.cleanup();\n if (!cancelled) setPages([...rendered]);\n }\n\n if (!cancelled) setStatus('done');\n } catch {\n if (!cancelled) setStatus('error');\n }\n })();\n\n return () => {\n cancelled = true;\n pdfRef.current?.destroy();\n pdfRef.current = null;\n };\n }, [url, baseWidth]);\n\n useEffect(() => {\n onPageInfo?.({ current, total: pages.length });\n }, [current, pages.length, onPageInfo]);\n\n useEffect(() => {\n const el = pageRefs.current[scrollToPage - 1];\n if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });\n }, [scrollToPage]);\n\n useEffect(() => {\n const root = containerRef.current;\n if (!root || pages.length === 0) return;\n const observer = new IntersectionObserver(\n (entries) => {\n const visible = entries\n .filter((e) => e.isIntersecting)\n .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];\n if (visible) {\n const idx = Number((visible.target as HTMLElement).dataset.pageIndex);\n if (!Number.isNaN(idx)) setCurrent(idx + 1);\n }\n },\n { root, rootMargin: '0px 0px -70% 0px', threshold: 0 },\n );\n pageRefs.current.forEach((el) => el && observer.observe(el));\n return () => observer.disconnect();\n }, [pages.length]);\n\n const setPageRef = useCallback((i: number) => (el: HTMLDivElement | null) => {\n pageRefs.current[i] = el;\n }, []);\n\n if (status === 'error') {\n return <div className=\"mr-doc-viewer__status\">Couldn't load this document.</div>;\n }\n\n const totalWidth = (pages[0]?.width ?? baseWidth) * scale;\n const totalHeight = pages.reduce((sum, p) => sum + p.height, 0) * scale;\n\n return (\n <div ref={containerRef} className=\"mr-doc-viewer__pdf-scroll\">\n <div\n className=\"mr-doc-viewer__pdf-sizer\"\n style={{ width: totalWidth, height: totalHeight || undefined }}\n >\n <div\n className=\"mr-doc-viewer__pdf-stack\"\n style={{ transform: `scale(${scale})`, transformOrigin: 'top left' }}\n >\n {status === 'loading' && pages.length === 0 && (\n <div className=\"mr-doc-viewer__status\">Loading document…</div>\n )}\n {pages.map((p, i) => (\n <div\n key={p.pageNum}\n data-page-index={i}\n ref={setPageRef(i)}\n >\n <img\n src={p.dataUrl}\n width={p.width}\n height={p.height}\n alt={`Page ${p.pageNum}`}\n className=\"mr-doc-viewer__page\"\n draggable={false}\n />\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/**\n * Loads pdf.js from the CDN at runtime and exposes it as `window.pdfjsLib`.\n *\n * pdfjs-dist v5 is externalized from the webpack bundle (see next.config.ts) to\n * avoid the \"Object.defineProperty called on non-object\" ESM error. So the\n * library must exist as a global *before* react-pdf (which imports `pdfjs-dist`,\n * resolved to `window.pdfjsLib`) evaluates. Call and await this before lazily\n * importing the PDF viewer. The legacy ESM build assigns `globalThis.pdfjsLib`\n * on load and also configures the matching worker.\n */\nconst PDFJS_VERSION = '5.4.296'; // must match the installed pdfjs-dist\n// jsDelivr mirrors the npm package exactly (cdnjs doesn't host the .mjs build).\n// The legacy build is the most browser-compatible.\nconst CDN_BASE = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}/legacy/build`;\n\nlet loadPromise: Promise<void> | null = null;\n\nexport function ensurePdfjs(): Promise<void> {\n if (typeof window === 'undefined') return Promise.resolve();\n // Already loaded.\n if ((window as unknown as { pdfjsLib?: unknown }).pdfjsLib) return Promise.resolve();\n if (loadPromise) return loadPromise;\n\n loadPromise = new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.type = 'module';\n // Import the legacy ESM build (sets globalThis.pdfjsLib), then point its\n // worker at the matching CDN worker.\n script.textContent = `\n import * as pdfjsLib from '${CDN_BASE}/pdf.min.mjs';\n pdfjsLib.GlobalWorkerOptions.workerSrc = '${CDN_BASE}/pdf.worker.min.mjs';\n window.pdfjsLib = pdfjsLib;\n window.dispatchEvent(new Event('pdfjs-ready'));\n `;\n script.onerror = () => reject(new Error('Failed to load pdf.js from CDN'));\n window.addEventListener('pdfjs-ready', () => resolve(), { once: true });\n document.head.appendChild(script);\n });\n return loadPromise;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1674,7 +1674,7 @@ function CareContextPanel({ caseId, showFiles: showFilesProp, onBack }) {
|
|
|
1674
1674
|
|
|
1675
1675
|
|
|
1676
1676
|
var DocumentViewer = _react.lazy.call(void 0,
|
|
1677
|
-
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-
|
|
1677
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-Y3SQ2BMS.js"))).then((m) => ({
|
|
1678
1678
|
default: m.DocumentViewer
|
|
1679
1679
|
}))
|
|
1680
1680
|
);
|
|
@@ -1846,10 +1846,12 @@ function SmartRecordSmartPanel({
|
|
|
1846
1846
|
|
|
1847
1847
|
var HIDDEN = { visible: false, direction: "down" };
|
|
1848
1848
|
function useJumpToSelected(activeId, listKey) {
|
|
1849
|
-
const
|
|
1849
|
+
const [body, setBody] = _react.useState.call(void 0, null);
|
|
1850
|
+
const scrollBodyRef = _react.useCallback.call(void 0, (node) => {
|
|
1851
|
+
setBody(node);
|
|
1852
|
+
}, []);
|
|
1850
1853
|
const [state, setState] = _react.useState.call(void 0, HIDDEN);
|
|
1851
1854
|
const evaluate = _react.useCallback.call(void 0, () => {
|
|
1852
|
-
const body = scrollBodyRef.current;
|
|
1853
1855
|
const active = _nullishCoalesce(_optionalChain([body, 'optionalAccess', _44 => _44.querySelector, 'call', _45 => _45(".mr-sv__record-item--active")]), () => ( null));
|
|
1854
1856
|
if (!body || !active) {
|
|
1855
1857
|
setState((s) => s.visible ? HIDDEN : s);
|
|
@@ -1864,9 +1866,8 @@ function useJumpToSelected(activeId, listKey) {
|
|
|
1864
1866
|
} else {
|
|
1865
1867
|
setState((s) => s.visible ? HIDDEN : s);
|
|
1866
1868
|
}
|
|
1867
|
-
}, [activeId]);
|
|
1869
|
+
}, [body, activeId]);
|
|
1868
1870
|
_react.useEffect.call(void 0, () => {
|
|
1869
|
-
const body = scrollBodyRef.current;
|
|
1870
1871
|
if (!body) return;
|
|
1871
1872
|
body.addEventListener("scroll", evaluate, { passive: true });
|
|
1872
1873
|
window.addEventListener("resize", evaluate);
|
|
@@ -1874,13 +1875,13 @@ function useJumpToSelected(activeId, listKey) {
|
|
|
1874
1875
|
body.removeEventListener("scroll", evaluate);
|
|
1875
1876
|
window.removeEventListener("resize", evaluate);
|
|
1876
1877
|
};
|
|
1877
|
-
}, [evaluate]);
|
|
1878
|
+
}, [body, evaluate]);
|
|
1878
1879
|
_react.useEffect.call(void 0, () => {
|
|
1879
1880
|
evaluate();
|
|
1880
1881
|
}, [evaluate, listKey]);
|
|
1881
1882
|
const jumpToSelected = _react.useCallback.call(void 0, () => {
|
|
1882
|
-
_optionalChain([
|
|
1883
|
-
}, []);
|
|
1883
|
+
_optionalChain([body, 'optionalAccess', _46 => _46.querySelector, 'call', _47 => _47(".mr-sv__record-item--active"), 'optionalAccess', _48 => _48.scrollIntoView, 'call', _49 => _49({ behavior: "smooth", block: "center" })]);
|
|
1884
|
+
}, [body]);
|
|
1884
1885
|
return {
|
|
1885
1886
|
scrollBodyRef,
|
|
1886
1887
|
showChip: state.visible,
|
|
@@ -1990,7 +1991,7 @@ function resolveContentType(file) {
|
|
|
1990
1991
|
if (t === "image/jpeg" || t === "image/jpg" || t === "image/png" || t === "application/pdf") {
|
|
1991
1992
|
return t;
|
|
1992
1993
|
}
|
|
1993
|
-
const ext = _optionalChain([file, 'access',
|
|
1994
|
+
const ext = _optionalChain([file, 'access', _50 => _50.name, 'access', _51 => _51.split, 'call', _52 => _52("."), 'access', _53 => _53.pop, 'call', _54 => _54(), 'optionalAccess', _55 => _55.toLowerCase, 'call', _56 => _56()]);
|
|
1994
1995
|
if (ext === "pdf") return "application/pdf";
|
|
1995
1996
|
if (ext === "png") return "image/png";
|
|
1996
1997
|
if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
|
|
@@ -2029,12 +2030,12 @@ function useUploadConnection() {
|
|
|
2029
2030
|
patientId
|
|
2030
2031
|
});
|
|
2031
2032
|
const queue = store.getState().uploadQueue;
|
|
2032
|
-
if (res.message === "queued_offline" || !_optionalChain([res, 'access',
|
|
2033
|
+
if (res.message === "queued_offline" || !_optionalChain([res, 'access', _57 => _57.batch_response, 'optionalAccess', _58 => _58.length])) {
|
|
2033
2034
|
throw new Error("No internet connection. Your upload will retry automatically when you're back online.");
|
|
2034
2035
|
}
|
|
2035
2036
|
localIds.forEach((id, i) => {
|
|
2036
|
-
const documentId = _nullishCoalesce(_optionalChain([res, 'access',
|
|
2037
|
-
const errorDetails = _optionalChain([res, 'access',
|
|
2037
|
+
const documentId = _nullishCoalesce(_optionalChain([res, 'access', _59 => _59.batch_response, 'optionalAccess', _60 => _60[i], 'optionalAccess', _61 => _61.document_id]), () => ( ""));
|
|
2038
|
+
const errorDetails = _optionalChain([res, 'access', _62 => _62.batch_response, 'optionalAccess', _63 => _63[i], 'optionalAccess', _64 => _64.error_details]);
|
|
2038
2039
|
if (errorDetails) {
|
|
2039
2040
|
queue.markFailed(id);
|
|
2040
2041
|
} else {
|
|
@@ -2275,7 +2276,7 @@ var SmartRecordsSidebar = _react.memo.call(void 0, function SmartRecordsSidebar2
|
|
|
2275
2276
|
if (!c) return false;
|
|
2276
2277
|
if (c.name.toLowerCase().includes(q)) return true;
|
|
2277
2278
|
return Object.values(recordsById).some(
|
|
2278
|
-
(r) => _optionalChain([r, 'optionalAccess',
|
|
2279
|
+
(r) => _optionalChain([r, 'optionalAccess', _65 => _65.cases, 'access', _66 => _66.includes, 'call', _67 => _67(caseId)]) && r.title.toLowerCase().includes(q)
|
|
2279
2280
|
);
|
|
2280
2281
|
});
|
|
2281
2282
|
}, [caseIds, casesById, recordsById, search]);
|
|
@@ -2456,7 +2457,7 @@ var VitalHistoryPanel2 = _react.lazy.call(void 0,
|
|
|
2456
2457
|
}))
|
|
2457
2458
|
);
|
|
2458
2459
|
var OriginalRecordPanel = _react.lazy.call(void 0,
|
|
2459
|
-
() => Promise.resolve().then(() => _interopRequireWildcard(require("./OriginalRecordPanel-
|
|
2460
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("./OriginalRecordPanel-SJIBGHYZ.js"))).then((m) => ({
|
|
2460
2461
|
default: m.OriginalRecordPanel
|
|
2461
2462
|
}))
|
|
2462
2463
|
);
|
|
@@ -2564,19 +2565,19 @@ function SmartRecordsViewInner({
|
|
|
2564
2565
|
const [selectedCaseId, setSelectedCaseId] = _react.useState.call(void 0, null);
|
|
2565
2566
|
const [uploadOpen, setUploadOpen] = _react.useState.call(void 0, false);
|
|
2566
2567
|
const stableCopyToNote = _react.useCallback.call(void 0,
|
|
2567
|
-
(text, anchor) => _optionalChain([onCopyToNoteRef, 'access',
|
|
2568
|
+
(text, anchor) => _optionalChain([onCopyToNoteRef, 'access', _68 => _68.current, 'optionalCall', _69 => _69(text, anchor)]),
|
|
2568
2569
|
[]
|
|
2569
2570
|
);
|
|
2570
2571
|
const stableCopyVitalsToNote = _react.useCallback.call(void 0,
|
|
2571
|
-
(text) => _optionalChain([onCopyVitalsToNoteRef, 'access',
|
|
2572
|
+
(text) => _optionalChain([onCopyVitalsToNoteRef, 'access', _70 => _70.current, 'optionalCall', _71 => _71(text)]),
|
|
2572
2573
|
[]
|
|
2573
2574
|
);
|
|
2574
2575
|
const stableToast = _react.useCallback.call(void 0,
|
|
2575
|
-
(message, type) => _optionalChain([onToastRef, 'access',
|
|
2576
|
+
(message, type) => _optionalChain([onToastRef, 'access', _72 => _72.current, 'optionalCall', _73 => _73(message, type)]),
|
|
2576
2577
|
[]
|
|
2577
2578
|
);
|
|
2578
2579
|
const attachToContext = _react.useCallback.call(void 0, (documentId, name) => {
|
|
2579
|
-
void _optionalChain([onAddToContextRef, 'access',
|
|
2580
|
+
void _optionalChain([onAddToContextRef, 'access', _74 => _74.current, 'optionalCall', _75 => _75([{ documentId, name }])]);
|
|
2580
2581
|
}, []);
|
|
2581
2582
|
const handleSelectCase = _react.useCallback.call(void 0,
|
|
2582
2583
|
(caseId) => {
|