@omersx/bemms 0.0.0-stage → 1.0.0

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 (335) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +203 -2
  3. package/drizzle.config.ts +11 -0
  4. package/next.config.ts +51 -0
  5. package/package.json +99 -3
  6. package/postcss.config.mjs +7 -0
  7. package/public/hospital_demo_devices.csv +32 -0
  8. package/public/icons/icon-192.png +0 -0
  9. package/public/icons/icon-512.png +0 -0
  10. package/public/icons/icon-maskable-512.png +0 -0
  11. package/public/manifest.webmanifest +31 -0
  12. package/public/sw.js +177 -0
  13. package/src/app/(auth)/layout.tsx +20 -0
  14. package/src/app/(auth)/login/page.tsx +135 -0
  15. package/src/app/(dashboard)/admin/audit-logs/page.tsx +178 -0
  16. package/src/app/(dashboard)/admin/data/page.tsx +37 -0
  17. package/src/app/(dashboard)/admin/departments/[deptId]/edit/page.tsx +206 -0
  18. package/src/app/(dashboard)/admin/departments/[deptId]/page.tsx +71 -0
  19. package/src/app/(dashboard)/admin/departments/new/page.tsx +6 -0
  20. package/src/app/(dashboard)/admin/departments/page.tsx +6 -0
  21. package/src/app/(dashboard)/admin/general/page.tsx +374 -0
  22. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/edit/page.tsx +304 -0
  23. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/page.tsx +94 -0
  24. package/src/app/(dashboard)/admin/hospitals/new/page.tsx +302 -0
  25. package/src/app/(dashboard)/admin/hospitals/page.tsx +6 -0
  26. package/src/app/(dashboard)/admin/layout.tsx +49 -0
  27. package/src/app/(dashboard)/admin/organizations/[orgId]/edit/page.tsx +172 -0
  28. package/src/app/(dashboard)/admin/organizations/[orgId]/page.tsx +82 -0
  29. package/src/app/(dashboard)/admin/organizations/new/page.tsx +150 -0
  30. package/src/app/(dashboard)/admin/organizations/page.tsx +6 -0
  31. package/src/app/(dashboard)/admin/page.tsx +258 -0
  32. package/src/app/(dashboard)/admin/roles/page.tsx +153 -0
  33. package/src/app/(dashboard)/admin/settings/page.tsx +69 -0
  34. package/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +153 -0
  35. package/src/app/(dashboard)/admin/users/[userId]/page.tsx +317 -0
  36. package/src/app/(dashboard)/admin/users/invite/page.tsx +3 -0
  37. package/src/app/(dashboard)/admin/users/new/page.tsx +5 -0
  38. package/src/app/(dashboard)/admin/users/page.tsx +23 -0
  39. package/src/app/(dashboard)/dashboard/page.tsx +462 -0
  40. package/src/app/(dashboard)/departments/[deptId]/page.tsx +39 -0
  41. package/src/app/(dashboard)/departments/new/page.tsx +330 -0
  42. package/src/app/(dashboard)/departments/page.tsx +52 -0
  43. package/src/app/(dashboard)/devices/[id]/edit/page.tsx +131 -0
  44. package/src/app/(dashboard)/devices/[id]/page.tsx +408 -0
  45. package/src/app/(dashboard)/devices/[id]/qr/page.tsx +54 -0
  46. package/src/app/(dashboard)/devices/new/page.tsx +382 -0
  47. package/src/app/(dashboard)/devices/page.tsx +80 -0
  48. package/src/app/(dashboard)/engineering-team/page.tsx +41 -0
  49. package/src/app/(dashboard)/layout.tsx +43 -0
  50. package/src/app/(dashboard)/maintenance/checklists/page.tsx +57 -0
  51. package/src/app/(dashboard)/maintenance/page.tsx +231 -0
  52. package/src/app/(dashboard)/maintenance/plans/page.tsx +142 -0
  53. package/src/app/(dashboard)/maintenance/tasks/[id]/page.tsx +997 -0
  54. package/src/app/(dashboard)/maintenance/tasks/page.tsx +119 -0
  55. package/src/app/(dashboard)/more/page.tsx +310 -0
  56. package/src/app/(dashboard)/my-department/page.tsx +151 -0
  57. package/src/app/(dashboard)/notifications/notification-list.tsx +103 -0
  58. package/src/app/(dashboard)/notifications/page.tsx +59 -0
  59. package/src/app/(dashboard)/profile/page.tsx +27 -0
  60. package/src/app/(dashboard)/reports/costs/page.tsx +127 -0
  61. package/src/app/(dashboard)/reports/history/page.tsx +105 -0
  62. package/src/app/(dashboard)/reports/inventory/page.tsx +116 -0
  63. package/src/app/(dashboard)/reports/maintenance/page.tsx +136 -0
  64. package/src/app/(dashboard)/reports/page.tsx +120 -0
  65. package/src/app/(dashboard)/reports/tickets/page.tsx +139 -0
  66. package/src/app/(dashboard)/reports/workload/page.tsx +135 -0
  67. package/src/app/(dashboard)/rounds/[id]/page.tsx +197 -0
  68. package/src/app/(dashboard)/rounds/history/page.tsx +82 -0
  69. package/src/app/(dashboard)/rounds/page.tsx +107 -0
  70. package/src/app/(dashboard)/scan/[code]/page.tsx +154 -0
  71. package/src/app/(dashboard)/scan/page.tsx +73 -0
  72. package/src/app/(dashboard)/team/page.tsx +42 -0
  73. package/src/app/(dashboard)/tickets/[id]/page.tsx +465 -0
  74. package/src/app/(dashboard)/tickets/create/page.tsx +722 -0
  75. package/src/app/(dashboard)/tickets/page.tsx +55 -0
  76. package/src/app/(dashboard)/tickets/triage/page.tsx +161 -0
  77. package/src/app/api/admin/data/export/route.ts +316 -0
  78. package/src/app/api/auth/[...nextauth]/route.ts +3 -0
  79. package/src/app/api/health/route.ts +46 -0
  80. package/src/app/api/notifications/sse/route.ts +91 -0
  81. package/src/app/api/notifications/subscribe/route.ts +54 -0
  82. package/src/app/api/notifications/vapid-key/route.ts +7 -0
  83. package/src/app/api/reports/export/route.ts +102 -0
  84. package/src/app/api/tickets/[id]/attachments/route.ts +129 -0
  85. package/src/app/api/tickets/[id]/live-sync/route.ts +52 -0
  86. package/src/app/error.tsx +54 -0
  87. package/src/app/globals.css +89 -0
  88. package/src/app/layout.tsx +59 -0
  89. package/src/app/not-found.tsx +32 -0
  90. package/src/app/page.tsx +5 -0
  91. package/src/components/admin/add-department-user-dialog.tsx +759 -0
  92. package/src/components/admin/add-engineer-dialog.tsx +773 -0
  93. package/src/components/admin/add-user-type-dialog.tsx +89 -0
  94. package/src/components/admin/audit-log-export.tsx +113 -0
  95. package/src/components/admin/audit-log-filters.tsx +255 -0
  96. package/src/components/admin/audit-log-table.tsx +383 -0
  97. package/src/components/admin/data-management/danger-zone.tsx +301 -0
  98. package/src/components/admin/data-management/data-management-tabs.tsx +61 -0
  99. package/src/components/admin/data-management/export-center.tsx +301 -0
  100. package/src/components/admin/data-management/import-wizard.tsx +558 -0
  101. package/src/components/admin/settings-forms/features-form.tsx +115 -0
  102. package/src/components/admin/settings-forms/health-panel.tsx +182 -0
  103. package/src/components/admin/settings-forms/localization-form.tsx +203 -0
  104. package/src/components/admin/settings-forms/retention-form.tsx +164 -0
  105. package/src/components/admin/settings-forms/security-form.tsx +186 -0
  106. package/src/components/admin/system-settings-tabs.tsx +88 -0
  107. package/src/components/admin/user-role-dialog.tsx +293 -0
  108. package/src/components/admin/user-status-dialog.tsx +166 -0
  109. package/src/components/admin/users-page-client.tsx +514 -0
  110. package/src/components/dashboard/action-buttons.tsx +93 -0
  111. package/src/components/dashboard/admin-alerts.tsx +86 -0
  112. package/src/components/dashboard/availability-alerts.tsx +65 -0
  113. package/src/components/dashboard/maintenance-due-list.tsx +87 -0
  114. package/src/components/dashboard/round-widget.tsx +110 -0
  115. package/src/components/dashboard/stat-card.tsx +38 -0
  116. package/src/components/dashboard/ticket-list-compact.tsx +86 -0
  117. package/src/components/departments/department-cards-view.tsx +244 -0
  118. package/src/components/departments/department-detail-view.tsx +467 -0
  119. package/src/components/departments/department-visuals.tsx +190 -0
  120. package/src/components/departments/my-department-view.tsx +333 -0
  121. package/src/components/devices/change-status-dialog.tsx +146 -0
  122. package/src/components/devices/device-action-buttons.tsx +125 -0
  123. package/src/components/devices/device-availability-banner.tsx +59 -0
  124. package/src/components/devices/device-badges.tsx +55 -0
  125. package/src/components/devices/device-documents-tab.tsx +93 -0
  126. package/src/components/devices/device-maintenance-tab.tsx +113 -0
  127. package/src/components/devices/device-mini-qr.tsx +109 -0
  128. package/src/components/devices/device-qr-card.tsx +187 -0
  129. package/src/components/devices/device-signatures-tab.tsx +607 -0
  130. package/src/components/devices/device-tickets-tab.tsx +139 -0
  131. package/src/components/devices/device-timelines.tsx +124 -0
  132. package/src/components/devices/qr-camera-scanner.tsx +167 -0
  133. package/src/components/devices/qr-code-display.tsx +139 -0
  134. package/src/components/devices/quick-add-category-dialog.tsx +135 -0
  135. package/src/components/devices/quick-add-manufacturer-dialog.tsx +212 -0
  136. package/src/components/devices/transfer-device-dialog.tsx +190 -0
  137. package/src/components/layout/header.tsx +80 -0
  138. package/src/components/layout/mobile-nav.tsx +91 -0
  139. package/src/components/layout/more-item-badge.tsx +15 -0
  140. package/src/components/layout/nav-badge.tsx +72 -0
  141. package/src/components/layout/sidebar.tsx +297 -0
  142. package/src/components/maintenance/maintenance-badges.tsx +579 -0
  143. package/src/components/maintenance/maintenance-nav.tsx +107 -0
  144. package/src/components/maintenance/parts-costs-manager.tsx +909 -0
  145. package/src/components/maintenance/signature-card.tsx +413 -0
  146. package/src/components/maintenance/task-action-bar.tsx +955 -0
  147. package/src/components/notifications/notification-bell.tsx +367 -0
  148. package/src/components/notifications/notification-item.tsx +93 -0
  149. package/src/components/notifications/notification-preferences.tsx +182 -0
  150. package/src/components/profile/user-profile-view.tsx +626 -0
  151. package/src/components/providers/nav-counts-provider.tsx +235 -0
  152. package/src/components/providers/session-provider.tsx +7 -0
  153. package/src/components/providers/theme-provider.tsx +124 -0
  154. package/src/components/pwa/install-prompt.tsx +82 -0
  155. package/src/components/pwa/sw-register.tsx +43 -0
  156. package/src/components/reports/export-button.tsx +62 -0
  157. package/src/components/reports/filter-chips.tsx +47 -0
  158. package/src/components/reports/summary-cards.tsx +40 -0
  159. package/src/components/reports/transparency-header.tsx +54 -0
  160. package/src/components/rounds/department-visit-sheet.tsx +199 -0
  161. package/src/components/rounds/mention-textarea.tsx +187 -0
  162. package/src/components/rounds/round-calendar.tsx +233 -0
  163. package/src/components/rounds/round-department-list.tsx +281 -0
  164. package/src/components/rounds/round-history-client.tsx +218 -0
  165. package/src/components/rounds/start-round-card.tsx +184 -0
  166. package/src/components/shared/confirm-dialog.tsx +67 -0
  167. package/src/components/shared/data-table.tsx +104 -0
  168. package/src/components/shared/page-header.tsx +20 -0
  169. package/src/components/shared/sign-out-button.tsx +52 -0
  170. package/src/components/shared/status-badge.tsx +42 -0
  171. package/src/components/shared/table-search.tsx +55 -0
  172. package/src/components/signatures/sign-action-dialog.tsx +253 -0
  173. package/src/components/signatures/signature-history.tsx +121 -0
  174. package/src/components/signatures/signature-status-panel.tsx +164 -0
  175. package/src/components/team/department-coverage.tsx +141 -0
  176. package/src/components/team/edit-engineer-dialog.tsx +242 -0
  177. package/src/components/team/engineer-card.tsx +180 -0
  178. package/src/components/team/engineer-details-dialog.tsx +432 -0
  179. package/src/components/team/schedule-calendar.tsx +204 -0
  180. package/src/components/team/staff-engineer-team-view.tsx +368 -0
  181. package/src/components/team/team-header-stats.tsx +88 -0
  182. package/src/components/team/team-page-client.tsx +216 -0
  183. package/src/components/theme/theme-selector-card.tsx +63 -0
  184. package/src/components/theme/theme-toggle.tsx +65 -0
  185. package/src/components/tickets/staff-status-label.tsx +58 -0
  186. package/src/components/tickets/staff-ticket-card.tsx +109 -0
  187. package/src/components/tickets/staff-ticket-progress-tracker.tsx +405 -0
  188. package/src/components/tickets/staff-tickets-view.tsx +182 -0
  189. package/src/components/tickets/ticket-action-buttons.tsx +873 -0
  190. package/src/components/tickets/ticket-auto-refresh.tsx +197 -0
  191. package/src/components/tickets/ticket-badges.tsx +43 -0
  192. package/src/components/tickets/ticket-card.tsx +197 -0
  193. package/src/components/tickets/ticket-cards-view.tsx +395 -0
  194. package/src/components/tickets/ticket-comment-section.tsx +684 -0
  195. package/src/components/tickets/ticket-dispatch-status.tsx +184 -0
  196. package/src/components/tickets/ticket-list-auto-refresh.tsx +55 -0
  197. package/src/components/tickets/ticket-reporter-profile.tsx +235 -0
  198. package/src/components/tickets/ticket-signatures-panel.tsx +32 -0
  199. package/src/components/tickets/ticket-timeline.tsx +131 -0
  200. package/src/components/tickets/ticket-triage-panel.tsx +124 -0
  201. package/src/components/tickets/tickets-nav.tsx +107 -0
  202. package/src/components/ui/avatar.tsx +43 -0
  203. package/src/components/ui/badge.tsx +34 -0
  204. package/src/components/ui/button.tsx +58 -0
  205. package/src/components/ui/card.tsx +77 -0
  206. package/src/components/ui/checkbox.tsx +31 -0
  207. package/src/components/ui/dialog.tsx +123 -0
  208. package/src/components/ui/dropdown-menu.tsx +126 -0
  209. package/src/components/ui/form.tsx +179 -0
  210. package/src/components/ui/input.tsx +24 -0
  211. package/src/components/ui/label.tsx +27 -0
  212. package/src/components/ui/popover.tsx +34 -0
  213. package/src/components/ui/progress.tsx +36 -0
  214. package/src/components/ui/scroll-area.tsx +47 -0
  215. package/src/components/ui/select.tsx +160 -0
  216. package/src/components/ui/separator.tsx +25 -0
  217. package/src/components/ui/sheet.tsx +131 -0
  218. package/src/components/ui/switch.tsx +30 -0
  219. package/src/components/ui/table.tsx +121 -0
  220. package/src/components/ui/tabs.tsx +62 -0
  221. package/src/components/ui/textarea.tsx +23 -0
  222. package/src/components/ui/tooltip.tsx +27 -0
  223. package/src/lib/actions/__tests__/devices.test.ts +176 -0
  224. package/src/lib/actions/__tests__/signatures.test.ts +303 -0
  225. package/src/lib/actions/__tests__/tickets.test.ts +243 -0
  226. package/src/lib/actions/audit-logs.ts +298 -0
  227. package/src/lib/actions/checklists.ts +429 -0
  228. package/src/lib/actions/dashboard.ts +358 -0
  229. package/src/lib/actions/data-management.ts +723 -0
  230. package/src/lib/actions/departments.ts +369 -0
  231. package/src/lib/actions/device-categories.ts +117 -0
  232. package/src/lib/actions/devices.ts +748 -0
  233. package/src/lib/actions/engineer-rounds.ts +973 -0
  234. package/src/lib/actions/engineers.ts +285 -0
  235. package/src/lib/actions/hospitals.ts +148 -0
  236. package/src/lib/actions/locations.ts +115 -0
  237. package/src/lib/actions/maintenance-plans.ts +316 -0
  238. package/src/lib/actions/maintenance-tasks.ts +415 -0
  239. package/src/lib/actions/manufacturers.ts +132 -0
  240. package/src/lib/actions/nav-counts.ts +144 -0
  241. package/src/lib/actions/notifications.ts +136 -0
  242. package/src/lib/actions/organizations.ts +99 -0
  243. package/src/lib/actions/qr.ts +147 -0
  244. package/src/lib/actions/report-history.ts +77 -0
  245. package/src/lib/actions/reports.ts +192 -0
  246. package/src/lib/actions/roles.ts +32 -0
  247. package/src/lib/actions/signature-auth.ts +377 -0
  248. package/src/lib/actions/signatures.ts +532 -0
  249. package/src/lib/actions/system-settings.ts +274 -0
  250. package/src/lib/actions/tickets.ts +1445 -0
  251. package/src/lib/actions/users.ts +740 -0
  252. package/src/lib/audit/index.ts +103 -0
  253. package/src/lib/auth/__tests__/rbac.test.ts +33 -0
  254. package/src/lib/auth/index.ts +338 -0
  255. package/src/lib/auth/password.ts +10 -0
  256. package/src/lib/auth/rbac.ts +103 -0
  257. package/src/lib/auth/types.ts +57 -0
  258. package/src/lib/constants/departments.ts +16 -0
  259. package/src/lib/constants/manufacturers.ts +32 -0
  260. package/src/lib/db/index.ts +23 -0
  261. package/src/lib/db/schema/audit-logs.ts +42 -0
  262. package/src/lib/db/schema/departments.ts +39 -0
  263. package/src/lib/db/schema/device-categories.ts +38 -0
  264. package/src/lib/db/schema/devices.ts +195 -0
  265. package/src/lib/db/schema/engineer-profiles.ts +63 -0
  266. package/src/lib/db/schema/engineer-rounds.ts +115 -0
  267. package/src/lib/db/schema/enums.ts +148 -0
  268. package/src/lib/db/schema/hospitals.ts +34 -0
  269. package/src/lib/db/schema/index.ts +24 -0
  270. package/src/lib/db/schema/locations.ts +46 -0
  271. package/src/lib/db/schema/maintenance-plans.ts +99 -0
  272. package/src/lib/db/schema/maintenance-records.ts +161 -0
  273. package/src/lib/db/schema/maintenance-tasks.ts +196 -0
  274. package/src/lib/db/schema/manufacturers.ts +60 -0
  275. package/src/lib/db/schema/notifications.ts +48 -0
  276. package/src/lib/db/schema/organizations.ts +24 -0
  277. package/src/lib/db/schema/report-history.ts +28 -0
  278. package/src/lib/db/schema/roles.ts +59 -0
  279. package/src/lib/db/schema/sessions.ts +46 -0
  280. package/src/lib/db/schema/signature-events.ts +48 -0
  281. package/src/lib/db/schema/signature-policies.ts +60 -0
  282. package/src/lib/db/schema/signatures.ts +87 -0
  283. package/src/lib/db/schema/tickets.ts +169 -0
  284. package/src/lib/db/schema/user-assignments.ts +85 -0
  285. package/src/lib/db/schema/users.ts +39 -0
  286. package/src/lib/db/seed.ts +131 -0
  287. package/src/lib/logger/__tests__/logger.test.ts +154 -0
  288. package/src/lib/logger/formatters.ts +159 -0
  289. package/src/lib/logger/index.ts +184 -0
  290. package/src/lib/logger/types.ts +48 -0
  291. package/src/lib/notifications/create.ts +71 -0
  292. package/src/lib/notifications/dispatcher.ts +490 -0
  293. package/src/lib/notifications/index.ts +7 -0
  294. package/src/lib/notifications/push-sender.ts +128 -0
  295. package/src/lib/notifications/push.ts +64 -0
  296. package/src/lib/notifications/triggers.ts +166 -0
  297. package/src/lib/utils/__tests__/crypto.test.ts +30 -0
  298. package/src/lib/utils/__tests__/export.test.ts +55 -0
  299. package/src/lib/utils/__tests__/nav-badge.test.ts +147 -0
  300. package/src/lib/utils/__tests__/staff-status.test.ts +50 -0
  301. package/src/lib/utils/attestations.ts +19 -0
  302. package/src/lib/utils/crypto.ts +18 -0
  303. package/src/lib/utils/csv-parser.ts +181 -0
  304. package/src/lib/utils/export.ts +45 -0
  305. package/src/lib/utils/nav-badge.ts +225 -0
  306. package/src/lib/utils/sound.ts +152 -0
  307. package/src/lib/utils/staff-status.ts +115 -0
  308. package/src/lib/utils.ts +6 -0
  309. package/src/lib/validators/__tests__/auth.test.ts +127 -0
  310. package/src/lib/validators/__tests__/data-management.test.ts +182 -0
  311. package/src/lib/validators/__tests__/devices-profile.test.ts +129 -0
  312. package/src/lib/validators/__tests__/devices.test.ts +98 -0
  313. package/src/lib/validators/__tests__/engineer-rounds.test.ts +170 -0
  314. package/src/lib/validators/__tests__/maintenance.test.ts +178 -0
  315. package/src/lib/validators/__tests__/signatures.test.ts +154 -0
  316. package/src/lib/validators/__tests__/system-settings.test.ts +119 -0
  317. package/src/lib/validators/__tests__/tickets.test.ts +176 -0
  318. package/src/lib/validators/__tests__/users.test.ts +92 -0
  319. package/src/lib/validators/auth.ts +32 -0
  320. package/src/lib/validators/data-management.ts +67 -0
  321. package/src/lib/validators/departments.ts +14 -0
  322. package/src/lib/validators/devices.ts +94 -0
  323. package/src/lib/validators/engineer-rounds.ts +70 -0
  324. package/src/lib/validators/hospitals.ts +17 -0
  325. package/src/lib/validators/locations.ts +15 -0
  326. package/src/lib/validators/maintenance.ts +211 -0
  327. package/src/lib/validators/organizations.ts +11 -0
  328. package/src/lib/validators/reports.ts +31 -0
  329. package/src/lib/validators/signatures.ts +49 -0
  330. package/src/lib/validators/system-settings.ts +55 -0
  331. package/src/lib/validators/tickets.ts +125 -0
  332. package/src/lib/validators/users.ts +43 -0
  333. package/src/middleware.ts +45 -0
  334. package/tailwind.config.ts +76 -0
  335. package/tsconfig.json +28 -0
