@eka-care/medical-records-ui 1.0.21 → 1.0.23

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.
@@ -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",
@@ -194,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
194
195
  export {
195
196
  DocumentViewer
196
197
  };
197
- //# sourceMappingURL=DocumentViewer-WXL7OZXK.mjs.map
198
+ //# sourceMappingURL=DocumentViewer-LJR3CLJP.mjs.map
@@ -1 +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), 60_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 (!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\">Loading document...</div>;\n }\n if (state.status === 'error') {\n return <div className=\"mr-doc-viewer__status\">Couldn't load this document.</div>;\n }\n if (!state.text.trim()) {\n return <div className=\"mr-doc-viewer__status\">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"]}
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), 60_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",
@@ -194,4 +195,4 @@ function DocumentViewer({ files, title, isHtml }) {
194
195
 
195
196
 
196
197
  exports.DocumentViewer = DocumentViewer;
197
- //# sourceMappingURL=DocumentViewer-Z3GOU5NU.js.map
198
+ //# sourceMappingURL=DocumentViewer-NM5THP2V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/medical-records-ui/medical-records-ui/dist/DocumentViewer-NM5THP2V.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-NM5THP2V.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), 60_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 createDocumentTypeLabelResolver(types) {
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) => _nullishCoalesce(_nullishCoalesce(fromConfig.get(code), () => ( STATIC_LABELS[code])), () => ( "Other"));
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', _9 => _9.current, 'optionalAccess', _10 => _10.contains, 'call', _11 => _11(n)]) || _optionalChain([menuRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.contains, 'call', _14 => _14(n)])) return;
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', _15 => _15.label]), () => ( placeholder)), () => ( ""))
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', _16 => _16.current, 'optionalAccess', _17 => _17.contains, 'call', _18 => _18(n)]) || _optionalChain([menuRef, 'access', _19 => _19.current, 'optionalAccess', _20 => _20.contains, 'call', _21 => _21(n)])) return;
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
- return { refresh, sourceRefreshedAt, editRecord, deleteRecord };
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', _22 => _22.reportName]), () => ( "Report"))}${_optionalChain([opts, 'optionalAccess', _23 => _23.date]) ? ` (${opts.date})` : ""}:**`;
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', _24 => _24[0], 'optionalAccess', _25 => _25.file_type]), () => ( ""))).toUpperCase() === "HTML";
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', _26 => _26.name, 'access', _27 => _27.split, 'call', _28 => _28("."), 'access', _29 => _29.pop, 'call', _30 => _30(), 'optionalAccess', _31 => _31.toLowerCase, 'call', _32 => _32()]);
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', _33 => _33.batch_response, 'optionalAccess', _34 => _34.length])) {
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', _35 => _35.batch_response, 'optionalAccess', _36 => _36[i], 'optionalAccess', _37 => _37.document_id]), () => ( ""));
1500
- const errorDetails = _optionalChain([res, 'access', _38 => _38.batch_response, 'optionalAccess', _39 => _39[i], 'optionalAccess', _40 => _40.error_details]);
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
- var OTHER_TYPE_SENTINEL = "__other__";
1603
- function recordMatchesFilters(r, filters, { skipType = false, labelOf } = {}) {
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, labelOf) {
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, { labelOf }) : false;
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
- const { store, labelOf } = useSdk();
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
- let hasOther = false;
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
- const key = labelOf(r.typeCode) === "Other" ? OTHER_TYPE_SENTINEL : r.typeCode;
1938
- if (!counts.has(key)) counts.set(key, 0);
1939
- if (recordMatchesFilters(r, filters, { skipType: true, labelOf })) {
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: code === OTHER_TYPE_SENTINEL ? "Other" : labelOf(code),
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-Z3GOU5NU.js"))).then((m) => ({ default: m.DocumentViewer }))
3929
+ () => Promise.resolve().then(() => _interopRequireWildcard(require("./DocumentViewer-NM5THP2V.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, environment } = useSdk();
4389
- const denialList = useDenialList(environment);
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: refresh }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecordsEmptyState, { onUpload: allowUpload ? openUpload : void 0 }) }),
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
  {