@omersx/bemms 0.0.0-stage → 0.1.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,955 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { useState, useTransition } from "react"
5
+ import { useRouter } from "next/navigation"
6
+ import Link from "next/link"
7
+ import { Button } from "@/components/ui/button"
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogDescription,
12
+ DialogFooter,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ } from "@/components/ui/dialog"
16
+ import { Label } from "@/components/ui/label"
17
+ import { Input } from "@/components/ui/input"
18
+ import { Textarea } from "@/components/ui/textarea"
19
+ import {
20
+ Select,
21
+ SelectContent,
22
+ SelectItem,
23
+ SelectTrigger,
24
+ SelectValue,
25
+ } from "@/components/ui/select"
26
+ import {
27
+ startMaintenanceTask,
28
+ markTaskWaiting,
29
+ completeTaskWork,
30
+ cancelMaintenanceTask,
31
+ } from "@/lib/actions/maintenance-tasks"
32
+ import {
33
+ reviewMaintenanceRecord,
34
+ releaseDeviceMaintenance,
35
+ signMaintenanceRecord,
36
+ } from "@/lib/actions/signatures"
37
+ import {
38
+ Play,
39
+ Pause,
40
+ CheckCheck,
41
+ FileSignature,
42
+ Eye,
43
+ Sparkles,
44
+ RotateCcw,
45
+ Ban,
46
+ Loader2,
47
+ AlertCircle,
48
+ ShieldCheck,
49
+ Wrench,
50
+ } from "lucide-react"
51
+
52
+ interface TaskActionBarProps {
53
+ taskId: string
54
+ taskNumber: string
55
+ statusCode: string
56
+ deviceId: string
57
+ resultRecordId?: string | null
58
+ currentUserId?: string
59
+ }
60
+
61
+ export function TaskActionBar({
62
+ taskId,
63
+ taskNumber,
64
+ statusCode,
65
+ deviceId,
66
+ resultRecordId,
67
+ currentUserId,
68
+ }: TaskActionBarProps) {
69
+ const router = useRouter()
70
+ const [isPending, startTransition] = useTransition()
71
+ const [error, setError] = useState<string | null>(null)
72
+
73
+ // Dialog States
74
+ const [waitingDialogOpen, setWaitingDialogOpen] = useState(false)
75
+ const [completeDialogOpen, setCompleteDialogOpen] = useState(false)
76
+ const [reviewDialogOpen, setReviewDialogOpen] = useState(false)
77
+ const [releaseDialogOpen, setReleaseDialogOpen] = useState(false)
78
+ const [cancelDialogOpen, setCancelDialogOpen] = useState(false)
79
+ const [signDialogOpen, setSignDialogOpen] = useState(false)
80
+
81
+ // Form States - Mark Waiting
82
+ const [waitingDependencyType, setWaitingDependencyType] = useState<
83
+ "parts" | "requester" | "vendor" | "department_access"
84
+ >("parts")
85
+ const [waitingReason, setWaitingReason] = useState("")
86
+
87
+ // Form States - Complete Work
88
+ const [diagnosis, setDiagnosis] = useState("")
89
+ const [rootCause, setRootCause] = useState("")
90
+ const [workPerformed, setWorkPerformed] = useState("")
91
+ const [findings, setFindings] = useState("")
92
+ const [recommendations, setRecommendations] = useState("")
93
+ const [finalResultCode, setFinalResultCode] = useState<string>("passed")
94
+ const [finalDeviceStatusCode, setFinalDeviceStatusCode] = useState<string>("awaiting_release")
95
+
96
+ // Form States - Review Work
97
+ const [reviewDecision, setReviewDecision] = useState<"approve" | "reject">("approve")
98
+ const [reviewNotes, setReviewNotes] = useState("")
99
+ const [rejectionReason, setRejectionReason] = useState("")
100
+ const [reviewPassword, setReviewPassword] = useState("")
101
+
102
+ // Form States - Release Device
103
+ const [releaseStatus, setReleaseStatus] = useState<"operational" | "operational_with_limitations">(
104
+ "operational"
105
+ )
106
+ const [releaseNotes, setReleaseNotes] = useState("")
107
+ const [releasePassword, setReleasePassword] = useState("")
108
+
109
+ // Form States - Sign & Submit
110
+ const [signPassword, setSignPassword] = useState("")
111
+ const [signComments, setSignComments] = useState("")
112
+
113
+ // Form States - Cancel Task
114
+ const [cancellationReason, setCancellationReason] = useState("")
115
+
116
+ // Handlers
117
+ const handleStartWork = () => {
118
+ setError(null)
119
+ startTransition(async () => {
120
+ const res = await startMaintenanceTask(taskId)
121
+ if (res?.success) {
122
+ router.refresh()
123
+ } else {
124
+ setError(res?.error || "Failed to start maintenance work")
125
+ }
126
+ })
127
+ }
128
+
129
+ const handleMarkWaiting = (e: React.FormEvent) => {
130
+ e.preventDefault()
131
+ if (!waitingReason.trim() || waitingReason.trim().length < 5) {
132
+ setError("Waiting reason must be at least 5 characters")
133
+ return
134
+ }
135
+
136
+ setError(null)
137
+ startTransition(async () => {
138
+ const res = await markTaskWaiting(taskId, {
139
+ waitingDependencyType,
140
+ waitingReason: waitingReason.trim(),
141
+ })
142
+ if (res?.success) {
143
+ setWaitingDialogOpen(false)
144
+ setWaitingReason("")
145
+ router.refresh()
146
+ } else {
147
+ setError(res?.error || "Failed to update task to waiting state")
148
+ }
149
+ })
150
+ }
151
+
152
+ const handleCompleteWork = (e: React.FormEvent) => {
153
+ e.preventDefault()
154
+ if (!workPerformed.trim() || workPerformed.trim().length < 10) {
155
+ setError("Work performed description must be at least 10 characters")
156
+ return
157
+ }
158
+
159
+ setError(null)
160
+ startTransition(async () => {
161
+ const res = await completeTaskWork(taskId, {
162
+ diagnosis: diagnosis.trim() || undefined,
163
+ rootCause: rootCause.trim() || undefined,
164
+ workPerformed: workPerformed.trim(),
165
+ findings: findings.trim() || undefined,
166
+ recommendations: recommendations.trim() || undefined,
167
+ finalResultCode: finalResultCode as any,
168
+ finalDeviceStatusCode: finalDeviceStatusCode as any,
169
+ })
170
+ if (res?.success) {
171
+ setCompleteDialogOpen(false)
172
+ router.refresh()
173
+ } else {
174
+ setError(res?.error || "Failed to record completed work")
175
+ }
176
+ })
177
+ }
178
+
179
+ const handleSignAndSubmit = (e: React.FormEvent) => {
180
+ e.preventDefault()
181
+ if (!signPassword) {
182
+ setError("Password is required for electronic signature authentication")
183
+ return
184
+ }
185
+
186
+ setError(null)
187
+ startTransition(async () => {
188
+ const res = await signMaintenanceRecord(
189
+ taskId,
190
+ {
191
+ diagnosis: diagnosis.trim() || undefined,
192
+ rootCause: rootCause.trim() || undefined,
193
+ workPerformed: workPerformed.trim() || "Maintenance work completed per checklist and standard procedure.",
194
+ findings: findings.trim() || undefined,
195
+ recommendations: recommendations.trim() || undefined,
196
+ finalResultCode: finalResultCode || "passed",
197
+ finalDeviceStatusCode: finalDeviceStatusCode || "awaiting_release",
198
+ },
199
+ {
200
+ signaturePurpose: "perform",
201
+ authMethod: "password_reauth",
202
+ password: signPassword,
203
+ comments: signComments.trim() || undefined,
204
+ }
205
+ )
206
+
207
+ if (res?.success) {
208
+ setSignDialogOpen(false)
209
+ setSignPassword("")
210
+ setSignComments("")
211
+ router.refresh()
212
+ } else {
213
+ setError(res?.error || "Failed to electronically sign and submit record")
214
+ }
215
+ })
216
+ }
217
+
218
+ const handleReviewWork = (e: React.FormEvent) => {
219
+ e.preventDefault()
220
+ if (!reviewPassword) {
221
+ setError("Password is required for electronic review signature")
222
+ return
223
+ }
224
+ if (reviewDecision === "reject" && (!rejectionReason.trim() || rejectionReason.trim().length < 5)) {
225
+ setError("Please provide a reason for rejecting the maintenance work (min 5 characters)")
226
+ return
227
+ }
228
+
229
+ setError(null)
230
+ startTransition(async () => {
231
+ const res = await reviewMaintenanceRecord(taskId, {
232
+ decision: reviewDecision,
233
+ reviewNotes: reviewNotes.trim() || undefined,
234
+ rejectionReason: rejectionReason.trim() || undefined,
235
+ password: reviewPassword,
236
+ })
237
+ if (res?.success) {
238
+ setReviewDialogOpen(false)
239
+ setReviewPassword("")
240
+ setReviewNotes("")
241
+ setRejectionReason("")
242
+ router.refresh()
243
+ } else {
244
+ setError(res?.error || "Failed to submit technical review")
245
+ }
246
+ })
247
+ }
248
+
249
+ const handleReleaseDevice = (e: React.FormEvent) => {
250
+ e.preventDefault()
251
+ if (!releasePassword) {
252
+ setError("Password is required for release authorization signature")
253
+ return
254
+ }
255
+
256
+ setError(null)
257
+ startTransition(async () => {
258
+ const res = await releaseDeviceMaintenance(taskId, {
259
+ finalDeviceStatusCode: releaseStatus,
260
+ releaseNotes: releaseNotes.trim() || undefined,
261
+ password: releasePassword,
262
+ })
263
+ if (res?.success) {
264
+ setReleaseDialogOpen(false)
265
+ setReleasePassword("")
266
+ setReleaseNotes("")
267
+ router.refresh()
268
+ } else {
269
+ setError(res?.error || "Failed to authorize device release")
270
+ }
271
+ })
272
+ }
273
+
274
+ const handleCancelTask = (e: React.FormEvent) => {
275
+ e.preventDefault()
276
+ if (!cancellationReason.trim() || cancellationReason.trim().length < 10) {
277
+ setError("Cancellation reason must be at least 10 characters")
278
+ return
279
+ }
280
+
281
+ setError(null)
282
+ startTransition(async () => {
283
+ const res = await cancelMaintenanceTask(taskId, {
284
+ cancellationReason: cancellationReason.trim(),
285
+ })
286
+ if (res?.success) {
287
+ setCancelDialogOpen(false)
288
+ setCancellationReason("")
289
+ router.refresh()
290
+ } else {
291
+ setError(res?.error || "Failed to cancel task")
292
+ }
293
+ })
294
+ }
295
+
296
+ return (
297
+ <div className="flex flex-col gap-2">
298
+ {error && (
299
+ <div className="flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-xs text-destructive">
300
+ <AlertCircle className="h-4 w-4 shrink-0" />
301
+ <span>{error}</span>
302
+ <Button
303
+ type="button"
304
+ variant="ghost"
305
+ size="sm"
306
+ onClick={() => setError(null)}
307
+ className="ml-auto h-6 text-xs text-destructive hover:bg-transparent"
308
+ >
309
+ Dismiss
310
+ </Button>
311
+ </div>
312
+ )}
313
+
314
+ <div className="flex flex-wrap items-center gap-2">
315
+ {/* State: ASSIGNED -> Start Work */}
316
+ {statusCode === "assigned" && (
317
+ <Button
318
+ type="button"
319
+ size="sm"
320
+ onClick={handleStartWork}
321
+ disabled={isPending}
322
+ className="bg-indigo-600 hover:bg-indigo-700 text-white gap-1.5 shadow-sm"
323
+ >
324
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />}
325
+ Start Work
326
+ </Button>
327
+ )}
328
+
329
+ {/* State: IN_PROGRESS -> Mark Waiting & Complete Work */}
330
+ {statusCode === "in_progress" && (
331
+ <>
332
+ <Button
333
+ type="button"
334
+ variant="outline"
335
+ size="sm"
336
+ onClick={() => {
337
+ setError(null)
338
+ setWaitingDialogOpen(true)
339
+ }}
340
+ disabled={isPending}
341
+ className="gap-1.5 border-amber-300 text-amber-800 dark:text-amber-300 hover:bg-amber-50 dark:hover:bg-amber-950/40"
342
+ >
343
+ <Pause className="h-4 w-4" />
344
+ Mark Waiting
345
+ </Button>
346
+
347
+ <Button
348
+ type="button"
349
+ size="sm"
350
+ onClick={() => {
351
+ setError(null)
352
+ setCompleteDialogOpen(true)
353
+ }}
354
+ disabled={isPending}
355
+ className="bg-emerald-600 hover:bg-emerald-700 text-white gap-1.5 shadow-sm"
356
+ >
357
+ <CheckCheck className="h-4 w-4" />
358
+ Complete Work
359
+ </Button>
360
+ </>
361
+ )}
362
+
363
+ {/* State: WAITING_DEPENDENCY -> Resume Work */}
364
+ {statusCode === "waiting_dependency" && (
365
+ <Button
366
+ type="button"
367
+ size="sm"
368
+ onClick={handleStartWork}
369
+ disabled={isPending}
370
+ className="bg-indigo-600 hover:bg-indigo-700 text-white gap-1.5 shadow-sm"
371
+ >
372
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <RotateCcw className="h-4 w-4" />}
373
+ Resume Work
374
+ </Button>
375
+ )}
376
+
377
+ {/* State: RETURNED_FOR_REWORK -> Resume Work */}
378
+ {statusCode === "returned_for_rework" && (
379
+ <>
380
+ <Button
381
+ type="button"
382
+ size="sm"
383
+ onClick={handleStartWork}
384
+ disabled={isPending}
385
+ className="bg-indigo-600 hover:bg-indigo-700 text-white gap-1.5 shadow-sm"
386
+ >
387
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Wrench className="h-4 w-4" />}
388
+ Resume Work
389
+ </Button>
390
+
391
+ <Button
392
+ type="button"
393
+ size="sm"
394
+ onClick={() => {
395
+ setError(null)
396
+ setCompleteDialogOpen(true)
397
+ }}
398
+ disabled={isPending}
399
+ className="bg-emerald-600 hover:bg-emerald-700 text-white gap-1.5 shadow-sm"
400
+ >
401
+ <CheckCheck className="h-4 w-4" />
402
+ Re-Complete Work
403
+ </Button>
404
+ </>
405
+ )}
406
+
407
+ {/* State: WORK_COMPLETE -> Sign & Submit Link and Action */}
408
+ {statusCode === "work_complete" && (
409
+ <div className="flex items-center gap-2">
410
+ <Button
411
+ type="button"
412
+ size="sm"
413
+ onClick={() => {
414
+ setError(null)
415
+ setSignDialogOpen(true)
416
+ }}
417
+ disabled={isPending}
418
+ className="bg-cyan-700 hover:bg-cyan-800 text-white gap-1.5 shadow-sm"
419
+ >
420
+ <FileSignature className="h-4 w-4" />
421
+ Sign & Submit
422
+ </Button>
423
+ <Button variant="outline" size="sm" asChild>
424
+ <Link href={`/maintenance/tasks/${taskId}/sign`} className="gap-1.5">
425
+ <ShieldCheck className="h-4 w-4 text-primary" />
426
+ Sign Page
427
+ </Link>
428
+ </Button>
429
+ </div>
430
+ )}
431
+
432
+ {/* State: AWAITING_REVIEW -> Review Work */}
433
+ {statusCode === "awaiting_review" && (
434
+ <Button
435
+ type="button"
436
+ size="sm"
437
+ onClick={() => {
438
+ setError(null)
439
+ setReviewDialogOpen(true)
440
+ }}
441
+ disabled={isPending}
442
+ className="bg-purple-600 hover:bg-purple-700 text-white gap-1.5 shadow-sm"
443
+ >
444
+ <Eye className="h-4 w-4" />
445
+ Review Work
446
+ </Button>
447
+ )}
448
+
449
+ {/* State: AWAITING_RELEASE -> Release Device */}
450
+ {statusCode === "awaiting_release" && (
451
+ <Button
452
+ type="button"
453
+ size="sm"
454
+ onClick={() => {
455
+ setError(null)
456
+ setReleaseDialogOpen(true)
457
+ }}
458
+ disabled={isPending}
459
+ className="bg-emerald-600 hover:bg-emerald-700 text-white gap-1.5 shadow-sm"
460
+ >
461
+ <Sparkles className="h-4 w-4" />
462
+ Release Device
463
+ </Button>
464
+ )}
465
+
466
+ {/* Optional Cancel Action for Active Stages */}
467
+ {["draft", "assigned", "in_progress", "waiting_dependency"].includes(statusCode) && (
468
+ <Button
469
+ type="button"
470
+ variant="ghost"
471
+ size="sm"
472
+ onClick={() => {
473
+ setError(null)
474
+ setCancelDialogOpen(true)
475
+ }}
476
+ disabled={isPending}
477
+ className="text-muted-foreground hover:text-destructive hover:bg-destructive/10 text-xs"
478
+ >
479
+ <Ban className="h-3.5 w-3.5 mr-1" />
480
+ Cancel Task
481
+ </Button>
482
+ )}
483
+ </div>
484
+
485
+ {/* ── 1. Dialog: Mark Waiting ───────────────────────────────────────── */}
486
+ <Dialog open={waitingDialogOpen} onOpenChange={setWaitingDialogOpen}>
487
+ <DialogContent className="sm:max-w-md">
488
+ <DialogHeader>
489
+ <DialogTitle>Pause Task — Waiting on Dependency</DialogTitle>
490
+ <DialogDescription>
491
+ Specify the dependency type and detailed justification for pausing maintenance work.
492
+ </DialogDescription>
493
+ </DialogHeader>
494
+
495
+ <form onSubmit={handleMarkWaiting} className="space-y-4 pt-2">
496
+ <div className="space-y-1.5">
497
+ <Label htmlFor="dependency-type">Dependency Category</Label>
498
+ <Select
499
+ value={waitingDependencyType}
500
+ onValueChange={(val: any) => setWaitingDependencyType(val)}
501
+ >
502
+ <SelectTrigger id="dependency-type">
503
+ <SelectValue placeholder="Select dependency" />
504
+ </SelectTrigger>
505
+ <SelectContent>
506
+ <SelectItem value="parts">Waiting for Spare Parts</SelectItem>
507
+ <SelectItem value="vendor">Waiting for External Vendor Service</SelectItem>
508
+ <SelectItem value="requester">Waiting for Clinical Department / Requester</SelectItem>
509
+ <SelectItem value="department_access">Waiting for Physical / Room Access</SelectItem>
510
+ </SelectContent>
511
+ </Select>
512
+ </div>
513
+
514
+ <div className="space-y-1.5">
515
+ <Label htmlFor="waiting-reason">Reason & Details</Label>
516
+ <Textarea
517
+ id="waiting-reason"
518
+ placeholder="e.g. Awaiting delivery of power supply unit PN-4402 from OEM supplier."
519
+ value={waitingReason}
520
+ onChange={(e) => setWaitingReason(e.target.value)}
521
+ rows={3}
522
+ required
523
+ />
524
+ </div>
525
+
526
+ <DialogFooter className="gap-2 sm:gap-0 pt-2">
527
+ <Button
528
+ type="button"
529
+ variant="outline"
530
+ onClick={() => setWaitingDialogOpen(false)}
531
+ disabled={isPending}
532
+ >
533
+ Cancel
534
+ </Button>
535
+ <Button type="submit" disabled={isPending} className="bg-amber-600 hover:bg-amber-700 text-white">
536
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : null}
537
+ Confirm Pause
538
+ </Button>
539
+ </DialogFooter>
540
+ </form>
541
+ </DialogContent>
542
+ </Dialog>
543
+
544
+ {/* ── 2. Dialog: Complete Work ───────────────────────────────────────── */}
545
+ <Dialog open={completeDialogOpen} onOpenChange={setCompleteDialogOpen}>
546
+ <DialogContent className="sm:max-w-lg max-h-[90vh] overflow-y-auto">
547
+ <DialogHeader>
548
+ <DialogTitle>Complete Maintenance Work</DialogTitle>
549
+ <DialogDescription>
550
+ Record the technical execution details, diagnosis findings, and initial result evaluation.
551
+ </DialogDescription>
552
+ </DialogHeader>
553
+
554
+ <form onSubmit={handleCompleteWork} className="space-y-4 pt-2">
555
+ <div className="space-y-1.5">
556
+ <Label htmlFor="work-performed">
557
+ Work Performed <span className="text-destructive">*</span>
558
+ </Label>
559
+ <Textarea
560
+ id="work-performed"
561
+ placeholder="Describe all maintenance, calibration, tests, and repairs executed..."
562
+ value={workPerformed}
563
+ onChange={(e) => setWorkPerformed(e.target.value)}
564
+ rows={3}
565
+ required
566
+ />
567
+ </div>
568
+
569
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
570
+ <div className="space-y-1.5">
571
+ <Label htmlFor="diagnosis">Diagnosis / Initial Condition</Label>
572
+ <Input
573
+ id="diagnosis"
574
+ placeholder="Root technical assessment"
575
+ value={diagnosis}
576
+ onChange={(e) => setDiagnosis(e.target.value)}
577
+ />
578
+ </div>
579
+
580
+ <div className="space-y-1.5">
581
+ <Label htmlFor="root-cause">Root Cause</Label>
582
+ <Input
583
+ id="root-cause"
584
+ placeholder="e.g. Component wear, electrical surge"
585
+ value={rootCause}
586
+ onChange={(e) => setRootCause(e.target.value)}
587
+ />
588
+ </div>
589
+ </div>
590
+
591
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
592
+ <div className="space-y-1.5">
593
+ <Label htmlFor="final-result-code">Final Test Result</Label>
594
+ <Select value={finalResultCode} onValueChange={setFinalResultCode}>
595
+ <SelectTrigger id="final-result-code">
596
+ <SelectValue />
597
+ </SelectTrigger>
598
+ <SelectContent>
599
+ <SelectItem value="passed">Passed</SelectItem>
600
+ <SelectItem value="passed_with_limitations">Passed with Limitations</SelectItem>
601
+ <SelectItem value="failed">Failed</SelectItem>
602
+ <SelectItem value="no_fault_found">No Fault Found</SelectItem>
603
+ <SelectItem value="decommission_recommended">Decommission Recommended</SelectItem>
604
+ <SelectItem value="requires_external_service">Requires External Service</SelectItem>
605
+ </SelectContent>
606
+ </Select>
607
+ </div>
608
+
609
+ <div className="space-y-1.5">
610
+ <Label htmlFor="device-status-code">Proposed Device Status</Label>
611
+ <Select value={finalDeviceStatusCode} onValueChange={setFinalDeviceStatusCode}>
612
+ <SelectTrigger id="device-status-code">
613
+ <SelectValue />
614
+ </SelectTrigger>
615
+ <SelectContent>
616
+ <SelectItem value="awaiting_release">Awaiting Release</SelectItem>
617
+ <SelectItem value="operational">Operational</SelectItem>
618
+ <SelectItem value="operational_with_limitations">Limited Use</SelectItem>
619
+ <SelectItem value="under_repair">Under Repair</SelectItem>
620
+ <SelectItem value="out_of_service">Out of Service</SelectItem>
621
+ </SelectContent>
622
+ </Select>
623
+ </div>
624
+ </div>
625
+
626
+ <div className="space-y-1.5">
627
+ <Label htmlFor="findings">Key Findings & Observations</Label>
628
+ <Input
629
+ id="findings"
630
+ placeholder="Observed test parameters, physical checks"
631
+ value={findings}
632
+ onChange={(e) => setFindings(e.target.value)}
633
+ />
634
+ </div>
635
+
636
+ <div className="space-y-1.5">
637
+ <Label htmlFor="recommendations">Recommendations for Next Service</Label>
638
+ <Input
639
+ id="recommendations"
640
+ placeholder="Preventive maintenance advice, parts to monitor"
641
+ value={recommendations}
642
+ onChange={(e) => setRecommendations(e.target.value)}
643
+ />
644
+ </div>
645
+
646
+ <DialogFooter className="gap-2 sm:gap-0 pt-3">
647
+ <Button
648
+ type="button"
649
+ variant="outline"
650
+ onClick={() => setCompleteDialogOpen(false)}
651
+ disabled={isPending}
652
+ >
653
+ Cancel
654
+ </Button>
655
+ <Button type="submit" disabled={isPending} className="bg-emerald-600 hover:bg-emerald-700 text-white">
656
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : null}
657
+ Save & Complete Work
658
+ </Button>
659
+ </DialogFooter>
660
+ </form>
661
+ </DialogContent>
662
+ </Dialog>
663
+
664
+ {/* ── 3. Dialog: Quick Sign & Submit ─────────────────────────────────── */}
665
+ <Dialog open={signDialogOpen} onOpenChange={setSignDialogOpen}>
666
+ <DialogContent className="sm:max-w-md">
667
+ <DialogHeader>
668
+ <DialogTitle className="flex items-center gap-2">
669
+ <ShieldCheck className="h-5 w-5 text-primary" />
670
+ Sign & Submit Maintenance Record
671
+ </DialogTitle>
672
+ <DialogDescription>
673
+ Electronically attest that the recorded maintenance work is accurate and complete.
674
+ </DialogDescription>
675
+ </DialogHeader>
676
+
677
+ <form onSubmit={handleSignAndSubmit} className="space-y-4 pt-2">
678
+ <blockquote className="rounded-md border-l-2 border-primary/50 bg-muted/40 p-3 text-xs italic text-foreground/90">
679
+ &quot;I confirm that I performed the recorded maintenance/inspection and the details entered are accurate.&quot;
680
+ </blockquote>
681
+
682
+ <div className="space-y-1.5">
683
+ <Label htmlFor="sign-comments">Signer Comments (Optional)</Label>
684
+ <Textarea
685
+ id="sign-comments"
686
+ placeholder="Additional notes or context for the technical reviewer..."
687
+ value={signComments}
688
+ onChange={(e) => setSignComments(e.target.value)}
689
+ rows={2}
690
+ />
691
+ </div>
692
+
693
+ <div className="space-y-1.5">
694
+ <Label htmlFor="sign-password">
695
+ Re-Authenticate Password <span className="text-destructive">*</span>
696
+ </Label>
697
+ <Input
698
+ id="sign-password"
699
+ type="password"
700
+ placeholder="Enter your account password"
701
+ value={signPassword}
702
+ onChange={(e) => setSignPassword(e.target.value)}
703
+ required
704
+ autoFocus
705
+ />
706
+ </div>
707
+
708
+ <DialogFooter className="gap-2 sm:gap-0 pt-2">
709
+ <Button
710
+ type="button"
711
+ variant="outline"
712
+ onClick={() => setSignDialogOpen(false)}
713
+ disabled={isPending}
714
+ >
715
+ Cancel
716
+ </Button>
717
+ <Button type="submit" disabled={isPending} className="bg-cyan-700 hover:bg-cyan-800 text-white">
718
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : <FileSignature className="h-4 w-4 mr-1.5" />}
719
+ Sign Record
720
+ </Button>
721
+ </DialogFooter>
722
+ </form>
723
+ </DialogContent>
724
+ </Dialog>
725
+
726
+ {/* ── 4. Dialog: Technical Review ────────────────────────────────────── */}
727
+ <Dialog open={reviewDialogOpen} onOpenChange={setReviewDialogOpen}>
728
+ <DialogContent className="sm:max-w-md">
729
+ <DialogHeader>
730
+ <DialogTitle className="flex items-center gap-2">
731
+ <Eye className="h-5 w-5 text-purple-600" />
732
+ Technical Peer Review
733
+ </DialogTitle>
734
+ <DialogDescription>
735
+ Review checklist findings, test measurements, and parts used to approve or return for rework.
736
+ </DialogDescription>
737
+ </DialogHeader>
738
+
739
+ <form onSubmit={handleReviewWork} className="space-y-4 pt-2">
740
+ <div className="space-y-1.5">
741
+ <Label htmlFor="review-decision">Review Decision</Label>
742
+ <Select
743
+ value={reviewDecision}
744
+ onValueChange={(val: any) => setReviewDecision(val)}
745
+ >
746
+ <SelectTrigger id="review-decision">
747
+ <SelectValue />
748
+ </SelectTrigger>
749
+ <SelectContent>
750
+ <SelectItem value="approve">Approve Work (Proceed to Release)</SelectItem>
751
+ <SelectItem value="reject">Reject & Return for Rework</SelectItem>
752
+ </SelectContent>
753
+ </Select>
754
+ </div>
755
+
756
+ {reviewDecision === "approve" ? (
757
+ <>
758
+ <blockquote className="rounded-md border-l-2 border-purple-500 bg-purple-50/40 dark:bg-purple-950/20 p-3 text-xs italic text-foreground/90">
759
+ &quot;I have reviewed the findings, checklist, test results, and parts used, and verify technical completeness.&quot;
760
+ </blockquote>
761
+ <div className="space-y-1.5">
762
+ <Label htmlFor="review-notes">Review Notes (Optional)</Label>
763
+ <Textarea
764
+ id="review-notes"
765
+ placeholder="Comments regarding technical verification..."
766
+ value={reviewNotes}
767
+ onChange={(e) => setReviewNotes(e.target.value)}
768
+ rows={2}
769
+ />
770
+ </div>
771
+ </>
772
+ ) : (
773
+ <div className="space-y-1.5">
774
+ <Label htmlFor="rejection-reason">
775
+ Rework Justification <span className="text-destructive">*</span>
776
+ </Label>
777
+ <Textarea
778
+ id="rejection-reason"
779
+ placeholder="Explain why this work was rejected and what specific corrections are needed..."
780
+ value={rejectionReason}
781
+ onChange={(e) => setRejectionReason(e.target.value)}
782
+ rows={3}
783
+ required
784
+ />
785
+ </div>
786
+ )}
787
+
788
+ <div className="space-y-1.5">
789
+ <Label htmlFor="review-password">
790
+ Re-Authenticate Password <span className="text-destructive">*</span>
791
+ </Label>
792
+ <Input
793
+ id="review-password"
794
+ type="password"
795
+ placeholder="Enter password to sign review"
796
+ value={reviewPassword}
797
+ onChange={(e) => setReviewPassword(e.target.value)}
798
+ required
799
+ />
800
+ </div>
801
+
802
+ <DialogFooter className="gap-2 sm:gap-0 pt-2">
803
+ <Button
804
+ type="button"
805
+ variant="outline"
806
+ onClick={() => setReviewDialogOpen(false)}
807
+ disabled={isPending}
808
+ >
809
+ Cancel
810
+ </Button>
811
+ <Button
812
+ type="submit"
813
+ disabled={isPending}
814
+ className={
815
+ reviewDecision === "approve"
816
+ ? "bg-purple-600 hover:bg-purple-700 text-white"
817
+ : "bg-rose-600 hover:bg-rose-700 text-white"
818
+ }
819
+ >
820
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : null}
821
+ {reviewDecision === "approve" ? "Sign & Approve" : "Return for Rework"}
822
+ </Button>
823
+ </DialogFooter>
824
+ </form>
825
+ </DialogContent>
826
+ </Dialog>
827
+
828
+ {/* ── 5. Dialog: Release Device ──────────────────────────────────────── */}
829
+ <Dialog open={releaseDialogOpen} onOpenChange={setReleaseDialogOpen}>
830
+ <DialogContent className="sm:max-w-md">
831
+ <DialogHeader>
832
+ <DialogTitle className="flex items-center gap-2">
833
+ <Sparkles className="h-5 w-5 text-emerald-600" />
834
+ Authorize Device Release
835
+ </DialogTitle>
836
+ <DialogDescription>
837
+ Authorize return of this medical device to active clinical service and finalize maintenance records.
838
+ </DialogDescription>
839
+ </DialogHeader>
840
+
841
+ <form onSubmit={handleReleaseDevice} className="space-y-4 pt-2">
842
+ <blockquote className="rounded-md border-l-2 border-emerald-500 bg-emerald-50/40 dark:bg-emerald-950/20 p-3 text-xs italic text-foreground/90">
843
+ &quot;I authorize the release of this medical device for clinical use with the specified status.&quot;
844
+ </blockquote>
845
+
846
+ <div className="space-y-1.5">
847
+ <Label htmlFor="release-status">Final Clinical Device Status</Label>
848
+ <Select
849
+ value={releaseStatus}
850
+ onValueChange={(val: any) => setReleaseStatus(val)}
851
+ >
852
+ <SelectTrigger id="release-status">
853
+ <SelectValue />
854
+ </SelectTrigger>
855
+ <SelectContent>
856
+ <SelectItem value="operational">Operational (Full Clinical Use)</SelectItem>
857
+ <SelectItem value="operational_with_limitations">Operational with Limitations</SelectItem>
858
+ </SelectContent>
859
+ </Select>
860
+ </div>
861
+
862
+ <div className="space-y-1.5">
863
+ <Label htmlFor="release-notes">Release Notes / Clinical Guidance (Optional)</Label>
864
+ <Textarea
865
+ id="release-notes"
866
+ placeholder="Guidance for clinical operators upon return..."
867
+ value={releaseNotes}
868
+ onChange={(e) => setReleaseNotes(e.target.value)}
869
+ rows={2}
870
+ />
871
+ </div>
872
+
873
+ <div className="space-y-1.5">
874
+ <Label htmlFor="release-password">
875
+ Re-Authenticate Password <span className="text-destructive">*</span>
876
+ </Label>
877
+ <Input
878
+ id="release-password"
879
+ type="password"
880
+ placeholder="Enter password to authorize release"
881
+ value={releasePassword}
882
+ onChange={(e) => setReleasePassword(e.target.value)}
883
+ required
884
+ />
885
+ </div>
886
+
887
+ <DialogFooter className="gap-2 sm:gap-0 pt-2">
888
+ <Button
889
+ type="button"
890
+ variant="outline"
891
+ onClick={() => setReleaseDialogOpen(false)}
892
+ disabled={isPending}
893
+ >
894
+ Cancel
895
+ </Button>
896
+ <Button type="submit" disabled={isPending} className="bg-emerald-600 hover:bg-emerald-700 text-white">
897
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : null}
898
+ Sign & Release Device
899
+ </Button>
900
+ </DialogFooter>
901
+ </form>
902
+ </DialogContent>
903
+ </Dialog>
904
+
905
+ {/* ── 6. Dialog: Cancel Task ─────────────────────────────────────────── */}
906
+ <Dialog open={cancelDialogOpen} onOpenChange={setCancelDialogOpen}>
907
+ <DialogContent className="sm:max-w-md">
908
+ <DialogHeader>
909
+ <DialogTitle className="flex items-center gap-2 text-destructive">
910
+ <Ban className="h-5 w-5" />
911
+ Cancel Maintenance Task
912
+ </DialogTitle>
913
+ <DialogDescription>
914
+ Are you sure you want to cancel this maintenance work order? This action is audited and cannot be undone.
915
+ </DialogDescription>
916
+ </DialogHeader>
917
+
918
+ <form onSubmit={handleCancelTask} className="space-y-4 pt-2">
919
+ <div className="space-y-1.5">
920
+ <Label htmlFor="cancel-reason">
921
+ Cancellation Reason <span className="text-destructive">*</span>
922
+ </Label>
923
+ <Textarea
924
+ id="cancel-reason"
925
+ placeholder="Provide a detailed explanation for cancelling this task (min 10 characters)..."
926
+ value={cancellationReason}
927
+ onChange={(e) => setCancellationReason(e.target.value)}
928
+ rows={3}
929
+ required
930
+ />
931
+ </div>
932
+
933
+ <DialogFooter className="gap-2 sm:gap-0 pt-2">
934
+ <Button
935
+ type="button"
936
+ variant="outline"
937
+ onClick={() => setCancelDialogOpen(false)}
938
+ disabled={isPending}
939
+ >
940
+ Keep Task
941
+ </Button>
942
+ <Button type="submit" disabled={isPending} variant="destructive">
943
+ {isPending ? <Loader2 className="h-4 w-4 animate-spin mr-1.5" /> : null}
944
+ Confirm Cancellation
945
+ </Button>
946
+ </DialogFooter>
947
+ </form>
948
+ </DialogContent>
949
+ </Dialog>
950
+ </div>
951
+ )
952
+ }
953
+
954
+ export default TaskActionBar
955
+