diff --git a/Media.JoshHeaps.Net/Pages/MedicalDocs.cshtml b/Media.JoshHeaps.Net/Pages/MedicalDocs.cshtml index 81b0c5b..abd2830 100644 --- a/Media.JoshHeaps.Net/Pages/MedicalDocs.cshtml +++ b/Media.JoshHeaps.Net/Pages/MedicalDocs.cshtml @@ -273,6 +273,18 @@ + +
+ @section Scripts { diff --git a/Media.JoshHeaps.Net/wwwroot/css/medical-docs.css b/Media.JoshHeaps.Net/wwwroot/css/medical-docs.css index b71b9d5..189a697 100644 --- a/Media.JoshHeaps.Net/wwwroot/css/medical-docs.css +++ b/Media.JoshHeaps.Net/wwwroot/css/medical-docs.css @@ -1369,6 +1369,131 @@ line-height: 1.6; } +/* ======================== */ +/* Document Viewer Modal */ +/* ======================== */ + +.doc-viewer-overlay { + position: fixed; + inset: 0; + z-index: 1000; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.doc-viewer-modal { + background: var(--bg-primary); + border: 1px solid var(--border-primary); + border-radius: 12px; + display: flex; + flex-direction: column; + max-width: 90vw; + max-height: 90vh; + width: auto; + min-width: 300px; + overflow: hidden; +} + +.doc-viewer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid var(--border-primary); + flex-shrink: 0; +} + +.doc-viewer-title { + font-size: 14px; + font-weight: 500; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.doc-viewer-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 24px; + line-height: 1; + cursor: pointer; + padding: 0 4px; + flex-shrink: 0; + margin-left: 12px; + transition: color 0.2s ease; +} + +.doc-viewer-close:hover { + color: var(--text-primary); +} + +.doc-viewer-body { + padding: 16px; + overflow: auto; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + min-height: 0; +} + +.doc-viewer-loading, +.doc-viewer-error { + color: var(--text-secondary); + font-size: 14px; + padding: 40px 20px; + text-align: center; +} + +.doc-viewer-error { + color: var(--danger); +} + +.doc-viewer-image { + max-width: 100%; + max-height: calc(90vh - 80px); + object-fit: contain; + border-radius: 4px; +} + +.doc-viewer-audio { + width: 100%; + min-width: 300px; + max-width: 500px; +} + +.doc-viewer-pdf { + width: 80vw; + height: calc(90vh - 80px); + max-width: 100%; + border: none; + border-radius: 4px; +} + +.doc-viewer-text { + font-family: inherit; + font-size: 14px; + color: var(--text-primary); + background: var(--bg-secondary); + border: 1px solid var(--border-primary); + border-radius: 6px; + padding: 16px; + max-height: calc(90vh - 120px); + overflow-y: auto; + white-space: pre-wrap; + word-wrap: break-word; + width: 100%; + margin: 0; + line-height: 1.6; +} + /* ======================== */ /* Responsive (<=768px) */ /* ======================== */ @@ -1532,6 +1657,23 @@ .doc-detail-row-inline > div[style*="grid-column"] { grid-column: auto !important; } + + .doc-viewer-overlay { + padding: 0; + } + + .doc-viewer-modal { + max-width: 100vw; + max-height: 100vh; + width: 100vw; + height: 100vh; + border-radius: 0; + } + + .doc-viewer-pdf { + width: 100%; + height: calc(100vh - 80px); + } } /* ======================== */ diff --git a/Media.JoshHeaps.Net/wwwroot/js/medical-docs/documents.js b/Media.JoshHeaps.Net/wwwroot/js/medical-docs/documents.js index 97fcf36..1280357 100644 --- a/Media.JoshHeaps.Net/wwwroot/js/medical-docs/documents.js +++ b/Media.JoshHeaps.Net/wwwroot/js/medical-docs/documents.js @@ -78,6 +78,18 @@ select.value = currentVal; }; + // --- Viewer --- + + app.getViewerType = function (doc) { + if (doc.documentType === 'note') return 'text'; + const mime = (doc.mimeType || '').toLowerCase(); + if (mime.startsWith('image/')) return 'image'; + if (mime.startsWith('audio/')) return 'audio'; + if (mime.startsWith('text/')) return 'text'; + if (mime === 'application/pdf') return 'pdf'; + return null; + }; + // --- Documents --- app.loadDocuments = async function () { @@ -135,6 +147,11 @@ const aiStatusBadge = app.getAiStatusBadge(doc); + const viewerType = app.getViewerType(doc); + const viewBtn = viewerType + ? `` + : ''; + const downloadBtn = doc.documentType === 'file' ? `` : ''; @@ -166,6 +183,7 @@${app.escapeHtml(doc.description || '')}`;
+ return;
+ }
+
+ try {
+ const res = await fetch(`${app.API}/documents/${id}/download`);
+ if (!res.ok) {
+ body.innerHTML = '${app.escapeHtml(text)}`;
+ URL.revokeObjectURL(currentBlobUrl);
+ currentBlobUrl = null;
+ }
+ } catch {
+ body.innerHTML = '