@@ -0,0 +1,413 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { useState } from "react"
5
+ import { Card, CardContent, CardHeader } from "@/components/ui/card"
6
+ import { Badge } from "@/components/ui/badge"
7
+ import { Button } from "@/components/ui/button"
8
+ import { cn } from "@/lib/utils"
9
+ import {
10
+ ShieldCheck,
11
+ User,
12
+ Clock,
13
+ Hash,
14
+ Copy,
15
+ Check,
16
+ MessageSquare,
17
+ Sparkles,
18
+ Eye,
19
+ Wrench,
20
+ AlertCircle,
21
+ XCircle,
22
+ RotateCcw,
23
+ CheckCircle2,
24
+ FileCheck,
25
+ } from "lucide-react"
26
+
27
+ // ── Types & Constants ────────────────────────────────────────────────────────
28
+
29
+ export interface ElectronicSignatureItem {
30
+ id: string
31
+ organizationId?: string
32
+ recordVersionId?: string
33
+ entityType?: string
34
+ entityId?: string
35
+ signaturePurpose:
36
+ | "perform"
37
+ | "review"
38
+ | "release"
39
+ | "approve"
40
+ | "reject"
41
+ | "amend"
42
+ | "void"
43
+ | "accept"
44
+ | "resolve"
45
+ | "close"
46
+ | string
47
+ signerUserId?: string
48
+ signerNameSnapshot: string
49
+ signerRoleSnapshot: string
50
+ signerScopeSnapshot?: string | null
51
+ attestationTextVersion: string
52
+ authMethod?: string
53
+ signedAt: Date | string
54
+ displayTimezone?: string | null
55
+ signedContentHashSha256: string
56
+ signatureStatus: "active" | "superseded" | "rejected" | "voided" | string
57
+ comments?: string | null
58
+ signerUser?: {
59
+ id?: string
60
+ fullName?: string
61
+ email?: string
62
+ } | null
63
+ // Source context enrichment (from getDeviceSignatures)
64
+ sourceType?: string
65
+ sourceLabel?: string
66
+ sourceNumber?: string
67
+ sourceHref?: string
68
+ }
69
+
70
+ const PURPOSE_CONFIG: Record<
71
+ string,
72
+ { label: string; badgeClass: string; icon: React.ComponentType<{ className?: string }> }
73
+ > = {
74
+ perform: {
75
+ label: "Performer / Work Executed",
76
+ badgeClass:
77
+ "bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
78
+ icon: Wrench,
79
+ },
80
+ review: {
81
+ label: "Technical Reviewer",
82
+ badgeClass:
83
+ "bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800",
84
+ icon: Eye,
85
+ },
86
+ release: {
87
+ label: "Release Authority",
88
+ badgeClass:
89
+ "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
90
+ icon: Sparkles,
91
+ },
92
+ approve: {
93
+ label: "Approval",
94
+ badgeClass:
95
+ "bg-teal-100 text-teal-800 border-teal-300 dark:bg-teal-950 dark:text-teal-300 dark:border-teal-800",
96
+ icon: CheckCircle2,
97
+ },
98
+ reject: {
99
+ label: "Rejection",
100
+ badgeClass:
101
+ "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
102
+ icon: XCircle,
103
+ },
104
+ amend: {
105
+ label: "Record Amendment",
106
+ badgeClass:
107
+ "bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
108
+ icon: RotateCcw,
109
+ },
110
+ void: {
111
+ label: "Voided",
112
+ badgeClass:
113
+ "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
114
+ icon: AlertCircle,
115
+ },
116
+ accept: {
117
+ label: "Engineer Acceptance / Work Started",
118
+ badgeClass:
119
+ "bg-indigo-100 text-indigo-800 border-indigo-300 dark:bg-indigo-950 dark:text-indigo-300 dark:border-indigo-800",
120
+ icon: ShieldCheck,
121
+ },
122
+ resolve: {
123
+ label: "Resolution Sign-Off",
124
+ badgeClass:
125
+ "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
126
+ icon: CheckCircle2,
127
+ },
128
+ close: {
129
+ label: "Ticket Closure Sign-Off",
130
+ badgeClass:
131
+ "bg-slate-100 text-slate-800 border-slate-300 dark:bg-slate-900 dark:text-slate-300 dark:border-slate-800",
132
+ icon: FileCheck,
133
+ },
134
+ }
135
+
136
+ const STATUS_CONFIG: Record<
137
+ string,
138
+ {
139
+ label: string
140
+ borderClass: string
141
+ badgeClass: string
142
+ dotClass: string
143
+ }
144
+ > = {
145
+ active: {
146
+ label: "Active",
147
+ borderClass: "border-l-4 border-l-emerald-500 bg-card",
148
+ badgeClass:
149
+ "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
150
+ dotClass: "bg-emerald-500 text-white ring-emerald-200 dark:ring-emerald-900",
151
+ },
152
+ superseded: {
153
+ label: "Superseded",
154
+ borderClass:
155
+ "border-l-4 border-l-slate-400 bg-slate-50/50 dark:bg-slate-900/30 opacity-90",
156
+ badgeClass:
157
+ "bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
158
+ dotClass: "bg-slate-400 text-white ring-slate-200 dark:ring-slate-800",
159
+ },
160
+ voided: {
161
+ label: "Voided",
162
+ borderClass:
163
+ "border-l-4 border-l-rose-500 bg-rose-50/30 dark:bg-rose-950/20",
164
+ badgeClass:
165
+ "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
166
+ dotClass: "bg-rose-500 text-white ring-rose-200 dark:ring-rose-900",
167
+ },
168
+ rejected: {
169
+ label: "Rejected",
170
+ borderClass:
171
+ "border-l-4 border-l-rose-500 bg-rose-50/30 dark:bg-rose-950/20",
172
+ badgeClass:
173
+ "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
174
+ dotClass: "bg-rose-500 text-white ring-rose-200 dark:ring-rose-900",
175
+ },
176
+ }
177
+
178
+ function formatSignatureTimestamp(
179
+ dateValue: Date | string,
180
+ timezone?: string | null
181
+ ): string {
182
+ try {
183
+ const d = new Date(dateValue)
184
+ const formatted = d.toLocaleString("en-US", {
185
+ year: "numeric",
186
+ month: "short",
187
+ day: "2-digit",
188
+ hour: "2-digit",
189
+ minute: "2-digit",
190
+ second: "2-digit",
191
+ hour12: true,
192
+ timeZoneName: "short",
193
+ })
194
+ return timezone && timezone !== "UTC" ? `${formatted} (${timezone})` : formatted
195
+ } catch {
196
+ return String(dateValue)
197
+ }
198
+ }
199
+
200
+ // ── 1. SignatureCard Component ────────────────────────────────────────────────
201
+
202
+ export interface SignatureCardProps {
203
+ signature: ElectronicSignatureItem | any
204
+ className?: string
205
+ }
206
+
207
+ export function SignatureCard({ signature, className }: SignatureCardProps) {
208
+ const [copied, setCopied] = useState(false)
209
+
210
+ const purpose = signature?.signaturePurpose?.toLowerCase() || "perform"
211
+ const purposeMeta = PURPOSE_CONFIG[purpose] || {
212
+ label: purpose.replace(/_/g, " ").replace(/\b\w/g, (c: string) => c.toUpperCase()),
213
+ badgeClass: "bg-muted text-muted-foreground border-border",
214
+ icon: ShieldCheck,
215
+ }
216
+ const PurposeIcon = purposeMeta.icon
217
+
218
+ const status = signature?.signatureStatus?.toLowerCase() || "active"
219
+ const statusMeta = STATUS_CONFIG[status] || STATUS_CONFIG.active
220
+
221
+ const signerName =
222
+ signature?.signerNameSnapshot ||
223
+ signature?.signerUser?.fullName ||
224
+ "Authorized Signer"
225
+ const signerRole = signature?.signerRoleSnapshot || "Biomedical Specialist"
226
+
227
+ const handleCopyHash = async () => {
228
+ if (!signature?.signedContentHashSha256) return
229
+ try {
230
+ await navigator.clipboard.writeText(signature.signedContentHashSha256)
231
+ setCopied(true)
232
+ setTimeout(() => setCopied(false), 2000)
233
+ } catch {
234
+ // Fallback
235
+ }
236
+ }
237
+
238
+ return (
239
+ <Card
240
+ className={cn(
241
+ "overflow-hidden transition-all shadow-sm border",
242
+ statusMeta.borderClass,
243
+ className
244
+ )}
245
+ >
246
+ <CardHeader className="p-4 pb-3">
247
+ <div className="flex flex-wrap items-start justify-between gap-2">
248
+ {/* Signer Info */}
249
+ <div className="flex items-start gap-2.5 min-w-0">
250
+ <div className="mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
251
+ <User className="h-4 w-4" />
252
+ </div>
253
+ <div className="min-w-0">
254
+ <div className="flex items-center gap-2">
255
+ <span className="font-semibold text-sm text-foreground truncate">
256
+ {signerName}
257
+ </span>
258
+ <span className="text-xs text-muted-foreground truncate">
259
+ • {signerRole}
260
+ </span>
261
+ </div>
262
+ <div className="flex items-center gap-1 text-xs text-muted-foreground mt-0.5">
263
+ <Clock className="h-3 w-3 shrink-0" />
264
+ <span>{formatSignatureTimestamp(signature.signedAt, signature.displayTimezone)}</span>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ {/* Badges: Purpose & Status */}
270
+ <div className="flex flex-wrap items-center gap-1.5 ml-auto">
271
+ <Badge
272
+ variant="outline"
273
+ className={cn("inline-flex items-center gap-1 text-xs font-medium", purposeMeta.badgeClass)}
274
+ >
275
+ <PurposeIcon className="h-3 w-3" />
276
+ <span>{purposeMeta.label}</span>
277
+ </Badge>
278
+
279
+ <Badge
280
+ variant="outline"
281
+ className={cn("capitalize text-xs font-semibold", statusMeta.badgeClass)}
282
+ >
283
+ {statusMeta.label}
284
+ </Badge>
285
+ </div>
286
+ </div>
287
+ </CardHeader>
288
+
289
+ <CardContent className="p-4 pt-0 space-y-3">
290
+ {/* Attestation Text in Quote Block */}
291
+ {signature?.attestationTextVersion && (
292
+ <blockquote className="relative rounded-r-md border-l-2 border-primary/50 bg-muted/40 px-3 py-2 text-xs italic text-foreground/90 leading-relaxed">
293
+ &quot;{signature.attestationTextVersion}&quot;
294
+ </blockquote>
295
+ )}
296
+
297
+ {/* Comments if any */}
298
+ {signature?.comments && (
299
+ <div className="flex items-start gap-2 rounded-md bg-muted/30 p-2.5 text-xs text-foreground/90 border border-border/60">
300
+ <MessageSquare className="h-3.5 w-3.5 mt-0.5 shrink-0 text-muted-foreground" />
301
+ <div className="min-w-0 space-y-0.5">
302
+ <span className="font-semibold text-[11px] uppercase tracking-wider text-muted-foreground block">
303
+ Signer Comments:
304
+ </span>
305
+ <p className="whitespace-pre-wrap leading-normal">{signature.comments}</p>
306
+ </div>
307
+ </div>
308
+ )}
309
+
310
+ {/* SHA-256 Hash Display with Copy Button */}
311
+ {signature?.signedContentHashSha256 && (
312
+ <div className="flex items-center justify-between gap-2 rounded-md border bg-muted/50 px-2.5 py-1.5">
313
+ <div className="flex items-center gap-1.5 min-w-0 font-mono text-[11px] text-muted-foreground">
314
+ <Hash className="h-3.5 w-3.5 shrink-0 text-primary/70" />
315
+ <span className="truncate selection:bg-primary/20" title={signature.signedContentHashSha256}>
316
+ SHA-256: {signature.signedContentHashSha256}
317
+ </span>
318
+ </div>
319
+
320
+ <Button
321
+ type="button"
322
+ variant="ghost"
323
+ size="sm"
324
+ onClick={handleCopyHash}
325
+ className="h-6 gap-1 px-2 text-[11px] text-muted-foreground hover:text-foreground hover:bg-background shrink-0"
326
+ title="Copy cryptographic signature hash"
327
+ >
328
+ {copied ? (
329
+ <>
330
+ <Check className="h-3 w-3 text-emerald-600 dark:text-emerald-400" />
331
+ <span className="text-emerald-600 dark:text-emerald-400 font-medium">Copied</span>
332
+ </>
333
+ ) : (
334
+ <>
335
+ <Copy className="h-3 w-3" />
336
+ <span>Copy</span>
337
+ </>
338
+ )}
339
+ </Button>
340
+ </div>
341
+ )}
342
+ </CardContent>
343
+ </Card>
344
+ )
345
+ }
346
+
347
+ // ── 2. SignatureTimeline Component ────────────────────────────────────────────
348
+
349
+ export interface SignatureTimelineProps {
350
+ signatures?: ElectronicSignatureItem[] | any[]
351
+ className?: string
352
+ emptyMessage?: string
353
+ }
354
+
355
+ export function SignatureTimeline({
356
+ signatures = [],
357
+ className,
358
+ emptyMessage = "No electronic signatures recorded for this record yet.",
359
+ }: SignatureTimelineProps) {
360
+ // Sort chronologically (oldest first)
361
+ const sortedSignatures = [...signatures].sort((a, b) => {
362
+ const timeA = a.signedAt ? new Date(a.signedAt).getTime() : 0
363
+ const timeB = b.signedAt ? new Date(b.signedAt).getTime() : 0
364
+ return timeA - timeB
365
+ })
366
+
367
+ if (sortedSignatures.length === 0) {
368
+ return (
369
+ <div className={cn("rounded-lg border border-dashed p-8 text-center", className)}>
370
+ <FileCheck className="mx-auto h-8 w-8 text-muted-foreground/40 mb-2" />
371
+ <p className="text-sm font-medium text-muted-foreground">{emptyMessage}</p>
372
+ <p className="text-xs text-muted-foreground/70 mt-1">
373
+ Signatures will appear here after review and release execution.
374
+ </p>
375
+ </div>
376
+ )
377
+ }
378
+
379
+ return (
380
+ <div className={cn("relative space-y-6 pl-6 sm:pl-8", className)}>
381
+ {/* Vertical Connecting Dotted Line */}
382
+ <div
383
+ className="absolute left-2.5 sm:left-3.5 top-3 bottom-3 w-0.5 border-l-2 border-dotted border-slate-300 dark:border-slate-700"
384
+ aria-hidden="true"
385
+ />
386
+
387
+ {sortedSignatures.map((signature, index) => {
388
+ const status = signature?.signatureStatus?.toLowerCase() || "active"
389
+ const statusMeta = STATUS_CONFIG[status] || STATUS_CONFIG.active
390
+
391
+ return (
392
+ <div key={signature.id || `sig-${index}`} className="relative">
393
+ {/* Timeline Node Bullet */}
394
+ <div
395
+ className={cn(
396
+ "absolute -left-6 sm:-left-8 top-4 flex h-5 w-5 sm:h-6 sm:w-6 items-center justify-center rounded-full ring-4 shadow-xs",
397
+ statusMeta.dotClass
398
+ )}
399
+ >
400
+ <ShieldCheck className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
401
+ </div>
402
+
403
+ {/* Signature Card */}
404
+ <SignatureCard signature={signature} />
405
+ </div>
406
+ )
407
+ })}
408
+ </div>
409
+ )
410
+ }
411
+
412
+ export default SignatureTimeline
413
+