@omniradiology/omnirad 0.1.3

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.
Files changed (155) hide show
  1. package/README.md +438 -0
  2. package/app/api/ai-config/route.ts +131 -0
  3. package/app/api/ai-config/test/route.ts +49 -0
  4. package/app/api/auth/auto-login/route.ts +66 -0
  5. package/app/api/auth/check/route.ts +17 -0
  6. package/app/api/auth/login/route.ts +72 -0
  7. package/app/api/auth/logout/route.ts +25 -0
  8. package/app/api/auth/me/route.ts +75 -0
  9. package/app/api/auth/password/route.ts +49 -0
  10. package/app/api/auth/setup/route.ts +63 -0
  11. package/app/api/auth/users/route.ts +100 -0
  12. package/app/api/auth/wipe/route.ts +27 -0
  13. package/app/api/compliance/anonymize/patient/[id]/route.ts +104 -0
  14. package/app/api/compliance/audit/route.ts +110 -0
  15. package/app/api/compliance/export/patient/[id]/route.ts +108 -0
  16. package/app/api/compliance/restrict/patient/[id]/route.ts +59 -0
  17. package/app/api/compliance/settings/route.ts +93 -0
  18. package/app/api/copilot/annotate/route.ts +94 -0
  19. package/app/api/copilot/chat/route.ts +238 -0
  20. package/app/api/copilot/history/route.ts +95 -0
  21. package/app/api/copilot/reports/route.ts +81 -0
  22. package/app/api/fhir/Bundle/report/[id]/route.ts +85 -0
  23. package/app/api/fhir/DiagnosticReport/[id]/route.ts +45 -0
  24. package/app/api/fhir/ImagingStudy/[id]/route.ts +57 -0
  25. package/app/api/fhir/Patient/[id]/route.ts +26 -0
  26. package/app/api/fhir/ServiceRequest/route.ts +85 -0
  27. package/app/api/fhir/config/route.ts +102 -0
  28. package/app/api/fhir/config/test-connection/route.ts +49 -0
  29. package/app/api/fhir/metadata/route.ts +51 -0
  30. package/app/api/pacs/metadata/route.ts +32 -0
  31. package/app/api/pacs/qido/instances/route.ts +39 -0
  32. package/app/api/pacs/qido/series/route.ts +38 -0
  33. package/app/api/pacs/qido/studies/route.ts +37 -0
  34. package/app/api/pacs/test/route.ts +30 -0
  35. package/app/api/pacs/wado/render/route.ts +51 -0
  36. package/app/api/patients/[id]/reports/route.ts +18 -0
  37. package/app/api/patients/[id]/route.ts +43 -0
  38. package/app/api/patients/merge/route.ts +57 -0
  39. package/app/api/patients/route.ts +67 -0
  40. package/app/api/patients/search/route.ts +25 -0
  41. package/app/api/reports/[id]/route.ts +84 -0
  42. package/app/api/reports/[id]/status/route.ts +87 -0
  43. package/app/api/reports/clear/route.ts +16 -0
  44. package/app/api/reports/route.ts +112 -0
  45. package/app/api/segmentation-config/route.ts +238 -0
  46. package/app/api/settings/route.ts +245 -0
  47. package/app/api/settings/test-supabase/route.ts +103 -0
  48. package/app/api/upload/route.ts +48 -0
  49. package/app/copilot/page.tsx +30 -0
  50. package/app/globals.css +141 -0
  51. package/app/history/page.tsx +242 -0
  52. package/app/icon.svg +3 -0
  53. package/app/layout.tsx +47 -0
  54. package/app/login/page.tsx +175 -0
  55. package/app/pacs/page.tsx +78 -0
  56. package/app/page.tsx +125 -0
  57. package/app/patients/[id]/page.tsx +315 -0
  58. package/app/patients/page.tsx +110 -0
  59. package/app/profile/page.tsx +208 -0
  60. package/app/reports/page.tsx +432 -0
  61. package/app/settings/page.tsx +454 -0
  62. package/app/setup/page.tsx +199 -0
  63. package/components/admin/AuditLogTable.tsx +293 -0
  64. package/components/copilot/ActivityIndicator.tsx +215 -0
  65. package/components/copilot/ChatHistoryPanel.tsx +140 -0
  66. package/components/copilot/ChatMessage.tsx +251 -0
  67. package/components/copilot/ClickableReference.tsx +40 -0
  68. package/components/copilot/CopilotCornerstoneViewer.tsx +562 -0
  69. package/components/copilot/CopilotPanel.tsx +311 -0
  70. package/components/copilot/FindingsList.tsx +75 -0
  71. package/components/copilot/ViewerPanel.tsx +460 -0
  72. package/components/copilot/WorkspaceLayout.tsx +398 -0
  73. package/components/dashboard/AIConfigPanel.tsx +339 -0
  74. package/components/dashboard/AppearancePanel.tsx +491 -0
  75. package/components/dashboard/ApprovalModal.tsx +163 -0
  76. package/components/dashboard/CollaborationPanel.tsx +134 -0
  77. package/components/dashboard/CopilotConfigPanel.tsx +337 -0
  78. package/components/dashboard/DicomViewer.tsx +645 -0
  79. package/components/dashboard/FhirIntegrationPanel.tsx +331 -0
  80. package/components/dashboard/FullReportOverlay.tsx +269 -0
  81. package/components/dashboard/ImageViewer.tsx +541 -0
  82. package/components/dashboard/PatientForm.tsx +597 -0
  83. package/components/dashboard/RejectionModal.tsx +74 -0
  84. package/components/dashboard/ReportEditor.tsx +160 -0
  85. package/components/dashboard/ReportTemplates.tsx +729 -0
  86. package/components/dashboard/ReportView.tsx +539 -0
  87. package/components/dashboard/SegmentationConfigPanel.tsx +490 -0
  88. package/components/dashboard/StudyPlaceholder.tsx +17 -0
  89. package/components/dashboard/SupabaseIntegrationPanel.tsx +345 -0
  90. package/components/dashboard/UserManagementPanel.tsx +272 -0
  91. package/components/layout/ClientLayout.tsx +39 -0
  92. package/components/layout/Header.tsx +20 -0
  93. package/components/layout/Sidebar.tsx +119 -0
  94. package/components/pacs/PacsImageViewerModal.tsx +121 -0
  95. package/components/pacs/PacsSearchFilters.tsx +117 -0
  96. package/components/pacs/PacsSeriesViewer.tsx +190 -0
  97. package/components/pacs/PacsStudyTable.tsx +113 -0
  98. package/components/patients/patient-card.tsx +117 -0
  99. package/components/patients/patient-header.tsx +122 -0
  100. package/components/patients/patient-search.tsx +137 -0
  101. package/components/patients/patient-timeline.tsx +153 -0
  102. package/components/settings/ComplianceSettingsPanel.tsx +278 -0
  103. package/components/settings/SecurityPanel.tsx +418 -0
  104. package/components/ui/badge.tsx +19 -0
  105. package/components/ui/basic.tsx +156 -0
  106. package/db/index.ts +350 -0
  107. package/db/migrations/0000_odd_quasimodo.sql +117 -0
  108. package/db/migrations/meta/0000_snapshot.json +778 -0
  109. package/db/migrations/meta/_journal.json +13 -0
  110. package/db/schema.ts +239 -0
  111. package/drizzle.config.ts +10 -0
  112. package/lib/api.ts +689 -0
  113. package/lib/auth.ts +22 -0
  114. package/lib/copilot/action-executor.ts +94 -0
  115. package/lib/copilot/action-types.ts +72 -0
  116. package/lib/copilot/coordinate-mapper.ts +84 -0
  117. package/lib/dicomImageExtractor.ts +103 -0
  118. package/lib/dicomMetadataParser.ts +111 -0
  119. package/lib/fhir/client.ts +25 -0
  120. package/lib/fhir/constants.ts +21 -0
  121. package/lib/fhir/diagnostic-report.ts +88 -0
  122. package/lib/fhir/helpers.ts +73 -0
  123. package/lib/fhir/imaging-study.ts +49 -0
  124. package/lib/fhir/patient.ts +55 -0
  125. package/lib/fhir/service-request.ts +85 -0
  126. package/lib/fhir.ts +6 -0
  127. package/lib/pacs/dicom-utils.ts +72 -0
  128. package/lib/pacs/dicomweb.ts +72 -0
  129. package/lib/pacs/server-utils.ts +37 -0
  130. package/lib/patients.ts +25 -0
  131. package/lib/pdfHelper.ts +119 -0
  132. package/lib/reportHtmlGenerator.ts +581 -0
  133. package/lib/security/audit.ts +180 -0
  134. package/lib/security/authz.ts +246 -0
  135. package/lib/security/phi-redaction.ts +156 -0
  136. package/lib/security/rate-limit.ts +106 -0
  137. package/lib/security/secrets.ts +179 -0
  138. package/lib/supabase.ts +72 -0
  139. package/lib/utils.ts +6 -0
  140. package/next.config.ts +35 -0
  141. package/package.json +76 -0
  142. package/public/file.svg +1 -0
  143. package/public/globe.svg +1 -0
  144. package/public/logo.svg +8 -0
  145. package/public/next.svg +1 -0
  146. package/public/omnirad-favicon.svg +8 -0
  147. package/public/vercel.svg +1 -0
  148. package/public/window.svg +1 -0
  149. package/tsconfig.json +34 -0
  150. package/types/copilot-viewer.ts +155 -0
  151. package/types/copilot.ts +105 -0
  152. package/types/fhir.ts +21 -0
  153. package/types/html2pdf.d.ts +20 -0
  154. package/types/index.ts +139 -0
  155. package/types/pacs.ts +41 -0
