@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,997 @@
1
+ import { getMaintenanceTaskById } from "@/lib/actions/maintenance-tasks"
2
+ import { getTaskPartsAndCosts } from "@/lib/actions/checklists"
3
+ import { getRecordSignatures } from "@/lib/actions/signatures"
4
+ import { db } from "@/lib/db"
5
+ import { maintenanceRecords } from "@/lib/db/schema"
6
+ import { eq } from "drizzle-orm"
7
+ import { PageHeader } from "@/components/shared/page-header"
8
+ import {
9
+ Card,
10
+ CardContent,
11
+ CardDescription,
12
+ CardHeader,
13
+ CardTitle,
14
+ } from "@/components/ui/card"
15
+ import { Button } from "@/components/ui/button"
16
+ import { Badge } from "@/components/ui/badge"
17
+ import { Separator } from "@/components/ui/separator"
18
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
19
+ import {
20
+ Table,
21
+ TableBody,
22
+ TableCell,
23
+ TableHead,
24
+ TableHeader,
25
+ TableRow,
26
+ } from "@/components/ui/table"
27
+ import {
28
+ MaintenanceStatusBadge,
29
+ MaintenanceTypeBadge,
30
+ PriorityBadge,
31
+ ChecklistResultBadge,
32
+ FinalResultBadge,
33
+ } from "@/components/maintenance/maintenance-badges"
34
+ import { DeviceStatusBadge, CriticalityBadge } from "@/components/devices/device-badges"
35
+ import {
36
+ TicketStatusBadge,
37
+ TicketPriorityBadge,
38
+ TicketImpactBadge,
39
+ } from "@/components/tickets/ticket-badges"
40
+ import { PartsCostsManager } from "@/components/maintenance/parts-costs-manager"
41
+ import { SignatureTimeline } from "@/components/maintenance/signature-card"
42
+ import { TaskActionBar } from "@/components/maintenance/task-action-bar"
43
+ import {
44
+ ArrowLeft,
45
+ Calendar,
46
+ Clock,
47
+ User as UserIcon,
48
+ MapPin,
49
+ Monitor,
50
+ CheckSquare,
51
+ DollarSign,
52
+ ShieldCheck,
53
+ Info,
54
+ AlertTriangle,
55
+ FileText,
56
+ Activity,
57
+ Tag,
58
+ Building,
59
+ CheckCircle2,
60
+ XCircle,
61
+ Hash,
62
+ ExternalLink,
63
+ LifeBuoy,
64
+ } from "lucide-react"
65
+ import Link from "next/link"
66
+
67
+ export default async function MaintenanceTaskDetailPage({
68
+ params,
69
+ }: {
70
+ params: Promise<{ id: string }>
71
+ }) {
72
+ const { id } = await params
73
+
74
+ // 1. Fetch Task and Parts/Costs concurrently
75
+ const [taskRes, partsRes] = await Promise.all([
76
+ getMaintenanceTaskById(id),
77
+ getTaskPartsAndCosts(id),
78
+ ])
79
+
80
+ if (!taskRes?.success || !taskRes.data) {
81
+ return (
82
+ <div className="flex flex-col items-center justify-center min-h-[400px] space-y-4 p-8 text-center">
83
+ <div className="rounded-full bg-muted p-4">
84
+ <AlertTriangle className="h-8 w-8 text-muted-foreground" />
85
+ </div>
86
+ <h2 className="text-xl font-bold">Maintenance Task Not Found</h2>
87
+ <p className="text-sm text-muted-foreground max-w-md">
88
+ {taskRes?.error || "The requested maintenance task could not be loaded or does not exist."}
89
+ </p>
90
+ <Button asChild variant="outline">
91
+ <Link href="/maintenance/tasks">
92
+ <ArrowLeft className="mr-2 h-4 w-4" />
93
+ Back to Maintenance Tasks
94
+ </Link>
95
+ </Button>
96
+ </div>
97
+ )
98
+ }
99
+
100
+ const task = taskRes.data
101
+ const partsData = partsRes?.success && partsRes.data ? partsRes.data : { parts: [], costs: [], totalPartsCost: 0, totalOtherCosts: 0, grandTotal: 0 }
102
+
103
+ // 2. Fetch Signatures and Maintenance Record details if resultRecordId exists
104
+ let signatures: any[] = []
105
+ let recordVersions: any[] = []
106
+ let maintenanceRecord: any = null
107
+
108
+ if (task.resultRecordId) {
109
+ const [sigRes, recordData] = await Promise.all([
110
+ getRecordSignatures(task.resultRecordId),
111
+ db.query.maintenanceRecords.findFirst({
112
+ where: eq(maintenanceRecords.id, task.resultRecordId),
113
+ }),
114
+ ])
115
+
116
+ if (sigRes?.success && sigRes.data) {
117
+ signatures = sigRes.data.signatures || []
118
+ recordVersions = sigRes.data.versions || []
119
+ }
120
+ maintenanceRecord = recordData || null
121
+ }
122
+
123
+ // Active work states where parts and costs are editable
124
+ const isEditable = [
125
+ "assigned",
126
+ "in_progress",
127
+ "waiting_dependency",
128
+ "returned_for_rework",
129
+ "work_complete",
130
+ ].includes(task.statusCode)
131
+
132
+ // Checklist items & results processing
133
+ const templateItems: any[] =
134
+ (task.checklistTemplateVersion?.itemsJsonb as any[]) || []
135
+ const checklistResultsMap = new Map<string, any>()
136
+ if (Array.isArray(task.checklistResults)) {
137
+ task.checklistResults.forEach((r: any) => {
138
+ checklistResultsMap.set(r.templateItemId, r)
139
+ })
140
+ }
141
+
142
+ // Combine template items with executed results or fallback to checklist results
143
+ const checklistRows =
144
+ templateItems.length > 0
145
+ ? templateItems.map((item: any, idx: number) => {
146
+ const result = checklistResultsMap.get(item.id)
147
+ return {
148
+ order: item.itemOrder ?? idx + 1,
149
+ id: item.id,
150
+ label: item.label,
151
+ description: item.description,
152
+ isRequired: item.isRequired,
153
+ inputType: item.inputType,
154
+ toleranceMin: item.toleranceMin,
155
+ toleranceMax: item.toleranceMax,
156
+ unit: item.unit,
157
+ resultCode: result?.resultCode || null,
158
+ measuredValue: result?.measuredValue || null,
159
+ resultUnit: result?.unit || item.unit || null,
160
+ notes: result?.notes || null,
161
+ completedAt: result?.completedAt || null,
162
+ }
163
+ })
164
+ : (task.checklistResults || []).map((r: any, idx: number) => ({
165
+ order: r.itemOrder ?? idx + 1,
166
+ id: r.templateItemId || `item-${idx}`,
167
+ label: r.itemLabelSnapshot || `Checklist Item #${idx + 1}`,
168
+ description: null,
169
+ isRequired: true,
170
+ inputType: r.measuredValue ? "measurement" : "pass_fail",
171
+ toleranceMin: null,
172
+ toleranceMax: null,
173
+ unit: r.unit,
174
+ resultCode: r.resultCode,
175
+ measuredValue: r.measuredValue,
176
+ resultUnit: r.unit,
177
+ notes: r.notes,
178
+ completedAt: r.completedAt,
179
+ }))
180
+
181
+ const checklistTotal = checklistRows.length
182
+ const checklistPassed = checklistRows.filter(
183
+ (r: any) => r.resultCode === "passed"
184
+ ).length
185
+ const checklistFailed = checklistRows.filter(
186
+ (r: any) => r.resultCode === "failed" || r.resultCode === "requires_follow_up"
187
+ ).length
188
+
189
+ // Counts for tabs
190
+ const partsAndCostsCount =
191
+ (partsData.parts?.length || 0) + (partsData.costs?.length || 0)
192
+
193
+ // Format helper for display strings
194
+ const formatCategory = (cat?: string | null) => {
195
+ if (!cat) return "Not specified"
196
+ return cat
197
+ .replace(/_/g, " ")
198
+ .replace(/\b\w/g, (c) => c.toUpperCase())
199
+ }
200
+
201
+ return (
202
+ <div className="space-y-6">
203
+ {/* ── Top Back Link ──────────────────────────────────────────────────── */}
204
+ <div>
205
+ <Link
206
+ href="/maintenance/tasks"
207
+ className="inline-flex items-center text-xs sm:text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
208
+ >
209
+ <ArrowLeft className="mr-1.5 h-4 w-4" />
210
+ Back to Maintenance Tasks
211
+ </Link>
212
+ </div>
213
+
214
+ {/* ── 1. Header Section ──────────────────────────────────────────────── */}
215
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between border-b pb-6">
216
+ <div className="space-y-2">
217
+ <div className="flex flex-wrap items-center gap-2">
218
+ <span className="font-mono text-xs sm:text-sm font-semibold text-muted-foreground">
219
+ {task.taskNumber}
220
+ </span>
221
+ <MaintenanceStatusBadge status={task.statusCode} showIcon />
222
+ <PriorityBadge priority={task.priorityCode} showIcon />
223
+ <MaintenanceTypeBadge type={task.maintenanceType} showIcon />
224
+ </div>
225
+
226
+ <h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground">
227
+ {task.title}
228
+ </h1>
229
+
230
+ <div className="flex flex-wrap items-center gap-y-1 gap-x-4 text-xs text-muted-foreground">
231
+ <span className="flex items-center gap-1">
232
+ <Building className="h-3.5 w-3.5" />
233
+ {task.hospital?.name || "Hospital"}
234
+ {task.department?.name ? ` • ${task.department.name}` : ""}
235
+ </span>
236
+
237
+ {task.device && (
238
+ <span className="flex items-center gap-1">
239
+ <Monitor className="h-3.5 w-3.5" />
240
+ <Link
241
+ href={`/devices/${task.deviceId}`}
242
+ className="font-medium text-primary hover:underline"
243
+ >
244
+ {task.device.name}
245
+ </Link>
246
+ {task.device.assetNumber && (
247
+ <span className="text-muted-foreground/80">
248
+ ({task.device.assetNumber})
249
+ </span>
250
+ )}
251
+ </span>
252
+ )}
253
+
254
+ {task.dueDate && (
255
+ <span className="flex items-center gap-1">
256
+ <Calendar className="h-3.5 w-3.5" />
257
+ Due {new Date(task.dueDate).toLocaleDateString()}
258
+ </span>
259
+ )}
260
+
261
+ <span className="flex items-center gap-1">
262
+ <Clock className="h-3.5 w-3.5" />
263
+ Created {new Date(task.createdAt).toLocaleDateString()}
264
+ </span>
265
+ </div>
266
+ </div>
267
+
268
+ {/* ── 2. Action Buttons Bar (Contextual) ───────────────────────────── */}
269
+ <div className="shrink-0 pt-1">
270
+ <TaskActionBar
271
+ taskId={task.id}
272
+ taskNumber={task.taskNumber}
273
+ statusCode={task.statusCode}
274
+ deviceId={task.deviceId}
275
+ resultRecordId={task.resultRecordId}
276
+ />
277
+ </div>
278
+ </div>
279
+
280
+ {/* ── Quick Summary Cards Row ────────────────────────────────────────── */}
281
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
282
+ {/* Device Snapshot Item */}
283
+ <div className="rounded-lg border bg-card p-3 shadow-xs">
284
+ <span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
285
+ <Monitor className="h-3.5 w-3.5 text-primary" />
286
+ Target Device
287
+ </span>
288
+ <div className="mt-1 font-semibold text-sm truncate">
289
+ {task.device ? (
290
+ <Link
291
+ href={`/devices/${task.deviceId}`}
292
+ className="text-foreground hover:text-primary hover:underline truncate block"
293
+ >
294
+ {task.device.name}
295
+ </Link>
296
+ ) : (
297
+ "—"
298
+ )}
299
+ </div>
300
+ <div className="text-xs text-muted-foreground truncate">
301
+ {task.device?.assetNumber ? `Asset: ${task.device.assetNumber}` : "No asset tag"}
302
+ </div>
303
+ </div>
304
+
305
+ {/* Location & Dept */}
306
+ <div className="rounded-lg border bg-card p-3 shadow-xs">
307
+ <span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
308
+ <MapPin className="h-3.5 w-3.5 text-primary" />
309
+ Location
310
+ </span>
311
+ <div className="mt-1 font-semibold text-sm truncate">
312
+ {task.department?.name || task.hospital?.name || "—"}
313
+ </div>
314
+ <div className="text-xs text-muted-foreground truncate">
315
+ {task.hospital?.name || "Hospital Facility"}
316
+ </div>
317
+ </div>
318
+
319
+ {/* Assignee */}
320
+ <div className="rounded-lg border bg-card p-3 shadow-xs">
321
+ <span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
322
+ <UserIcon className="h-3.5 w-3.5 text-primary" />
323
+ Assigned Engineer
324
+ </span>
325
+ <div className="mt-1 font-semibold text-sm truncate">
326
+ {task.assignedEngineer?.fullName || "Unassigned"}
327
+ </div>
328
+ <div className="text-xs text-muted-foreground truncate">
329
+ {task.assignedEngineer?.email || "Biomedical Team"}
330
+ </div>
331
+ </div>
332
+
333
+ {/* Schedule & Due */}
334
+ <div className="rounded-lg border bg-card p-3 shadow-xs">
335
+ <span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
336
+ <Calendar className="h-3.5 w-3.5 text-primary" />
337
+ Due Date
338
+ </span>
339
+ <div className="mt-1 font-semibold text-sm font-mono truncate">
340
+ {task.dueDate ? new Date(task.dueDate).toLocaleDateString() : "Flexible / None"}
341
+ </div>
342
+ <div className="text-xs text-muted-foreground truncate">
343
+ {task.completedAt
344
+ ? `Completed ${new Date(task.completedAt).toLocaleDateString()}`
345
+ : task.startedAt
346
+ ? `Started ${new Date(task.startedAt).toLocaleDateString()}`
347
+ : "Not started"}
348
+ </div>
349
+ </div>
350
+ </div>
351
+
352
+ {/* ── 3. Tabs Section ────────────────────────────────────────────────── */}
353
+ <Tabs defaultValue="overview" className="w-full space-y-6">
354
+ <TabsList className="grid w-full grid-cols-4 sm:w-auto sm:inline-flex">
355
+ <TabsTrigger value="overview" className="gap-1.5 text-xs sm:text-sm">
356
+ <Info className="h-4 w-4" />
357
+ <span>Overview</span>
358
+ </TabsTrigger>
359
+
360
+ <TabsTrigger value="checklist" className="gap-1.5 text-xs sm:text-sm">
361
+ <CheckSquare className="h-4 w-4" />
362
+ <span>Checklist</span>
363
+ {checklistTotal > 0 && (
364
+ <Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
365
+ {checklistTotal}
366
+ </Badge>
367
+ )}
368
+ </TabsTrigger>
369
+
370
+ <TabsTrigger value="parts-costs" className="gap-1.5 text-xs sm:text-sm">
371
+ <DollarSign className="h-4 w-4" />
372
+ <span>Parts & Costs</span>
373
+ {partsAndCostsCount > 0 && (
374
+ <Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
375
+ {partsAndCostsCount}
376
+ </Badge>
377
+ )}
378
+ </TabsTrigger>
379
+
380
+ <TabsTrigger value="signatures" className="gap-1.5 text-xs sm:text-sm">
381
+ <ShieldCheck className="h-4 w-4" />
382
+ <span>Signatures</span>
383
+ {signatures.length > 0 && (
384
+ <Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
385
+ {signatures.length}
386
+ </Badge>
387
+ )}
388
+ </TabsTrigger>
389
+ </TabsList>
390
+
391
+ {/* ── TAB 1: OVERVIEW ──────────────────────────────────────────────── */}
392
+ <TabsContent value="overview" className="space-y-6">
393
+ <div className="grid gap-6 md:grid-cols-3">
394
+ {/* Left 2 Cols: Main Content */}
395
+ <div className="md:col-span-2 space-y-6">
396
+ {/* Waiting Dependency Alert Callout */}
397
+ {task.statusCode === "waiting_dependency" && (
398
+ <div className="rounded-lg border border-amber-300 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950/30">
399
+ <div className="flex items-start gap-3">
400
+ <Clock className="h-5 w-5 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
401
+ <div className="space-y-1 text-sm">
402
+ <h4 className="font-semibold text-amber-900 dark:text-amber-200">
403
+ Task Paused — Waiting on Dependency
404
+ </h4>
405
+ <p className="text-amber-800 dark:text-amber-300 text-xs sm:text-sm">
406
+ <span className="font-semibold capitalize">
407
+ {task.waitingDependencyType?.replace(/_/g, " ") || "External dependency"}:
408
+ </span>{" "}
409
+ {task.waitingReason || "No specific reason recorded."}
410
+ </p>
411
+ </div>
412
+ </div>
413
+ </div>
414
+ )}
415
+
416
+ {/* Task Description & Technical Category */}
417
+ <Card>
418
+ <CardHeader className="pb-3">
419
+ <CardTitle className="text-base font-semibold">Problem & Task Description</CardTitle>
420
+ </CardHeader>
421
+ <CardContent className="space-y-4">
422
+ <p className="whitespace-pre-wrap text-sm text-foreground leading-relaxed">
423
+ {task.description || "No specific task description provided."}
424
+ </p>
425
+
426
+ <div className="flex flex-wrap gap-4 pt-4 border-t">
427
+ <div className="space-y-1">
428
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
429
+ Technical Category
430
+ </span>
431
+ <div className="text-sm font-medium">
432
+ {formatCategory(task.technicalProblemCategory)}
433
+ </div>
434
+ </div>
435
+
436
+ <div className="space-y-1">
437
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
438
+ Maintenance Type
439
+ </span>
440
+ <div>
441
+ <MaintenanceTypeBadge type={task.maintenanceType} showIcon />
442
+ </div>
443
+ </div>
444
+
445
+ <div className="space-y-1">
446
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
447
+ Task Priority
448
+ </span>
449
+ <div>
450
+ <PriorityBadge priority={task.priorityCode} showIcon />
451
+ </div>
452
+ </div>
453
+ </div>
454
+ </CardContent>
455
+ </Card>
456
+
457
+ {/* Maintenance Execution & Findings Card (if completed or record available) */}
458
+ {maintenanceRecord && (
459
+ <Card className="border-cyan-200/70 dark:border-cyan-900/50">
460
+ <CardHeader className="bg-cyan-50/40 dark:bg-cyan-950/20 pb-3">
461
+ <div className="flex items-center justify-between">
462
+ <div className="flex items-center gap-2">
463
+ <Activity className="h-4 w-4 text-cyan-700 dark:text-cyan-400" />
464
+ <CardTitle className="text-base font-semibold">
465
+ Executed Maintenance & Resolution
466
+ </CardTitle>
467
+ </div>
468
+ <span className="font-mono text-xs text-muted-foreground">
469
+ {maintenanceRecord.recordNumber}
470
+ </span>
471
+ </div>
472
+ <CardDescription className="text-xs">
473
+ Technical findings, diagnostic conclusions, and resolution summary
474
+ </CardDescription>
475
+ </CardHeader>
476
+
477
+ <CardContent className="space-y-4 pt-4">
478
+ {/* Work Performed */}
479
+ <div className="space-y-1">
480
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
481
+ Work Executed
482
+ </span>
483
+ <p className="text-sm whitespace-pre-wrap text-foreground bg-muted/30 p-3 rounded-md border">
484
+ {maintenanceRecord.workPerformed || "Standard maintenance procedure completed."}
485
+ </p>
486
+ </div>
487
+
488
+ {/* Diagnosis & Root Cause Grid */}
489
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
490
+ {maintenanceRecord.diagnosis && (
491
+ <div className="space-y-1">
492
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
493
+ Diagnosis
494
+ </span>
495
+ <p className="text-sm text-foreground">{maintenanceRecord.diagnosis}</p>
496
+ </div>
497
+ )}
498
+
499
+ {maintenanceRecord.rootCause && (
500
+ <div className="space-y-1">
501
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
502
+ Root Cause
503
+ </span>
504
+ <p className="text-sm text-foreground">{maintenanceRecord.rootCause}</p>
505
+ </div>
506
+ )}
507
+ </div>
508
+
509
+ {/* Findings & Recommendations */}
510
+ {(maintenanceRecord.findings || maintenanceRecord.recommendations) && (
511
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2 border-t">
512
+ {maintenanceRecord.findings && (
513
+ <div className="space-y-1">
514
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
515
+ Key Findings
516
+ </span>
517
+ <p className="text-sm text-muted-foreground">{maintenanceRecord.findings}</p>
518
+ </div>
519
+ )}
520
+
521
+ {maintenanceRecord.recommendations && (
522
+ <div className="space-y-1">
523
+ <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
524
+ Recommendations
525
+ </span>
526
+ <p className="text-sm text-muted-foreground">{maintenanceRecord.recommendations}</p>
527
+ </div>
528
+ )}
529
+ </div>
530
+ )}
531
+
532
+ {/* Final Result & Device Status Badges */}
533
+ <div className="flex flex-wrap items-center gap-4 pt-3 border-t">
534
+ <div className="space-y-1">
535
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
536
+ Final Test Result
537
+ </span>
538
+ <FinalResultBadge result={maintenanceRecord.finalResultCode} />
539
+ </div>
540
+
541
+ <div className="space-y-1">
542
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
543
+ Final Device Status
544
+ </span>
545
+ <DeviceStatusBadge status={maintenanceRecord.finalDeviceStatusCode} />
546
+ </div>
547
+
548
+ {maintenanceRecord.calibrationCertificateNumber && (
549
+ <div className="space-y-1">
550
+ <span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
551
+ Calibration Certificate
552
+ </span>
553
+ <span className="font-mono text-xs font-medium text-foreground">
554
+ {maintenanceRecord.calibrationCertificateNumber}
555
+ </span>
556
+ </div>
557
+ )}
558
+ </div>
559
+ </CardContent>
560
+ </Card>
561
+ )}
562
+
563
+ {/* Linked Service Ticket Card */}
564
+ {task.serviceTicket ? (
565
+ <Card>
566
+ <CardHeader className="pb-3">
567
+ <div className="flex items-center justify-between">
568
+ <div className="flex items-center gap-2">
569
+ <LifeBuoy className="h-4 w-4 text-primary" />
570
+ <CardTitle className="text-base font-semibold">Originating Service Ticket</CardTitle>
571
+ </div>
572
+ <Button variant="ghost" size="sm" asChild className="h-7 text-xs">
573
+ <Link href={`/tickets/${task.serviceTicket.id}`} className="gap-1">
574
+ <span>View Ticket</span>
575
+ <ExternalLink className="h-3 w-3" />
576
+ </Link>
577
+ </Button>
578
+ </div>
579
+ </CardHeader>
580
+ <CardContent className="space-y-3">
581
+ <div className="flex flex-wrap items-center gap-2">
582
+ <span className="font-mono text-xs font-semibold text-muted-foreground">
583
+ {task.serviceTicket.ticketNumber}
584
+ </span>
585
+ <TicketPriorityBadge priority={task.serviceTicket.priorityCode} />
586
+ <TicketStatusBadge status={task.serviceTicket.statusCode} />
587
+ </div>
588
+
589
+ <h4 className="font-semibold text-sm text-foreground">
590
+ {task.serviceTicket.title}
591
+ </h4>
592
+
593
+ <p className="text-xs sm:text-sm text-muted-foreground line-clamp-2">
594
+ {task.serviceTicket.description}
595
+ </p>
596
+
597
+ <div className="flex flex-wrap gap-4 pt-2 border-t text-xs text-muted-foreground">
598
+ <div>
599
+ <span className="font-medium text-foreground">Category:</span>{" "}
600
+ {task.serviceTicket.reportedProblemCategory || "General"}
601
+ </div>
602
+ <div>
603
+ <span className="font-medium text-foreground">Impact:</span>{" "}
604
+ <TicketImpactBadge impact={task.serviceTicket.reportedImpact} />
605
+ </div>
606
+ </div>
607
+ </CardContent>
608
+ </Card>
609
+ ) : null}
610
+ </div>
611
+
612
+ {/* Right 1 Col: Metadata & Context */}
613
+ <div className="space-y-6">
614
+ {/* Target Device Snapshot Card */}
615
+ <Card>
616
+ <CardHeader className="pb-3">
617
+ <div className="flex items-center justify-between">
618
+ <CardTitle className="text-sm font-semibold flex items-center gap-2">
619
+ <Monitor className="h-4 w-4 text-primary" />
620
+ Device Details
621
+ </CardTitle>
622
+ {task.device && (
623
+ <Button variant="ghost" size="sm" asChild className="h-7 text-xs">
624
+ <Link href={`/devices/${task.deviceId}`} className="gap-1">
625
+ <span>Open</span>
626
+ <ExternalLink className="h-3 w-3" />
627
+ </Link>
628
+ </Button>
629
+ )}
630
+ </div>
631
+ </CardHeader>
632
+ <CardContent className="space-y-3 text-xs">
633
+ {task.device ? (
634
+ <div className="space-y-2.5">
635
+ <div>
636
+ <span className="text-muted-foreground block text-[11px]">Device Name</span>
637
+ <Link
638
+ href={`/devices/${task.deviceId}`}
639
+ className="font-semibold text-sm text-primary hover:underline block"
640
+ >
641
+ {task.device.name}
642
+ </Link>
643
+ </div>
644
+
645
+ <div className="grid grid-cols-2 gap-2 pt-1 border-t">
646
+ <div>
647
+ <span className="text-muted-foreground block text-[11px]">Asset Tag</span>
648
+ <span className="font-mono font-medium text-foreground">
649
+ {task.device.assetNumber || "—"}
650
+ </span>
651
+ </div>
652
+ <div>
653
+ <span className="text-muted-foreground block text-[11px]">Serial #</span>
654
+ <span className="font-mono font-medium text-foreground">
655
+ {task.device.serialNumber || "—"}
656
+ </span>
657
+ </div>
658
+ </div>
659
+
660
+ <div className="grid grid-cols-2 gap-2 pt-1 border-t">
661
+ <div>
662
+ <span className="text-muted-foreground block text-[11px]">Model</span>
663
+ <span className="font-medium text-foreground">
664
+ {task.device.modelNameFree || "—"}
665
+ </span>
666
+ </div>
667
+ <div>
668
+ <span className="text-muted-foreground block text-[11px]">Criticality</span>
669
+ <CriticalityBadge level={task.device.criticalityLevel} />
670
+ </div>
671
+ </div>
672
+
673
+ <div className="pt-1 border-t">
674
+ <span className="text-muted-foreground block text-[11px] mb-1">
675
+ Current Device Status
676
+ </span>
677
+ <DeviceStatusBadge status={task.device.currentStatusCode} />
678
+ </div>
679
+ </div>
680
+ ) : (
681
+ <p className="text-muted-foreground italic">No medical device linked.</p>
682
+ )}
683
+ </CardContent>
684
+ </Card>
685
+
686
+ {/* Assignment & People Card */}
687
+ <Card>
688
+ <CardHeader className="pb-3">
689
+ <CardTitle className="text-sm font-semibold flex items-center gap-2">
690
+ <UserIcon className="h-4 w-4 text-primary" />
691
+ Assignment & Responsibility
692
+ </CardTitle>
693
+ </CardHeader>
694
+ <CardContent className="space-y-3 text-xs">
695
+ <div>
696
+ <span className="text-muted-foreground block text-[11px]">Assigned Engineer</span>
697
+ <span className="font-medium text-sm text-foreground block mt-0.5">
698
+ {task.assignedEngineer?.fullName || "Unassigned"}
699
+ </span>
700
+ {task.assignedEngineer?.email && (
701
+ <span className="text-muted-foreground text-[11px]">
702
+ {task.assignedEngineer.email}
703
+ </span>
704
+ )}
705
+ </div>
706
+
707
+ <div className="pt-2 border-t">
708
+ <span className="text-muted-foreground block text-[11px]">Created By</span>
709
+ <span className="font-medium text-foreground">
710
+ {task.createdByUser?.fullName || "System / Automated"}
711
+ </span>
712
+ </div>
713
+ </CardContent>
714
+ </Card>
715
+
716
+ {/* Execution Milestones & Timeline Card */}
717
+ <Card>
718
+ <CardHeader className="pb-3">
719
+ <CardTitle className="text-sm font-semibold flex items-center gap-2">
720
+ <Clock className="h-4 w-4 text-primary" />
721
+ Milestones & Dates
722
+ </CardTitle>
723
+ </CardHeader>
724
+ <CardContent className="space-y-2.5 text-xs">
725
+ <div className="flex justify-between items-center">
726
+ <span className="text-muted-foreground">Created:</span>
727
+ <span className="font-mono text-foreground">
728
+ {new Date(task.createdAt).toLocaleString("en-US", {
729
+ month: "short",
730
+ day: "numeric",
731
+ year: "numeric",
732
+ hour: "2-digit",
733
+ minute: "2-digit",
734
+ })}
735
+ </span>
736
+ </div>
737
+
738
+ {task.startedAt && (
739
+ <div className="flex justify-between items-center pt-1 border-t">
740
+ <span className="text-muted-foreground">Work Started:</span>
741
+ <span className="font-mono text-foreground">
742
+ {new Date(task.startedAt).toLocaleString("en-US", {
743
+ month: "short",
744
+ day: "numeric",
745
+ year: "numeric",
746
+ hour: "2-digit",
747
+ minute: "2-digit",
748
+ })}
749
+ </span>
750
+ </div>
751
+ )}
752
+
753
+ {task.completedAt && (
754
+ <div className="flex justify-between items-center pt-1 border-t">
755
+ <span className="text-muted-foreground">Work Completed:</span>
756
+ <span className="font-mono text-foreground">
757
+ {new Date(task.completedAt).toLocaleString("en-US", {
758
+ month: "short",
759
+ day: "numeric",
760
+ year: "numeric",
761
+ hour: "2-digit",
762
+ minute: "2-digit",
763
+ })}
764
+ </span>
765
+ </div>
766
+ )}
767
+
768
+ {task.dueDate && (
769
+ <div className="flex justify-between items-center pt-1 border-t">
770
+ <span className="text-muted-foreground font-medium">Due Date:</span>
771
+ <span className="font-mono font-semibold text-foreground">
772
+ {new Date(task.dueDate).toLocaleDateString()}
773
+ </span>
774
+ </div>
775
+ )}
776
+
777
+ <div className="flex justify-between items-center pt-1 border-t">
778
+ <span className="text-muted-foreground">Last Updated:</span>
779
+ <span className="font-mono text-muted-foreground">
780
+ {new Date(task.updatedAt).toLocaleString("en-US", {
781
+ month: "short",
782
+ day: "numeric",
783
+ hour: "2-digit",
784
+ minute: "2-digit",
785
+ })}
786
+ </span>
787
+ </div>
788
+ </CardContent>
789
+ </Card>
790
+ </div>
791
+ </div>
792
+ </TabsContent>
793
+
794
+ {/* ── TAB 2: CHECKLIST ────────────────────────────────────────────── */}
795
+ <TabsContent value="checklist" className="space-y-6">
796
+ <Card>
797
+ <CardHeader className="pb-3">
798
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
799
+ <div className="space-y-1">
800
+ <div className="flex items-center gap-2">
801
+ <CheckSquare className="h-5 w-5 text-primary" />
802
+ <CardTitle className="text-base font-semibold">
803
+ Maintenance Inspection & Procedure Checklist
804
+ </CardTitle>
805
+ </div>
806
+ <CardDescription className="text-xs">
807
+ {task.checklistTemplateVersion
808
+ ? `Template Version v${task.checklistTemplateVersion.versionNumber} • Read-only review of measured values and criteria verification`
809
+ : "Standard maintenance checklist execution results"}
810
+ </CardDescription>
811
+ </div>
812
+
813
+ {checklistTotal > 0 && (
814
+ <div className="flex items-center gap-2">
815
+ <Badge variant="outline" className="text-xs bg-emerald-50 text-emerald-700 border-emerald-300">
816
+ {checklistPassed} Passed
817
+ </Badge>
818
+ {checklistFailed > 0 && (
819
+ <Badge variant="outline" className="text-xs bg-red-50 text-red-700 border-red-300">
820
+ {checklistFailed} Issues
821
+ </Badge>
822
+ )}
823
+ </div>
824
+ )}
825
+ </div>
826
+ </CardHeader>
827
+
828
+ <CardContent className="p-0">
829
+ {checklistRows.length === 0 ? (
830
+ <div className="p-12 text-center space-y-3">
831
+ <CheckSquare className="mx-auto h-10 w-10 text-muted-foreground/40" />
832
+ <div className="space-y-1">
833
+ <p className="text-sm font-semibold text-foreground">No Checklist Attached</p>
834
+ <p className="text-xs text-muted-foreground max-w-sm mx-auto">
835
+ This maintenance task was created without a standardized checklist template. Work details are recorded in the task resolution.
836
+ </p>
837
+ </div>
838
+ </div>
839
+ ) : (
840
+ <div className="overflow-x-auto">
841
+ <Table>
842
+ <TableHeader>
843
+ <TableRow className="bg-muted/50">
844
+ <TableHead className="w-[60px] text-center font-semibold">#</TableHead>
845
+ <TableHead className="min-w-[200px] font-semibold">Checklist Item / Requirement</TableHead>
846
+ <TableHead className="w-[140px] font-semibold">Type & Range</TableHead>
847
+ <TableHead className="w-[130px] font-semibold">Result</TableHead>
848
+ <TableHead className="w-[130px] font-semibold">Measured Value</TableHead>
849
+ <TableHead className="min-w-[180px] font-semibold">Findings / Notes</TableHead>
850
+ </TableRow>
851
+ </TableHeader>
852
+ <TableBody>
853
+ {checklistRows.map((row: any) => {
854
+ const hasTolerance =
855
+ row.toleranceMin !== null &&
856
+ row.toleranceMin !== undefined &&
857
+ row.toleranceMax !== null &&
858
+ row.toleranceMax !== undefined
859
+
860
+ return (
861
+ <TableRow key={row.id} className="hover:bg-muted/30">
862
+ <TableCell className="text-center font-mono text-xs text-muted-foreground font-semibold">
863
+ {row.order}
864
+ </TableCell>
865
+
866
+ <TableCell>
867
+ <div className="font-medium text-xs sm:text-sm text-foreground flex items-center gap-1.5">
868
+ <span>{row.label}</span>
869
+ {row.isRequired && (
870
+ <Badge variant="secondary" className="text-[10px] px-1 py-0 h-3.5 bg-muted">
871
+ Required
872
+ </Badge>
873
+ )}
874
+ </div>
875
+ {row.description && (
876
+ <p className="text-xs text-muted-foreground mt-0.5">{row.description}</p>
877
+ )}
878
+ </TableCell>
879
+
880
+ <TableCell className="text-xs text-muted-foreground font-mono">
881
+ <span className="capitalize">{row.inputType.replace(/_/g, " ")}</span>
882
+ {hasTolerance && (
883
+ <span className="block text-[11px] text-muted-foreground/80">
884
+ [{row.toleranceMin} - {row.toleranceMax} {row.unit || ""}]
885
+ </span>
886
+ )}
887
+ </TableCell>
888
+
889
+ <TableCell>
890
+ {row.resultCode ? (
891
+ <ChecklistResultBadge result={row.resultCode} showIcon />
892
+ ) : (
893
+ <Badge variant="outline" className="text-xs text-muted-foreground">
894
+ Pending
895
+ </Badge>
896
+ )}
897
+ </TableCell>
898
+
899
+ <TableCell className="font-mono text-xs font-semibold text-foreground">
900
+ {row.measuredValue ? (
901
+ <span>
902
+ {row.measuredValue} {row.resultUnit || row.unit || ""}
903
+ </span>
904
+ ) : (
905
+ <span className="text-muted-foreground font-normal">—</span>
906
+ )}
907
+ </TableCell>
908
+
909
+ <TableCell className="text-xs text-muted-foreground">
910
+ {row.notes ? (
911
+ <span className="whitespace-pre-wrap">{row.notes}</span>
912
+ ) : (
913
+ <span className="text-muted-foreground/60">—</span>
914
+ )}
915
+ </TableCell>
916
+ </TableRow>
917
+ )
918
+ })}
919
+ </TableBody>
920
+ </Table>
921
+ </div>
922
+ )}
923
+ </CardContent>
924
+ </Card>
925
+ </TabsContent>
926
+
927
+ {/* ── TAB 3: PARTS & COSTS ────────────────────────────────────────── */}
928
+ <TabsContent value="parts-costs" className="space-y-6">
929
+ <PartsCostsManager
930
+ taskId={task.id}
931
+ parts={partsData.parts}
932
+ costs={partsData.costs}
933
+ editable={isEditable}
934
+ />
935
+ </TabsContent>
936
+
937
+ {/* ── TAB 4: SIGNATURES ───────────────────────────────────────────── */}
938
+ <TabsContent value="signatures" className="space-y-6">
939
+ <Card>
940
+ <CardHeader className="pb-3">
941
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
942
+ <div className="space-y-1">
943
+ <div className="flex items-center gap-2">
944
+ <ShieldCheck className="h-5 w-5 text-primary" />
945
+ <CardTitle className="text-base font-semibold">
946
+ Electronic Signatures & Audit Trail
947
+ </CardTitle>
948
+ </div>
949
+ <CardDescription className="text-xs">
950
+ Cryptographic signature attestation history for work execution, peer review, and release authorization
951
+ </CardDescription>
952
+ </div>
953
+
954
+ {signatures.length > 0 && (
955
+ <Badge variant="outline" className="text-xs bg-emerald-50 text-emerald-800 border-emerald-300">
956
+ <CheckCircle2 className="w-3 h-3 mr-1" />
957
+ {signatures.length} Verified {signatures.length === 1 ? "Signature" : "Signatures"}
958
+ </Badge>
959
+ )}
960
+ </div>
961
+ </CardHeader>
962
+
963
+ <CardContent className="space-y-6 pt-2">
964
+ {/* Record Version Hash Information */}
965
+ {recordVersions.length > 0 && (
966
+ <div className="rounded-lg border bg-muted/30 p-3 space-y-2">
967
+ <div className="flex items-center justify-between">
968
+ <span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
969
+ <Hash className="h-3.5 w-3.5 text-primary" />
970
+ Record Version v{recordVersions[0].versionNumber} Canonical Digest
971
+ </span>
972
+ <span className="text-[11px] text-muted-foreground">
973
+ Created {new Date(recordVersions[0].createdAt).toLocaleString()}
974
+ </span>
975
+ </div>
976
+ <div className="font-mono text-[11px] text-muted-foreground truncate bg-background p-2 rounded border">
977
+ SHA-256: {recordVersions[0].contentHashSha256}
978
+ </div>
979
+ </div>
980
+ )}
981
+
982
+ {/* Signature Timeline Component */}
983
+ <SignatureTimeline
984
+ signatures={signatures}
985
+ emptyMessage={
986
+ task.statusCode === "work_complete"
987
+ ? "Work is complete. Click 'Sign & Submit' in the header to execute performer signature."
988
+ : "No electronic signatures recorded yet. Signatures appear upon completion, review, and release."
989
+ }
990
+ />
991
+ </CardContent>
992
+ </Card>
993
+ </TabsContent>
994
+ </Tabs>
995
+ </div>
996
+ )
997
+ }