@eka-care/medical-records-ui 1.0.21 → 1.0.25
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-WXL7OZXK.mjs → DocumentViewer-JTKMSJ3V.mjs} +6 -5
- package/dist/DocumentViewer-JTKMSJ3V.mjs.map +1 -0
- package/dist/{DocumentViewer-Z3GOU5NU.js → DocumentViewer-NMFEAQUY.js} +6 -5
- package/dist/DocumentViewer-NMFEAQUY.js.map +1 -0
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +51 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +109 -140
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/DocumentViewer-WXL7OZXK.mjs.map +0 -1
- package/dist/DocumentViewer-Z3GOU5NU.js.map +0 -1
|
@@ -27,6 +27,7 @@ function TextViewer({ url }) {
|
|
|
27
27
|
const res = await fetch(url);
|
|
28
28
|
if (!res.ok) throw new Error(`fetch ${res.status}`);
|
|
29
29
|
const text = await res.text();
|
|
30
|
+
if (text.trimStart().startsWith("%PDF")) throw new Error("binary");
|
|
30
31
|
if (!cancelled) setState({ status: "loaded", text });
|
|
31
32
|
} catch {
|
|
32
33
|
if (!cancelled) setState({ status: "error", text: "" });
|
|
@@ -37,13 +38,13 @@ function TextViewer({ url }) {
|
|
|
37
38
|
};
|
|
38
39
|
}, [url]);
|
|
39
40
|
if (state.status === "loading") {
|
|
40
|
-
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", children: "Loading document..." });
|
|
41
|
+
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "Loading document..." });
|
|
41
42
|
}
|
|
42
43
|
if (state.status === "error") {
|
|
43
|
-
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", children: "Couldn't load this document." });
|
|
44
|
+
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "Couldn't load this document." });
|
|
44
45
|
}
|
|
45
46
|
if (!state.text.trim()) {
|
|
46
|
-
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", children: "This file appears to be empty." });
|
|
47
|
+
return /* @__PURE__ */ jsx("div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "This file appears to be empty." });
|
|
47
48
|
}
|
|
48
49
|
return /* @__PURE__ */ jsx(
|
|
49
50
|
"iframe",
|
|
@@ -93,7 +94,7 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
93
94
|
const blob = await res.blob();
|
|
94
95
|
const objUrl = URL.createObjectURL(blob);
|
|
95
96
|
fn(objUrl);
|
|
96
|
-
setTimeout(() => URL.revokeObjectURL(objUrl),
|
|
97
|
+
setTimeout(() => URL.revokeObjectURL(objUrl), 12e4);
|
|
97
98
|
} catch {
|
|
98
99
|
fn(primary.url);
|
|
99
100
|
}
|
|
@@ -194,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
194
195
|
export {
|
|
195
196
|
DocumentViewer
|
|
196
197
|
};
|
|
197
|
-
//# sourceMappingURL=DocumentViewer-
|
|
198
|
+
//# sourceMappingURL=DocumentViewer-JTKMSJ3V.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/RecordPreview/DocumentViewer.tsx","../src/views/RecordPreview/TextViewer.tsx"],"sourcesContent":["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 file 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"],"mappings":"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","names":["useEffect","useState","jsx","useState","useEffect"]}
|
|
@@ -27,6 +27,7 @@ function TextViewer({ url }) {
|
|
|
27
27
|
const res = await fetch(url);
|
|
28
28
|
if (!res.ok) throw new Error(`fetch ${res.status}`);
|
|
29
29
|
const text = await res.text();
|
|
30
|
+
if (text.trimStart().startsWith("%PDF")) throw new Error("binary");
|
|
30
31
|
if (!cancelled) setState({ status: "loaded", text });
|
|
31
32
|
} catch (e) {
|
|
32
33
|
if (!cancelled) setState({ status: "error", text: "" });
|
|
@@ -37,13 +38,13 @@ function TextViewer({ url }) {
|
|
|
37
38
|
};
|
|
38
39
|
}, [url]);
|
|
39
40
|
if (state.status === "loading") {
|
|
40
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", children: "Loading document..." });
|
|
41
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "Loading document..." });
|
|
41
42
|
}
|
|
42
43
|
if (state.status === "error") {
|
|
43
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", children: "Couldn't load this document." });
|
|
44
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "Couldn't load this document." });
|
|
44
45
|
}
|
|
45
46
|
if (!state.text.trim()) {
|
|
46
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", children: "This file appears to be empty." });
|
|
47
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "mr-doc-viewer__status", style: { color: "var(--mr-muted-foreground)" }, children: "This file appears to be empty." });
|
|
47
48
|
}
|
|
48
49
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
49
50
|
"iframe",
|
|
@@ -93,7 +94,7 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
93
94
|
const blob = await res.blob();
|
|
94
95
|
const objUrl = URL.createObjectURL(blob);
|
|
95
96
|
fn(objUrl);
|
|
96
|
-
setTimeout(() => URL.revokeObjectURL(objUrl),
|
|
97
|
+
setTimeout(() => URL.revokeObjectURL(objUrl), 12e4);
|
|
97
98
|
} catch (e2) {
|
|
98
99
|
fn(primary.url);
|
|
99
100
|
}
|
|
@@ -194,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
|
|
|
194
195
|
|
|
195
196
|
|
|
196
197
|
exports.DocumentViewer = DocumentViewer;
|
|
197
|
-
//# sourceMappingURL=DocumentViewer-
|
|
198
|
+
//# sourceMappingURL=DocumentViewer-NMFEAQUY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-NMFEAQUY.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-NMFEAQUY.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 file 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"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -31,9 +31,10 @@ interface SdkProviderProps {
|
|
|
31
31
|
bid?: string;
|
|
32
32
|
patientId?: string;
|
|
33
33
|
documentTypes?: DocumentTypeConfig[];
|
|
34
|
+
onUnknownDocumentType?: (code: string) => void;
|
|
34
35
|
children: ReactNode;
|
|
35
36
|
}
|
|
36
|
-
declare function SdkProvider({ config, bid, patientId, documentTypes, children }: SdkProviderProps): react.JSX.Element;
|
|
37
|
+
declare function SdkProvider({ config, bid, patientId, documentTypes, onUnknownDocumentType, children }: SdkProviderProps): react.JSX.Element;
|
|
37
38
|
|
|
38
39
|
type DocumentTypeLabelResolver = (code: string) => string;
|
|
39
40
|
|
|
@@ -256,11 +257,13 @@ interface RecordsViewProps {
|
|
|
256
257
|
}>) => void | Promise<void>;
|
|
257
258
|
onRemoveAttachment?: (documentId: string) => void | Promise<void>;
|
|
258
259
|
onToast?: (message: string, type?: 'error' | 'success') => void;
|
|
260
|
+
allowUpload?: boolean;
|
|
259
261
|
}
|
|
260
|
-
declare function RecordsView({ className, onUpload, onCopyToNote, attachedIds, onAttachManyToContext, onRemoveAttachment, onToast, }: RecordsViewProps): react.JSX.Element;
|
|
262
|
+
declare function RecordsView({ className, onUpload, onCopyToNote, attachedIds, onAttachManyToContext, onRemoveAttachment, onToast, allowUpload: allowUploadProp, }: RecordsViewProps): react.JSX.Element;
|
|
261
263
|
|
|
262
264
|
interface RecordsConnectionActions {
|
|
263
265
|
refresh: () => Promise<void>;
|
|
266
|
+
retry: () => Promise<void>;
|
|
264
267
|
/** Unix epoch ms of the last successful server refresh, or null before first call. */
|
|
265
268
|
sourceRefreshedAt: number | null;
|
|
266
269
|
editRecord: (documentId: string, data: EditDocumentRequest) => Promise<void>;
|
package/dist/index.d.ts
CHANGED
|
@@ -31,9 +31,10 @@ interface SdkProviderProps {
|
|
|
31
31
|
bid?: string;
|
|
32
32
|
patientId?: string;
|
|
33
33
|
documentTypes?: DocumentTypeConfig[];
|
|
34
|
+
onUnknownDocumentType?: (code: string) => void;
|
|
34
35
|
children: ReactNode;
|
|
35
36
|
}
|
|
36
|
-
declare function SdkProvider({ config, bid, patientId, documentTypes, children }: SdkProviderProps): react.JSX.Element;
|
|
37
|
+
declare function SdkProvider({ config, bid, patientId, documentTypes, onUnknownDocumentType, children }: SdkProviderProps): react.JSX.Element;
|
|
37
38
|
|
|
38
39
|
type DocumentTypeLabelResolver = (code: string) => string;
|
|
39
40
|
|
|
@@ -256,11 +257,13 @@ interface RecordsViewProps {
|
|
|
256
257
|
}>) => void | Promise<void>;
|
|
257
258
|
onRemoveAttachment?: (documentId: string) => void | Promise<void>;
|
|
258
259
|
onToast?: (message: string, type?: 'error' | 'success') => void;
|
|
260
|
+
allowUpload?: boolean;
|
|
259
261
|
}
|
|
260
|
-
declare function RecordsView({ className, onUpload, onCopyToNote, attachedIds, onAttachManyToContext, onRemoveAttachment, onToast, }: RecordsViewProps): react.JSX.Element;
|
|
262
|
+
declare function RecordsView({ className, onUpload, onCopyToNote, attachedIds, onAttachManyToContext, onRemoveAttachment, onToast, allowUpload: allowUploadProp, }: RecordsViewProps): react.JSX.Element;
|
|
261
263
|
|
|
262
264
|
interface RecordsConnectionActions {
|
|
263
265
|
refresh: () => Promise<void>;
|
|
266
|
+
retry: () => Promise<void>;
|
|
264
267
|
/** Unix epoch ms of the last successful server refresh, or null before first call. */
|
|
265
268
|
sourceRefreshedAt: number | null;
|
|
266
269
|
editRecord: (documentId: string, data: EditDocumentRequest) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -329,12 +329,20 @@ var SdkContext = _react.createContext.call(void 0, null);
|
|
|
329
329
|
|
|
330
330
|
// src/helpers/documentTypeLabels.ts
|
|
331
331
|
var STATIC_LABELS = _nullishCoalesce(DOCUMENT_TYPE_LABELS, () => ( {}));
|
|
332
|
-
function
|
|
332
|
+
function toTitleCase(code) {
|
|
333
|
+
return code.replace(/[_\-]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
334
|
+
}
|
|
335
|
+
function createDocumentTypeLabelResolver(types, onUnknownDocumentType) {
|
|
333
336
|
const fromConfig = /* @__PURE__ */ new Map();
|
|
334
337
|
for (const t of _nullishCoalesce(types, () => ( []))) {
|
|
335
338
|
if (t.id && t.display_name) fromConfig.set(t.id, t.display_name);
|
|
336
339
|
}
|
|
337
|
-
return (code) =>
|
|
340
|
+
return (code) => {
|
|
341
|
+
const label = _nullishCoalesce(fromConfig.get(code), () => ( STATIC_LABELS[code]));
|
|
342
|
+
if (label) return label;
|
|
343
|
+
_optionalChain([onUnknownDocumentType, 'optionalCall', _9 => _9(code)]);
|
|
344
|
+
return toTitleCase(code);
|
|
345
|
+
};
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
// src/components/icons/EmptyRecordsIcon.tsx
|
|
@@ -514,7 +522,7 @@ function RecordsPlaceholder() {
|
|
|
514
522
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "mr-records-view__empty-desc", children: "Browse and attach the patient's medical records here." })
|
|
515
523
|
] });
|
|
516
524
|
}
|
|
517
|
-
function SdkProviderInner({ config, bid, patientId, documentTypes, children }) {
|
|
525
|
+
function SdkProviderInner({ config, bid, patientId, documentTypes, onUnknownDocumentType, children }) {
|
|
518
526
|
initWorkerClient(config, patientId);
|
|
519
527
|
_react.useEffect.call(void 0, () => {
|
|
520
528
|
setAuthToken(config.accessToken);
|
|
@@ -529,8 +537,8 @@ function SdkProviderInner({ config, bid, patientId, documentTypes, children }) {
|
|
|
529
537
|
return () => window.removeEventListener("online", handleOnline);
|
|
530
538
|
}, []);
|
|
531
539
|
const labelOf = _react.useMemo.call(void 0,
|
|
532
|
-
() => createDocumentTypeLabelResolver(documentTypes),
|
|
533
|
-
[documentTypes]
|
|
540
|
+
() => createDocumentTypeLabelResolver(documentTypes, onUnknownDocumentType),
|
|
541
|
+
[documentTypes, onUnknownDocumentType]
|
|
534
542
|
);
|
|
535
543
|
const docTypes = _react.useMemo.call(void 0,
|
|
536
544
|
() => _nullishCoalesce(documentTypes, () => ( Object.entries(DOCUMENT_TYPE_LABELS).map(([id, display_name]) => ({ id, display_name })))),
|
|
@@ -542,9 +550,9 @@ function SdkProviderInner({ config, bid, patientId, documentTypes, children }) {
|
|
|
542
550
|
);
|
|
543
551
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, SdkContext.Provider, { value, children });
|
|
544
552
|
}
|
|
545
|
-
function SdkProvider({ config, bid, patientId, documentTypes, children }) {
|
|
553
|
+
function SdkProvider({ config, bid, patientId, documentTypes, onUnknownDocumentType, children }) {
|
|
546
554
|
if (!bid || !patientId) return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsPlaceholder, {});
|
|
547
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, SdkProviderInner, { config, bid, patientId, documentTypes, children });
|
|
555
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, SdkProviderInner, { config, bid, patientId, documentTypes, onUnknownDocumentType, children });
|
|
548
556
|
}
|
|
549
557
|
|
|
550
558
|
// src/connection/useSdk.ts
|
|
@@ -836,7 +844,7 @@ function PresetSelect({
|
|
|
836
844
|
if (!open) return;
|
|
837
845
|
const onDown = (e) => {
|
|
838
846
|
const n = e.target;
|
|
839
|
-
if (_optionalChain([fieldRef, 'access',
|
|
847
|
+
if (_optionalChain([fieldRef, 'access', _10 => _10.current, 'optionalAccess', _11 => _11.contains, 'call', _12 => _12(n)]) || _optionalChain([menuRef, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.contains, 'call', _15 => _15(n)])) return;
|
|
840
848
|
setMenu(null);
|
|
841
849
|
};
|
|
842
850
|
const onScroll = (e) => {
|
|
@@ -868,7 +876,7 @@ function PresetSelect({
|
|
|
868
876
|
"span",
|
|
869
877
|
{
|
|
870
878
|
className: `mr-preset-select__value${selected ? "" : " mr-preset-select__value--placeholder"}`,
|
|
871
|
-
children: _nullishCoalesce(_nullishCoalesce(_optionalChain([selected, 'optionalAccess',
|
|
879
|
+
children: _nullishCoalesce(_nullishCoalesce(_optionalChain([selected, 'optionalAccess', _16 => _16.label]), () => ( placeholder)), () => ( ""))
|
|
872
880
|
}
|
|
873
881
|
),
|
|
874
882
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ChevronsUpDown, { size: 16, "aria-hidden": true, className: "mr-preset-select__chevron" })
|
|
@@ -952,7 +960,7 @@ function MultiSelect({
|
|
|
952
960
|
if (!open) return;
|
|
953
961
|
const onDown = (e) => {
|
|
954
962
|
const n = e.target;
|
|
955
|
-
if (_optionalChain([fieldRef, 'access',
|
|
963
|
+
if (_optionalChain([fieldRef, 'access', _17 => _17.current, 'optionalAccess', _18 => _18.contains, 'call', _19 => _19(n)]) || _optionalChain([menuRef, 'access', _20 => _20.current, 'optionalAccess', _21 => _21.contains, 'call', _22 => _22(n)])) return;
|
|
956
964
|
setMenu(null);
|
|
957
965
|
};
|
|
958
966
|
const onScroll = (e) => {
|
|
@@ -1233,7 +1241,11 @@ function useRecordsConnection() {
|
|
|
1233
1241
|
store.getState().records.setError(e instanceof Error ? e.message : "Failed to refresh");
|
|
1234
1242
|
}
|
|
1235
1243
|
}, [bid, patientId, store, applyItems]);
|
|
1236
|
-
|
|
1244
|
+
const retry = _react.useCallback.call(void 0, async () => {
|
|
1245
|
+
loadedKeyRef.current = null;
|
|
1246
|
+
await load();
|
|
1247
|
+
}, [load]);
|
|
1248
|
+
return { refresh, retry, sourceRefreshedAt, editRecord, deleteRecord };
|
|
1237
1249
|
}
|
|
1238
1250
|
|
|
1239
1251
|
// src/connection/useRecordDetail.ts
|
|
@@ -1315,7 +1327,7 @@ function parseVitals(report) {
|
|
|
1315
1327
|
}
|
|
1316
1328
|
var isOutOfRange = (v) => v.status === "high" || v.status === "low";
|
|
1317
1329
|
function vitalsNoteHeading(opts) {
|
|
1318
|
-
return `**Vitals / Lab parameters \u2014 ${_nullishCoalesce(_optionalChain([opts, 'optionalAccess',
|
|
1330
|
+
return `**Vitals / Lab parameters \u2014 ${_nullishCoalesce(_optionalChain([opts, 'optionalAccess', _23 => _23.reportName]), () => ( "Report"))}${_optionalChain([opts, 'optionalAccess', _24 => _24.date]) ? ` (${opts.date})` : ""}:**`;
|
|
1319
1331
|
}
|
|
1320
1332
|
function vitalsToNoteText(vitals, opts) {
|
|
1321
1333
|
const lines = vitals.map((v) => {
|
|
@@ -1358,7 +1370,7 @@ function useRecordPreview(documentId) {
|
|
|
1358
1370
|
if (cancelled) return;
|
|
1359
1371
|
const rawFiles = _nullishCoalesce(res.files, () => ( []));
|
|
1360
1372
|
const files = rawFiles.filter((f) => f.asset_url).map((f) => ({ url: f.asset_url, kind: classifyFile(f.file_type) }));
|
|
1361
|
-
const isHtml = (_nullishCoalesce(_optionalChain([rawFiles, 'access',
|
|
1373
|
+
const isHtml = (_nullishCoalesce(_optionalChain([rawFiles, 'access', _25 => _25[0], 'optionalAccess', _26 => _26.file_type]), () => ( ""))).toUpperCase() === "HTML";
|
|
1362
1374
|
const vitals = res.smart_report ? parseVitals(res.smart_report) : [];
|
|
1363
1375
|
setData({
|
|
1364
1376
|
status: "loaded",
|
|
@@ -1453,7 +1465,7 @@ function resolveContentType(file) {
|
|
|
1453
1465
|
if (t === "image/jpeg" || t === "image/jpg" || t === "image/png" || t === "application/pdf") {
|
|
1454
1466
|
return t;
|
|
1455
1467
|
}
|
|
1456
|
-
const ext = _optionalChain([file, 'access',
|
|
1468
|
+
const ext = _optionalChain([file, 'access', _27 => _27.name, 'access', _28 => _28.split, 'call', _29 => _29("."), 'access', _30 => _30.pop, 'call', _31 => _31(), 'optionalAccess', _32 => _32.toLowerCase, 'call', _33 => _33()]);
|
|
1457
1469
|
if (ext === "pdf") return "application/pdf";
|
|
1458
1470
|
if (ext === "png") return "image/png";
|
|
1459
1471
|
if (ext === "jpg" || ext === "jpeg") return "image/jpeg";
|
|
@@ -1492,12 +1504,12 @@ function useUploadConnection() {
|
|
|
1492
1504
|
patientId
|
|
1493
1505
|
});
|
|
1494
1506
|
const queue = store.getState().uploadQueue;
|
|
1495
|
-
if (res.message === "queued_offline" || !_optionalChain([res, 'access',
|
|
1507
|
+
if (res.message === "queued_offline" || !_optionalChain([res, 'access', _34 => _34.batch_response, 'optionalAccess', _35 => _35.length])) {
|
|
1496
1508
|
throw new Error("No internet connection. Your upload will retry automatically when you're back online.");
|
|
1497
1509
|
}
|
|
1498
1510
|
localIds.forEach((id, i) => {
|
|
1499
|
-
const documentId = _nullishCoalesce(_optionalChain([res, 'access',
|
|
1500
|
-
const errorDetails = _optionalChain([res, 'access',
|
|
1511
|
+
const documentId = _nullishCoalesce(_optionalChain([res, 'access', _36 => _36.batch_response, 'optionalAccess', _37 => _37[i], 'optionalAccess', _38 => _38.document_id]), () => ( ""));
|
|
1512
|
+
const errorDetails = _optionalChain([res, 'access', _39 => _39.batch_response, 'optionalAccess', _40 => _40[i], 'optionalAccess', _41 => _41.error_details]);
|
|
1501
1513
|
if (errorDetails) {
|
|
1502
1514
|
queue.markFailed(id);
|
|
1503
1515
|
} else {
|
|
@@ -1526,28 +1538,6 @@ function useLogout() {
|
|
|
1526
1538
|
}, []);
|
|
1527
1539
|
}
|
|
1528
1540
|
|
|
1529
|
-
// src/connection/useDenialList.ts
|
|
1530
|
-
|
|
1531
|
-
var HUB_URLS = {
|
|
1532
|
-
prod: "https://hub.eka.care",
|
|
1533
|
-
dev: "https://hub.dev.eka.care"
|
|
1534
|
-
};
|
|
1535
|
-
function useDenialList(environment) {
|
|
1536
|
-
const [denialList, setDenialList] = _react.useState.call(void 0, []);
|
|
1537
|
-
_react.useEffect.call(void 0, () => {
|
|
1538
|
-
const controller = new AbortController();
|
|
1539
|
-
const hubUrl = _nullishCoalesce(HUB_URLS[_nullishCoalesce(environment, () => ( "prod"))], () => ( HUB_URLS.prod));
|
|
1540
|
-
const url = `${hubUrl}/onboarding/5/configuration/?config_keys=denial_list&format=json`;
|
|
1541
|
-
fetch(url, { signal: controller.signal }).then((res) => res.json()).then((data) => {
|
|
1542
|
-
const list = _optionalChain([data, 'optionalAccess', _41 => _41.denial_list]);
|
|
1543
|
-
if (Array.isArray(list)) setDenialList(list);
|
|
1544
|
-
}).catch(() => {
|
|
1545
|
-
});
|
|
1546
|
-
return () => controller.abort();
|
|
1547
|
-
}, [environment]);
|
|
1548
|
-
return denialList;
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
1541
|
// src/stores/selectors/recordsSelectors.ts
|
|
1552
1542
|
|
|
1553
1543
|
|
|
@@ -1599,15 +1589,8 @@ function useCasesStatus(store) {
|
|
|
1599
1589
|
|
|
1600
1590
|
|
|
1601
1591
|
// src/helpers/recordFilter.ts
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
if (!skipType && filters.type) {
|
|
1605
|
-
if (filters.type === OTHER_TYPE_SENTINEL) {
|
|
1606
|
-
if (!labelOf || labelOf(r.typeCode) !== "Other") return false;
|
|
1607
|
-
} else if (r.typeCode !== filters.type) {
|
|
1608
|
-
return false;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1592
|
+
function recordMatchesFilters(r, filters, { skipType = false } = {}) {
|
|
1593
|
+
if (!skipType && filters.type && r.typeCode !== filters.type) return false;
|
|
1611
1594
|
const search = filters.search.trim().toLowerCase();
|
|
1612
1595
|
if (search && !r.title.toLowerCase().includes(search)) return false;
|
|
1613
1596
|
if (filters.documentDateFrom != null && r.createdAtEpoch < filters.documentDateFrom) return false;
|
|
@@ -1622,14 +1605,14 @@ function recordMatchesFilters(r, filters, { skipType = false, labelOf } = {}) {
|
|
|
1622
1605
|
}
|
|
1623
1606
|
|
|
1624
1607
|
// src/stores/selectors/filteredSelectors.ts
|
|
1625
|
-
function useFilteredRecordIds(store
|
|
1608
|
+
function useFilteredRecordIds(store) {
|
|
1626
1609
|
const ids = _zustand.useStore.call(void 0, store, (s) => s.records.ids);
|
|
1627
1610
|
const byId = _zustand.useStore.call(void 0, store, (s) => s.records.byId);
|
|
1628
1611
|
const filters = _zustand.useStore.call(void 0, store, (s) => s.filters);
|
|
1629
1612
|
return _react.useMemo.call(void 0, () => {
|
|
1630
1613
|
const filtered = ids.filter((id) => {
|
|
1631
1614
|
const r = byId[id];
|
|
1632
|
-
return r ? recordMatchesFilters(r, filters
|
|
1615
|
+
return r ? recordMatchesFilters(r, filters) : false;
|
|
1633
1616
|
});
|
|
1634
1617
|
const dir = filters.sortDir === "asc" ? 1 : -1;
|
|
1635
1618
|
const sorted = [...filtered].sort((a, b) => {
|
|
@@ -1674,8 +1657,7 @@ function useCasesStatus2() {
|
|
|
1674
1657
|
return useCasesStatus(useSdk().store);
|
|
1675
1658
|
}
|
|
1676
1659
|
function useFilteredRecordIds2() {
|
|
1677
|
-
|
|
1678
|
-
return useFilteredRecordIds(store, labelOf);
|
|
1660
|
+
return useFilteredRecordIds(useSdk().store);
|
|
1679
1661
|
}
|
|
1680
1662
|
|
|
1681
1663
|
// src/views/RecordsView/FilterPopover.tsx
|
|
@@ -1717,22 +1699,7 @@ function FilterPopover({ onClose }) {
|
|
|
1717
1699
|
const ids = _zustand.useStore.call(void 0, store, (s) => s.records.ids);
|
|
1718
1700
|
const typeOptions = _react.useMemo.call(void 0, () => {
|
|
1719
1701
|
const seen = /* @__PURE__ */ new Set();
|
|
1720
|
-
|
|
1721
|
-
const options = [];
|
|
1722
|
-
for (const id of ids) {
|
|
1723
|
-
const code = _optionalChain([byId, 'access', _46 => _46[id], 'optionalAccess', _47 => _47.typeCode]);
|
|
1724
|
-
if (!code) continue;
|
|
1725
|
-
const label = labelOf(code);
|
|
1726
|
-
if (label === "Other") {
|
|
1727
|
-
hasOther = true;
|
|
1728
|
-
continue;
|
|
1729
|
-
}
|
|
1730
|
-
if (seen.has(code)) continue;
|
|
1731
|
-
seen.add(code);
|
|
1732
|
-
options.push({ value: code, label });
|
|
1733
|
-
}
|
|
1734
|
-
if (hasOther) options.push({ value: OTHER_TYPE_SENTINEL, label: "Other" });
|
|
1735
|
-
return options;
|
|
1702
|
+
return ids.map((id) => _optionalChain([byId, 'access', _46 => _46[id], 'optionalAccess', _47 => _47.typeCode])).filter((code) => !!code && !seen.has(code) && !!seen.add(code)).map((code) => ({ value: code, label: labelOf(code) }));
|
|
1736
1703
|
}, [ids, byId, labelOf]);
|
|
1737
1704
|
const [docType, setDocType] = _react.useState.call(void 0, _nullishCoalesce(filters.type, () => ( "")));
|
|
1738
1705
|
const [docPreset, setDocPreset] = _react.useState.call(void 0, filters.documentDatePreset);
|
|
@@ -1934,15 +1901,14 @@ function RecordsToolbar({
|
|
|
1934
1901
|
for (const id of ids) {
|
|
1935
1902
|
const r = byId[id];
|
|
1936
1903
|
if (!r) continue;
|
|
1937
|
-
|
|
1938
|
-
if (
|
|
1939
|
-
|
|
1940
|
-
counts.set(key, (_nullishCoalesce(counts.get(key), () => ( 0))) + 1);
|
|
1904
|
+
if (!counts.has(r.typeCode)) counts.set(r.typeCode, 0);
|
|
1905
|
+
if (recordMatchesFilters(r, filters, { skipType: true })) {
|
|
1906
|
+
counts.set(r.typeCode, (_nullishCoalesce(counts.get(r.typeCode), () => ( 0))) + 1);
|
|
1941
1907
|
}
|
|
1942
1908
|
}
|
|
1943
1909
|
return [...counts.entries()].map(([code, count]) => ({
|
|
1944
1910
|
code,
|
|
1945
|
-
label:
|
|
1911
|
+
label: labelOf(code),
|
|
1946
1912
|
count
|
|
1947
1913
|
}));
|
|
1948
1914
|
}, [ids, byId, labelOf, filters]);
|
|
@@ -3960,7 +3926,7 @@ function VitalCard({
|
|
|
3960
3926
|
|
|
3961
3927
|
var turndown = new (0, _turndown2.default)({ headingStyle: "atx", bulletListMarker: "-" });
|
|
3962
3928
|
var DocumentViewer = _react.lazy.call(void 0,
|
|
3963
|
-
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-
|
|
3929
|
+
() => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-NMFEAQUY.js"))).then((m) => ({ default: m.DocumentViewer }))
|
|
3964
3930
|
);
|
|
3965
3931
|
var MAX_PREVIEW_TAGS = 2;
|
|
3966
3932
|
function RecordPreview({ recordId, onBack }) {
|
|
@@ -4383,12 +4349,11 @@ function useRecordsView({
|
|
|
4383
4349
|
attachedIds,
|
|
4384
4350
|
onAttachManyToContext,
|
|
4385
4351
|
onRemoveAttachment,
|
|
4386
|
-
onToast
|
|
4352
|
+
onToast,
|
|
4353
|
+
allowUpload = true
|
|
4387
4354
|
}) {
|
|
4388
|
-
const { store, labelOf
|
|
4389
|
-
const
|
|
4390
|
-
const allowUpload = !denialList.includes("UPLOAD_MEDICAL_RECORDS");
|
|
4391
|
-
const { refresh, sourceRefreshedAt, deleteRecord } = useRecordsConnection();
|
|
4355
|
+
const { store, labelOf } = useSdk();
|
|
4356
|
+
const { refresh, retry, sourceRefreshedAt, deleteRecord } = useRecordsConnection();
|
|
4392
4357
|
const { upload } = useUploadConnection();
|
|
4393
4358
|
const status = useRecordsStatus2();
|
|
4394
4359
|
const filteredIds = useFilteredRecordIds2();
|
|
@@ -4536,6 +4501,7 @@ function useRecordsView({
|
|
|
4536
4501
|
// data
|
|
4537
4502
|
upload,
|
|
4538
4503
|
refresh,
|
|
4504
|
+
retry,
|
|
4539
4505
|
sourceRefreshedAt,
|
|
4540
4506
|
actions,
|
|
4541
4507
|
allowUpload,
|
|
@@ -4563,7 +4529,8 @@ function RecordsView({
|
|
|
4563
4529
|
attachedIds,
|
|
4564
4530
|
onAttachManyToContext,
|
|
4565
4531
|
onRemoveAttachment,
|
|
4566
|
-
onToast
|
|
4532
|
+
onToast,
|
|
4533
|
+
allowUpload: allowUploadProp = true
|
|
4567
4534
|
}) {
|
|
4568
4535
|
const {
|
|
4569
4536
|
store,
|
|
@@ -4590,6 +4557,7 @@ function RecordsView({
|
|
|
4590
4557
|
isTabEmpty,
|
|
4591
4558
|
upload,
|
|
4592
4559
|
refresh,
|
|
4560
|
+
retry,
|
|
4593
4561
|
sourceRefreshedAt,
|
|
4594
4562
|
actions,
|
|
4595
4563
|
allowUpload,
|
|
@@ -4608,14 +4576,15 @@ function RecordsView({
|
|
|
4608
4576
|
attachedIds,
|
|
4609
4577
|
onAttachManyToContext,
|
|
4610
4578
|
onRemoveAttachment,
|
|
4611
|
-
onToast
|
|
4579
|
+
onToast,
|
|
4580
|
+
allowUpload: allowUploadProp
|
|
4612
4581
|
});
|
|
4613
4582
|
if (isInitialLoading) {
|
|
4614
4583
|
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `mr-records-view${className ? ` ${className}` : ""}`, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsLoadingState, {}) });
|
|
4615
4584
|
}
|
|
4616
4585
|
if (isLoadFailed || isTabEmpty) {
|
|
4617
4586
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
4618
|
-
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `mr-records-view mr-records-view--state${className ? ` ${className}` : ""}`, children: isLoadFailed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsErrorState, { onRetry:
|
|
4587
|
+
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `mr-records-view mr-records-view--state${className ? ` ${className}` : ""}`, children: isLoadFailed ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsErrorState, { onRetry: retry }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsEmptyState, { onUpload: allowUpload ? openUpload : void 0 }) }),
|
|
4619
4588
|
uploadOpen && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
4620
4589
|
UploadModal,
|
|
4621
4590
|
{
|