@@ -0,0 +1,278 @@
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+
5
+ interface ComplianceConfig {
6
+ dataRetentionDays: number;
7
+ auditRetentionDays: number;
8
+ sessionTimeoutMinutes: number;
9
+ idleTimeoutMinutes: number;
10
+ enableGdprExport: boolean;
11
+ enableGdprAnonymize: boolean;
12
+ enableGdprRestriction: boolean;
13
+ legalBasis: string;
14
+ privacyPolicyUrl: string;
15
+ dpoContactEmail: string;
16
+ }
17
+
18
+ const defaults: ComplianceConfig = {
19
+ dataRetentionDays: 2555,
20
+ auditRetentionDays: 2555,
21
+ sessionTimeoutMinutes: 15,
22
+ idleTimeoutMinutes: 30,
23
+ enableGdprExport: true,
24
+ enableGdprAnonymize: true,
25
+ enableGdprRestriction: true,
26
+ legalBasis: "legitimate_interest",
27
+ privacyPolicyUrl: "",
28
+ dpoContactEmail: "",
29
+ };
30
+
31
+ export default function ComplianceSettingsPanel() {
32
+ const [config, setConfig] = useState<ComplianceConfig>(defaults);
33
+ const [loading, setLoading] = useState(true);
34
+ const [saving, setSaving] = useState(false);
35
+ const [saved, setSaved] = useState(false);
36
+
37
+ useEffect(() => {
38
+ fetch("/api/compliance/settings")
39
+ .then(r => r.ok ? r.json() : defaults)
40
+ .then(data => setConfig(data))
41
+ .catch(() => {})
42
+ .finally(() => setLoading(false));
43
+ }, []);
44
+
45
+ const handleSave = async () => {
46
+ setSaving(true);
47
+ setSaved(false);
48
+ try {
49
+ const res = await fetch("/api/compliance/settings", {
50
+ method: "PUT",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify(config),
53
+ });
54
+ if (res.ok) setSaved(true);
55
+ } catch {}
56
+ setSaving(false);
57
+ setTimeout(() => setSaved(false), 3000);
58
+ };
59
+
60
+ if (loading) {
61
+ return <div style={{ padding: "24px", opacity: 0.5 }}>Loading compliance settings...</div>;
62
+ }
63
+
64
+ return (
65
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
66
+ {/* HIPAA Section */}
67
+ <div style={sectionStyle}>
68
+ <h3 style={sectionTitleStyle}>🏥 HIPAA Settings</h3>
69
+
70
+ <div style={fieldGroupStyle}>
71
+ <div style={fieldStyle}>
72
+ <label style={labelStyle}>Data Retention Period (days)</label>
73
+ <input
74
+ type="number"
75
+ min={365}
76
+ max={9999}
77
+ value={config.dataRetentionDays}
78
+ onChange={(e) => setConfig(c => ({ ...c, dataRetentionDays: parseInt(e.target.value) || 2555 }))}
79
+ style={inputStyle}
80
+ />
81
+ <span style={hintStyle}>HIPAA requires minimum 6 years (2190 days). Default: ~7 years</span>
82
+ </div>
83
+ <div style={fieldStyle}>
84
+ <label style={labelStyle}>Audit Log Retention (days)</label>
85
+ <input
86
+ type="number"
87
+ min={365}
88
+ max={9999}
89
+ value={config.auditRetentionDays}
90
+ onChange={(e) => setConfig(c => ({ ...c, auditRetentionDays: parseInt(e.target.value) || 2555 }))}
91
+ style={inputStyle}
92
+ />
93
+ <span style={hintStyle}>Must match or exceed data retention period</span>
94
+ </div>
95
+ </div>
96
+
97
+ <div style={fieldGroupStyle}>
98
+ <div style={fieldStyle}>
99
+ <label style={labelStyle}>Session Timeout (minutes)</label>
100
+ <input
101
+ type="number"
102
+ min={5}
103
+ max={480}
104
+ value={config.sessionTimeoutMinutes}
105
+ onChange={(e) => setConfig(c => ({ ...c, sessionTimeoutMinutes: parseInt(e.target.value) || 15 }))}
106
+ style={inputStyle}
107
+ />
108
+ <span style={hintStyle}>HIPAA recommends 15 minutes or less</span>
109
+ </div>
110
+ <div style={fieldStyle}>
111
+ <label style={labelStyle}>Idle Timeout (minutes)</label>
112
+ <input
113
+ type="number"
114
+ min={5}
115
+ max={480}
116
+ value={config.idleTimeoutMinutes}
117
+ onChange={(e) => setConfig(c => ({ ...c, idleTimeoutMinutes: parseInt(e.target.value) || 30 }))}
118
+ style={inputStyle}
119
+ />
120
+ <span style={hintStyle}>Auto-logout after inactivity</span>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ {/* GDPR Section */}
126
+ <div style={sectionStyle}>
127
+ <h3 style={sectionTitleStyle}>🇪🇺 GDPR Settings</h3>
128
+
129
+ <div style={fieldGroupStyle}>
130
+ <div style={fieldStyle}>
131
+ <label style={labelStyle}>Legal Basis for Processing</label>
132
+ <select
133
+ value={config.legalBasis}
134
+ onChange={(e) => setConfig(c => ({ ...c, legalBasis: e.target.value }))}
135
+ style={inputStyle}
136
+ >
137
+ <option value="legitimate_interest">Legitimate Interest</option>
138
+ <option value="consent">Patient Consent</option>
139
+ <option value="contract">Contractual Necessity</option>
140
+ <option value="legal_obligation">Legal Obligation</option>
141
+ </select>
142
+ </div>
143
+ </div>
144
+
145
+ <div style={{ display: "flex", flexDirection: "column", gap: "12px", marginTop: "8px" }}>
146
+ <label style={toggleStyle}>
147
+ <input
148
+ type="checkbox"
149
+ checked={config.enableGdprExport}
150
+ onChange={(e) => setConfig(c => ({ ...c, enableGdprExport: e.target.checked }))}
151
+ style={{ marginRight: "8px" }}
152
+ />
153
+ <span>Enable Data Export (Article 20 — Right to Data Portability)</span>
154
+ </label>
155
+ <label style={toggleStyle}>
156
+ <input
157
+ type="checkbox"
158
+ checked={config.enableGdprRestriction}
159
+ onChange={(e) => setConfig(c => ({ ...c, enableGdprRestriction: e.target.checked }))}
160
+ style={{ marginRight: "8px" }}
161
+ />
162
+ <span>Enable Processing Restriction (Article 18 — Right to Restriction)</span>
163
+ </label>
164
+ <label style={toggleStyle}>
165
+ <input
166
+ type="checkbox"
167
+ checked={config.enableGdprAnonymize}
168
+ onChange={(e) => setConfig(c => ({ ...c, enableGdprAnonymize: e.target.checked }))}
169
+ style={{ marginRight: "8px" }}
170
+ />
171
+ <span>Enable Anonymization (Article 17 — Right to Erasure)</span>
172
+ </label>
173
+ </div>
174
+
175
+ <div style={{ ...fieldGroupStyle, marginTop: "16px" }}>
176
+ <div style={fieldStyle}>
177
+ <label style={labelStyle}>DPO Contact Email</label>
178
+ <input
179
+ type="email"
180
+ placeholder="dpo@yourhospital.com"
181
+ value={config.dpoContactEmail}
182
+ onChange={(e) => setConfig(c => ({ ...c, dpoContactEmail: e.target.value }))}
183
+ style={inputStyle}
184
+ />
185
+ </div>
186
+ <div style={fieldStyle}>
187
+ <label style={labelStyle}>Privacy Policy URL</label>
188
+ <input
189
+ type="url"
190
+ placeholder="https://yourhospital.com/privacy"
191
+ value={config.privacyPolicyUrl}
192
+ onChange={(e) => setConfig(c => ({ ...c, privacyPolicyUrl: e.target.value }))}
193
+ style={inputStyle}
194
+ />
195
+ </div>
196
+ </div>
197
+ </div>
198
+
199
+ {/* Save Button */}
200
+ <div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
201
+ <button
202
+ onClick={handleSave}
203
+ disabled={saving}
204
+ style={{
205
+ padding: "10px 24px",
206
+ borderRadius: "8px",
207
+ border: "none",
208
+ background: saving ? "#6b7280" : "linear-gradient(135deg, #3b82f6, #8b5cf6)",
209
+ color: "#fff",
210
+ fontWeight: 600,
211
+ cursor: saving ? "default" : "pointer",
212
+ fontSize: "14px",
213
+ transition: "all 0.2s",
214
+ }}
215
+ >
216
+ {saving ? "Saving..." : "Save Compliance Settings"}
217
+ </button>
218
+ {saved && (
219
+ <span style={{ color: "#22c55e", fontWeight: 500, animation: "fadeIn 0.3s" }}>
220
+ ✓ Settings saved
221
+ </span>
222
+ )}
223
+ </div>
224
+ </div>
225
+ );
226
+ }
227
+
228
+ const sectionStyle: React.CSSProperties = {
229
+ padding: "20px",
230
+ borderRadius: "12px",
231
+ background: "var(--card-bg, rgba(255,255,255,0.05))",
232
+ border: "1px solid var(--border-color, rgba(255,255,255,0.1))",
233
+ };
234
+
235
+ const sectionTitleStyle: React.CSSProperties = {
236
+ margin: "0 0 16px",
237
+ fontSize: "16px",
238
+ fontWeight: 600,
239
+ };
240
+
241
+ const fieldGroupStyle: React.CSSProperties = {
242
+ display: "grid",
243
+ gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
244
+ gap: "16px",
245
+ };
246
+
247
+ const fieldStyle: React.CSSProperties = {
248
+ display: "flex",
249
+ flexDirection: "column",
250
+ gap: "4px",
251
+ };
252
+
253
+ const labelStyle: React.CSSProperties = {
254
+ fontSize: "13px",
255
+ fontWeight: 500,
256
+ opacity: 0.8,
257
+ };
258
+
259
+ const inputStyle: React.CSSProperties = {
260
+ padding: "8px 12px",
261
+ borderRadius: "6px",
262
+ border: "1px solid var(--border-color, rgba(255,255,255,0.15))",
263
+ background: "var(--input-bg, rgba(255,255,255,0.05))",
264
+ color: "inherit",
265
+ fontSize: "14px",
266
+ };
267
+
268
+ const hintStyle: React.CSSProperties = {
269
+ fontSize: "11px",
270
+ opacity: 0.5,
271
+ };
272
+
273
+ const toggleStyle: React.CSSProperties = {
274
+ display: "flex",
275
+ alignItems: "center",
276
+ cursor: "pointer",
277
+ fontSize: "14px",
278
+ };