@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,408 @@
1
+ import {
2
+ getDeviceProfile,
3
+ getDeviceStatusHistory,
4
+ getDeviceLocationHistory,
5
+ getDeviceMaintenanceHistory,
6
+ getDeviceSignatures,
7
+ getDeviceDocuments,
8
+ } from "@/lib/actions/devices"
9
+ import { getActiveQrLabel } from "@/lib/actions/qr"
10
+ import { getDeviceActiveTickets, getDeviceTicketHistory } from "@/lib/actions/tickets"
11
+ import { PageHeader } from "@/components/shared/page-header"
12
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
13
+ import { Button } from "@/components/ui/button"
14
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
15
+ import { DeviceStatusBadge, CriticalityBadge, RiskClassBadge } from "@/components/devices/device-badges"
16
+ import { DeviceAvailabilityBanner } from "@/components/devices/device-availability-banner"
17
+ import { DeviceStatusTimeline, DeviceLocationTimeline } from "@/components/devices/device-timelines"
18
+ import { DeviceActionButtons } from "@/components/devices/device-action-buttons"
19
+ import { DeviceMaintenanceTab } from "@/components/devices/device-maintenance-tab"
20
+ import { DeviceTicketsTab } from "@/components/devices/device-tickets-tab"
21
+ import { DeviceSignaturesTab } from "@/components/devices/device-signatures-tab"
22
+ import { DeviceDocumentsTab } from "@/components/devices/device-documents-tab"
23
+ import { TicketStatusBadge } from "@/components/tickets/ticket-badges"
24
+ import { DeviceMiniQr } from "@/components/devices/device-mini-qr"
25
+ import { AlertTriangle, Wrench, ShieldCheck, FileText, Activity, MapPin, Ticket } from "lucide-react"
26
+ import Link from "next/link"
27
+
28
+ export default async function DeviceDetailPage({ params }: { params: Promise<{ id: string }> }) {
29
+ const { id } = await params
30
+ return <DeviceDetailContent deviceId={id} />
31
+ }
32
+
33
+ async function DeviceDetailContent({ deviceId }: { deviceId: string }) {
34
+ const [
35
+ profileResult,
36
+ statusResult,
37
+ locationResult,
38
+ qrResult,
39
+ ticketsResult,
40
+ ticketHistoryResult,
41
+ maintenanceResult,
42
+ signaturesResult,
43
+ documentsResult,
44
+ ] = await Promise.all([
45
+ getDeviceProfile(deviceId),
46
+ getDeviceStatusHistory(deviceId),
47
+ getDeviceLocationHistory(deviceId),
48
+ getActiveQrLabel(deviceId),
49
+ getDeviceActiveTickets(deviceId),
50
+ getDeviceTicketHistory(deviceId),
51
+ getDeviceMaintenanceHistory(deviceId),
52
+ getDeviceSignatures(deviceId),
53
+ getDeviceDocuments(deviceId),
54
+ ])
55
+
56
+ const profile = profileResult?.success ? profileResult.data : null
57
+ const device = profile?.device
58
+ const statusHistory = (statusResult?.success && Array.isArray(statusResult.data)) ? statusResult.data : []
59
+ const locationHistory = (locationResult?.success && Array.isArray(locationResult.data)) ? locationResult.data : []
60
+ const qrLabel = qrResult?.success ? qrResult.data : null
61
+ const activeTickets = (ticketsResult?.success && Array.isArray(ticketsResult.data)) ? ticketsResult.data : []
62
+ const allTickets = (ticketHistoryResult?.success && Array.isArray(ticketHistoryResult.data)) ? ticketHistoryResult.data : []
63
+ const maintenanceTasks = maintenanceResult?.success ? (maintenanceResult.data?.tasks || []) : []
64
+ const signatures = signaturesResult?.success ? (signaturesResult.data?.signatures || []) : []
65
+ const signatureEvents = signaturesResult?.success ? (signaturesResult.data?.events || []) : []
66
+ const signedActivitiesCount = new Set(
67
+ signatures.map((s: any) => `${s.sourceType || s.entityType}:${s.entityId}`)
68
+ ).size
69
+ const documents = (documentsResult?.success && Array.isArray(documentsResult.data)) ? documentsResult.data : []
70
+
71
+ if (!device) {
72
+ return (
73
+ <div className="p-12 text-center">
74
+ <h2 className="text-xl font-semibold mb-2">Device Not Found</h2>
75
+ <p className="text-muted-foreground text-sm mb-4">
76
+ The requested device record could not be found or you do not have permission to view it.
77
+ </p>
78
+ <Link href="/devices">
79
+ <Button variant="outline">Back to Device Inventory</Button>
80
+ </Link>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ const visibleSections = profile?.visibleSections || ["overview"]
86
+ const allowedActions = profile?.allowedActions || ["report_problem", "view_qr"]
87
+
88
+ const showTechnical = visibleSections.includes("technical")
89
+ const showMaintenance = visibleSections.includes("maintenance")
90
+ const showSignatures = visibleSections.includes("signatures")
91
+ const showStatusHistory = visibleSections.includes("status-history")
92
+ const showLocationHistory = visibleSections.includes("location-history")
93
+ const showDocuments = visibleSections.includes("documents")
94
+
95
+ return (
96
+ <div className="space-y-6">
97
+ {/* Role-shaped Header with Action Buttons */}
98
+ <PageHeader
99
+ title={device.name}
100
+ description={`${device.internalCode} — Asset #${device.assetNumber}`}
101
+ >
102
+ <DeviceActionButtons
103
+ deviceId={device.id}
104
+ deviceName={device.name}
105
+ currentHospital={(device as any).hospital?.name || "Hospital"}
106
+ currentDepartment={(device as any).department?.name || "Department"}
107
+ currentLocation={(device as any).location?.name || device.exactLocationDescription || undefined}
108
+ currentStatus={device.currentStatusCode}
109
+ allowedActions={allowedActions}
110
+ />
111
+ </PageHeader>
112
+
113
+ {/* Safety & Availability Banner */}
114
+ <DeviceAvailabilityBanner
115
+ status={device.currentStatusCode}
116
+ limitationsNote={device.statusLimitationsNote}
117
+ />
118
+
119
+ {/* Tabbed Navigation */}
120
+ <Tabs defaultValue="overview" className="space-y-6">
121
+ <TabsList className="flex flex-wrap h-auto p-1 gap-1">
122
+ <TabsTrigger value="overview">Overview</TabsTrigger>
123
+ {showTechnical && <TabsTrigger value="technical">Technical</TabsTrigger>}
124
+ <TabsTrigger value="tickets" className="gap-1.5">
125
+ <Ticket className="w-3.5 h-3.5" />
126
+ Tickets ({allTickets.length})
127
+ </TabsTrigger>
128
+ {showMaintenance && (
129
+ <TabsTrigger value="maintenance" className="gap-1.5">
130
+ <Wrench className="w-3.5 h-3.5" />
131
+ Maintenance ({maintenanceTasks.length})
132
+ </TabsTrigger>
133
+ )}
134
+ {showSignatures && (
135
+ <TabsTrigger value="signatures" className="gap-1.5">
136
+ <ShieldCheck className="w-3.5 h-3.5" />
137
+ Signatures ({signedActivitiesCount})
138
+ </TabsTrigger>
139
+ )}
140
+ {showStatusHistory && <TabsTrigger value="status-history">Status History</TabsTrigger>}
141
+ {showLocationHistory && <TabsTrigger value="location-history">Location History</TabsTrigger>}
142
+ {showDocuments && (
143
+ <TabsTrigger value="documents" className="gap-1.5">
144
+ <FileText className="w-3.5 h-3.5" />
145
+ Documents ({documents.length})
146
+ </TabsTrigger>
147
+ )}
148
+ </TabsList>
149
+
150
+ {/* 1. Overview Tab */}
151
+ <TabsContent value="overview" className="space-y-6">
152
+ <div className="grid gap-6 md:grid-cols-2">
153
+ {/* Identity Card */}
154
+ <Card>
155
+ <CardHeader className="pb-3">
156
+ <CardTitle className="text-base">Device Identity</CardTitle>
157
+ </CardHeader>
158
+ <CardContent className="space-y-4 text-sm">
159
+ {qrLabel && (
160
+ <DeviceMiniQr
161
+ deviceId={device.id}
162
+ opaqueReference={qrLabel.opaqueReference}
163
+ assetNumber={device.assetNumber}
164
+ deviceName={device.name}
165
+ />
166
+ )}
167
+
168
+ <div className="grid gap-3 pt-1">
169
+ <div className="flex justify-between">
170
+ <span className="text-muted-foreground">Internal Code</span>
171
+ <span className="font-mono font-medium">{device.internalCode}</span>
172
+ </div>
173
+ <div className="flex justify-between">
174
+ <span className="text-muted-foreground">Asset Number</span>
175
+ <span className="font-mono font-medium">{device.assetNumber}</span>
176
+ </div>
177
+ <div className="flex justify-between">
178
+ <span className="text-muted-foreground">Serial Number</span>
179
+ <span>{device.serialNumber || "—"}</span>
180
+ </div>
181
+ <div className="flex justify-between">
182
+ <span className="text-muted-foreground">Category</span>
183
+ <span>{(device as any).deviceCategory?.name || "—"}</span>
184
+ </div>
185
+ <div className="flex justify-between">
186
+ <span className="text-muted-foreground">Manufacturer</span>
187
+ <span>{(device as any).manufacturer?.name || "—"}</span>
188
+ </div>
189
+ <div className="flex justify-between">
190
+ <span className="text-muted-foreground">Model</span>
191
+ <span>{(device as any).deviceModel?.modelName || device.modelNameFree || "—"}</span>
192
+ </div>
193
+ <div className="flex justify-between items-center">
194
+ <span className="text-muted-foreground">Status</span>
195
+ <DeviceStatusBadge status={device.currentStatusCode} />
196
+ </div>
197
+ <div className="flex justify-between items-center">
198
+ <span className="text-muted-foreground">Criticality</span>
199
+ <CriticalityBadge level={device.criticalityLevel} />
200
+ </div>
201
+ <div className="flex justify-between items-center">
202
+ <span className="text-muted-foreground">Risk Class</span>
203
+ <RiskClassBadge classification={device.riskClassification} />
204
+ </div>
205
+ </div>
206
+ </CardContent>
207
+ </Card>
208
+
209
+ {/* Location & Assignment Card */}
210
+ <Card>
211
+ <CardHeader><CardTitle className="text-base">Location & Assignment</CardTitle></CardHeader>
212
+ <CardContent className="grid gap-3 text-sm">
213
+ <div className="flex justify-between">
214
+ <span className="text-muted-foreground">Organization</span>
215
+ <span>{(device as any).organization?.name || "—"}</span>
216
+ </div>
217
+ <div className="flex justify-between">
218
+ <span className="text-muted-foreground">Hospital</span>
219
+ <span>{(device as any).hospital?.name || "—"}</span>
220
+ </div>
221
+ <div className="flex justify-between">
222
+ <span className="text-muted-foreground">Department</span>
223
+ <span>{(device as any).department?.name || "—"}</span>
224
+ </div>
225
+ <div className="flex justify-between">
226
+ <span className="text-muted-foreground">Location / Room</span>
227
+ <span>{(device as any).location?.name || device.exactLocationDescription || "—"}</span>
228
+ </div>
229
+ <div className="flex justify-between">
230
+ <span className="text-muted-foreground">Assigned Engineer</span>
231
+ <span>{(device as any).assignedEngineer?.fullName || "Unassigned"}</span>
232
+ </div>
233
+ {qrLabel && (
234
+ <div className="flex justify-between items-center">
235
+ <span className="text-muted-foreground">QR Reference</span>
236
+ <Link
237
+ href={`/devices/${device.id}/qr`}
238
+ className="font-mono text-xs text-primary hover:underline"
239
+ >
240
+ {qrLabel.opaqueReference}
241
+ </Link>
242
+ </div>
243
+ )}
244
+ {device.nextPmDueDate && (
245
+ <div className="flex justify-between">
246
+ <span className="text-muted-foreground">Next PM Due</span>
247
+ <span className="font-medium">{new Date(device.nextPmDueDate).toLocaleDateString()}</span>
248
+ </div>
249
+ )}
250
+ </CardContent>
251
+ </Card>
252
+
253
+ {/* Purchase & Warranty Card */}
254
+ <Card className="md:col-span-2">
255
+ <CardHeader><CardTitle className="text-base">Purchase & Warranty</CardTitle></CardHeader>
256
+ <CardContent className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 text-sm">
257
+ <div>
258
+ <span className="text-muted-foreground block text-xs">Purchase Date</span>
259
+ <span>{device.purchaseDate ? new Date(device.purchaseDate).toLocaleDateString() : "—"}</span>
260
+ </div>
261
+ <div>
262
+ <span className="text-muted-foreground block text-xs">Purchase Cost</span>
263
+ <span>{device.purchaseCost ? `${device.currency || "USD"} ${device.purchaseCost}` : "—"}</span>
264
+ </div>
265
+ <div>
266
+ <span className="text-muted-foreground block text-xs">Installation Date</span>
267
+ <span>{device.installationDate ? new Date(device.installationDate).toLocaleDateString() : "—"}</span>
268
+ </div>
269
+ <div>
270
+ <span className="text-muted-foreground block text-xs">Warranty Start</span>
271
+ <span>{device.warrantyStartDate ? new Date(device.warrantyStartDate).toLocaleDateString() : "—"}</span>
272
+ </div>
273
+ <div>
274
+ <span className="text-muted-foreground block text-xs">Warranty End</span>
275
+ <span>{device.warrantyEndDate ? new Date(device.warrantyEndDate).toLocaleDateString() : "—"}</span>
276
+ </div>
277
+ <div>
278
+ <span className="text-muted-foreground block text-xs">Lifecycle Status</span>
279
+ <span className="capitalize font-medium">{device.lifecycleStatus}</span>
280
+ </div>
281
+ </CardContent>
282
+ </Card>
283
+
284
+ {/* Active Tickets Display */}
285
+ {activeTickets.length > 0 && (
286
+ <Card className="md:col-span-2 border-amber-200 dark:border-amber-900/50">
287
+ <CardHeader className="bg-amber-50/60 dark:bg-amber-950/20 pb-4 border-b border-amber-100 dark:border-amber-900/40">
288
+ <CardTitle className="text-amber-900 dark:text-amber-300 text-base flex items-center gap-2">
289
+ <AlertTriangle className="w-4 h-4 text-amber-600" />
290
+ Active Service Tickets ({activeTickets.length})
291
+ </CardTitle>
292
+ </CardHeader>
293
+ <CardContent className="pt-4 grid gap-3">
294
+ {activeTickets.map((t: any) => (
295
+ <div
296
+ key={t.id}
297
+ className="flex flex-col md:flex-row md:items-center justify-between p-3.5 rounded-lg border bg-card gap-4"
298
+ >
299
+ <div className="space-y-1">
300
+ <Link
301
+ href={`/tickets/${t.id}`}
302
+ className="font-medium hover:underline text-primary flex items-center gap-2"
303
+ >
304
+ <span className="font-mono text-xs px-1.5 py-0.5 bg-muted rounded">
305
+ {t.ticketNumber}
306
+ </span>
307
+ {t.title}
308
+ </Link>
309
+ <p className="text-xs text-muted-foreground line-clamp-1">{t.description}</p>
310
+ </div>
311
+ <div className="flex items-center gap-3 shrink-0">
312
+ <TicketStatusBadge status={t.statusCode} />
313
+ <Button asChild variant="outline" size="sm">
314
+ <Link href={`/tickets/${t.id}`}>View Ticket</Link>
315
+ </Button>
316
+ </div>
317
+ </div>
318
+ ))}
319
+ </CardContent>
320
+ </Card>
321
+ )}
322
+ </div>
323
+ </TabsContent>
324
+
325
+ {/* 2. Technical Specifications Tab */}
326
+ {showTechnical && (
327
+ <TabsContent value="technical">
328
+ <Card>
329
+ <CardHeader><CardTitle className="text-base">Technical Specifications</CardTitle></CardHeader>
330
+ <CardContent>
331
+ {device.technicalSpecifications ? (
332
+ <pre className="bg-muted p-4 rounded-md text-xs font-mono overflow-auto max-h-96">
333
+ {JSON.stringify(device.technicalSpecifications, null, 2)}
334
+ </pre>
335
+ ) : (
336
+ <p className="text-muted-foreground text-sm">No technical specifications recorded.</p>
337
+ )}
338
+ </CardContent>
339
+ </Card>
340
+ </TabsContent>
341
+ )}
342
+
343
+ {/* 3. Service Tickets History Tab */}
344
+ <TabsContent value="tickets">
345
+ <DeviceTicketsTab
346
+ tickets={allTickets}
347
+ deviceId={device.id}
348
+ deviceName={device.name}
349
+ />
350
+ </TabsContent>
351
+
352
+ {/* 4. Maintenance Tab */}
353
+ {showMaintenance && (
354
+ <TabsContent value="maintenance">
355
+ <DeviceMaintenanceTab
356
+ maintenanceTasks={maintenanceTasks}
357
+ deviceId={device.id}
358
+ />
359
+ </TabsContent>
360
+ )}
361
+
362
+ {/* 4. Signatures & Approvals Tab */}
363
+ {showSignatures && (
364
+ <TabsContent value="signatures">
365
+ <DeviceSignaturesTab
366
+ signatures={signatures}
367
+ signatureEvents={signatureEvents}
368
+ />
369
+ </TabsContent>
370
+ )}
371
+
372
+ {/* 5. Status History Tab */}
373
+ {showStatusHistory && (
374
+ <TabsContent value="status-history">
375
+ <Card>
376
+ <CardHeader><CardTitle className="text-base">Status Change History</CardTitle></CardHeader>
377
+ <CardContent>
378
+ <DeviceStatusTimeline entries={statusHistory as any} />
379
+ </CardContent>
380
+ </Card>
381
+ </TabsContent>
382
+ )}
383
+
384
+ {/* 6. Location Transfer History Tab */}
385
+ {showLocationHistory && (
386
+ <TabsContent value="location-history">
387
+ <Card>
388
+ <CardHeader><CardTitle className="text-base">Location Transfer History</CardTitle></CardHeader>
389
+ <CardContent>
390
+ <DeviceLocationTimeline entries={locationHistory as any} />
391
+ </CardContent>
392
+ </Card>
393
+ </TabsContent>
394
+ )}
395
+
396
+ {/* 7. Documents Tab */}
397
+ {showDocuments && (
398
+ <TabsContent value="documents">
399
+ <DeviceDocumentsTab
400
+ documents={documents}
401
+ deviceId={device.id}
402
+ />
403
+ </TabsContent>
404
+ )}
405
+ </Tabs>
406
+ </div>
407
+ )
408
+ }
@@ -0,0 +1,54 @@
1
+ import { getDeviceById } from "@/lib/actions/devices"
2
+ import { getActiveQrLabel } from "@/lib/actions/qr"
3
+ import { PageHeader } from "@/components/shared/page-header"
4
+ import { Button } from "@/components/ui/button"
5
+ import { ArrowLeft } from "lucide-react"
6
+ import Link from "next/link"
7
+ import { use } from "react"
8
+ import { DeviceQrCard } from "@/components/devices/device-qr-card"
9
+
10
+ export default function DeviceQrPage({ params }: { params: Promise<{ id: string }> }) {
11
+ const { id } = use(params)
12
+ return <QrContent deviceId={id} />
13
+ }
14
+
15
+ async function QrContent({ deviceId }: { deviceId: string }) {
16
+ const [deviceResult, qrResult] = await Promise.all([
17
+ getDeviceById(deviceId),
18
+ getActiveQrLabel(deviceId),
19
+ ])
20
+
21
+ const device = deviceResult?.success ? deviceResult.data : null
22
+ const qrLabel = qrResult?.success ? qrResult.data : null
23
+
24
+ if (!device) {
25
+ return <div className="p-8 text-center text-muted-foreground">Device not found</div>
26
+ }
27
+
28
+ return (
29
+ <div className="space-y-6">
30
+ <PageHeader
31
+ title={`QR Code: ${device.name}`}
32
+ description={`${device.internalCode} — Asset #${device.assetNumber}`}
33
+ >
34
+ <Link href={`/devices/${deviceId}`} className="print:hidden">
35
+ <Button variant="outline" size="sm">
36
+ <ArrowLeft className="w-4 h-4 mr-2" /> Back to Device
37
+ </Button>
38
+ </Link>
39
+ </PageHeader>
40
+
41
+ <DeviceQrCard
42
+ device={{
43
+ id: device.id,
44
+ name: device.name,
45
+ internalCode: device.internalCode,
46
+ assetNumber: device.assetNumber,
47
+ hospital: (device as any).hospital,
48
+ department: (device as any).department,
49
+ }}
50
+ qrLabel={qrLabel || null}
51
+ />
52
+ </div>
53
+ )
54
+ }