@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,121 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { cn } from "@/lib/utils"
6
+ import { Lock, ArrowRight, History } from "lucide-react"
7
+
8
+ export interface SignatureEventItem {
9
+ id: string
10
+ eventType: string
11
+ actorUserId?: string
12
+ timestamp: Date | string
13
+ previousStatus?: string | null
14
+ newStatus?: string | null
15
+ reason?: string | null
16
+ currentEventHash: string
17
+ previousEventHash?: string | null
18
+ }
19
+
20
+ export interface SignatureHistoryProps {
21
+ events: SignatureEventItem[]
22
+ className?: string
23
+ }
24
+
25
+ const EVENT_COLORS: Record<string, string> = {
26
+ created: "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/30 dark:text-blue-300 dark:border-blue-800",
27
+ approved: "bg-emerald-100 text-emerald-800 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-300 dark:border-emerald-800",
28
+ rejected: "bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-300 dark:border-red-800",
29
+ amended: "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-900/30 dark:text-orange-300 dark:border-orange-800",
30
+ voided: "bg-rose-100 text-rose-800 border-rose-200 dark:bg-rose-900/30 dark:text-rose-300 dark:border-rose-800",
31
+ reviewed: "bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-900/30 dark:text-purple-300 dark:border-purple-800",
32
+ }
33
+
34
+ const formatTime = (date: Date | string) => {
35
+ const d = new Date(date)
36
+ return d.toLocaleString("en-US", {
37
+ day: "2-digit",
38
+ month: "short",
39
+ year: "numeric",
40
+ hour: "2-digit",
41
+ minute: "2-digit",
42
+ second: "2-digit",
43
+ hour12: true,
44
+ })
45
+ }
46
+
47
+ function truncateHash(hash: string) {
48
+ if (!hash || hash.length < 16) return hash
49
+ return `${hash.substring(0, 8)}...${hash.substring(hash.length - 8)}`
50
+ }
51
+
52
+ export function SignatureHistory({ events, className }: SignatureHistoryProps) {
53
+ // Sort from newest to oldest
54
+ const sortedEvents = [...events].sort((a, b) => {
55
+ return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
56
+ })
57
+
58
+ if (sortedEvents.length === 0) {
59
+ return (
60
+ <div className={cn("text-center py-8 text-muted-foreground", className)}>
61
+ <History className="mx-auto h-8 w-8 opacity-40 mb-2" />
62
+ <p className="text-sm">No signature events recorded.</p>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ return (
68
+ <div className={cn("relative space-y-0 ml-3 sm:ml-4 border-l-2 border-muted", className)}>
69
+ {sortedEvents.map((event, idx) => {
70
+ const isLast = idx === sortedEvents.length - 1
71
+ const eventTypeClass = EVENT_COLORS[event.eventType.toLowerCase()] ||
72
+ "bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700"
73
+
74
+ return (
75
+ <div key={event.id} className="relative pl-6 sm:pl-8 pb-8 last:pb-0 group">
76
+ {/* Timeline Dot */}
77
+ <div className="absolute -left-[5px] top-1 h-2 w-2 rounded-full bg-border ring-4 ring-background group-hover:bg-primary transition-colors" />
78
+
79
+ <div className="space-y-2">
80
+ <div className="flex flex-wrap items-center gap-2">
81
+ <Badge variant="outline" className={cn("capitalize text-[10px] font-semibold tracking-wide", eventTypeClass)}>
82
+ {event.eventType}
83
+ </Badge>
84
+ <span className="text-xs text-muted-foreground font-medium">
85
+ {formatTime(event.timestamp)}
86
+ </span>
87
+ </div>
88
+
89
+ {event.previousStatus && event.newStatus && (
90
+ <div className="flex items-center gap-2 text-sm text-foreground/80">
91
+ <span className="capitalize">{event.previousStatus}</span>
92
+ <ArrowRight className="h-3 w-3 text-muted-foreground" />
93
+ <span className="capitalize font-medium text-foreground">{event.newStatus}</span>
94
+ </div>
95
+ )}
96
+
97
+ {event.reason && (
98
+ <p className="text-sm text-foreground bg-muted/40 p-2.5 rounded-md border border-border/50">
99
+ <span className="font-semibold text-xs text-muted-foreground block mb-1 uppercase tracking-wider">Reason</span>
100
+ {event.reason}
101
+ </p>
102
+ )}
103
+
104
+ <div className="flex items-center gap-1.5 text-[11px] text-muted-foreground/70 font-mono mt-1">
105
+ <Lock className="h-3 w-3" />
106
+ <span title={event.currentEventHash}>Hash: {truncateHash(event.currentEventHash)}</span>
107
+ {event.previousEventHash && (
108
+ <span title={event.previousEventHash}>
109
+ <ArrowRight className="h-2.5 w-2.5 inline mx-1" />
110
+ {truncateHash(event.previousEventHash)}
111
+ </span>
112
+ )}
113
+ </div>
114
+ </div>
115
+ </div>
116
+ )
117
+ })}
118
+ </div>
119
+ )
120
+ }
121
+
@@ -0,0 +1,164 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Badge } from "@/components/ui/badge"
7
+ import { cn } from "@/lib/utils"
8
+ import { CheckCircle2, Clock, Circle, XCircle, PenSquare } from "lucide-react"
9
+
10
+ export interface SignatureRequirement {
11
+ purpose: string
12
+ allowedRoles: string[]
13
+ required: boolean
14
+ }
15
+
16
+ export interface CompletedSignature {
17
+ id: string
18
+ purpose: string
19
+ signerName: string
20
+ signerRole: string
21
+ signedAt: Date | string
22
+ status: string
23
+ contentHash: string
24
+ }
25
+
26
+ export interface SignatureStatusPanelProps {
27
+ required: SignatureRequirement[]
28
+ completed: CompletedSignature[]
29
+ pending: SignatureRequirement[]
30
+ canSign: { purpose: string; allowed: boolean; reason?: string }[]
31
+ onSignClick?: (purpose: string) => void
32
+ className?: string
33
+ }
34
+
35
+ const formatPurpose = (purpose: string) => {
36
+ const map: Record<string, string> = {
37
+ perform: "Performer Signature",
38
+ review: "Reviewer Signature",
39
+ release: "Release Authority",
40
+ approve: "Approval",
41
+ reject: "Rejection",
42
+ }
43
+ if (map[purpose]) return map[purpose]
44
+ return purpose.charAt(0).toUpperCase() + purpose.slice(1)
45
+ }
46
+
47
+ const formatTime = (date: Date | string) => {
48
+ const d = new Date(date)
49
+ return d.toLocaleString("en-US", {
50
+ day: "2-digit",
51
+ month: "short",
52
+ year: "numeric",
53
+ hour: "2-digit",
54
+ minute: "2-digit",
55
+ hour12: false,
56
+ })
57
+ }
58
+
59
+ export function SignatureStatusPanel({
60
+ required,
61
+ completed,
62
+ pending,
63
+ canSign,
64
+ onSignClick,
65
+ className,
66
+ }: SignatureStatusPanelProps) {
67
+ // Combine into an ordered list of steps based on the requirements
68
+ const steps = required.map((req) => {
69
+ const comp = completed.find((c) => c.purpose === req.purpose)
70
+ const isPending = pending.some((p) => p.purpose === req.purpose)
71
+ const signAuth = canSign.find((c) => c.purpose === req.purpose)
72
+
73
+ return {
74
+ requirement: req,
75
+ completed: comp,
76
+ isPending,
77
+ signAuth,
78
+ }
79
+ })
80
+
81
+ return (
82
+ <Card className={cn("overflow-hidden", className)}>
83
+ <CardHeader className="bg-muted/40 pb-3 pt-4 px-4 border-b">
84
+ <CardTitle className="text-sm font-semibold flex items-center gap-2">
85
+ Signature Status
86
+ </CardTitle>
87
+ </CardHeader>
88
+ <CardContent className="p-0">
89
+ <div className="flex flex-col divide-y">
90
+ {steps.map((step, idx) => {
91
+ const purposeLabel = formatPurpose(step.requirement.purpose)
92
+
93
+ if (step.completed) {
94
+ const isRejected = step.completed.status === 'rejected'
95
+ return (
96
+ <div key={idx} className="flex items-start gap-3 p-4 bg-card">
97
+ {isRejected ? (
98
+ <XCircle className="h-5 w-5 text-destructive shrink-0 mt-0.5" />
99
+ ) : (
100
+ <CheckCircle2 className="h-5 w-5 text-emerald-500 shrink-0 mt-0.5" />
101
+ )}
102
+ <div className="min-w-0 flex-1 space-y-1">
103
+ <p className="text-sm font-medium text-foreground">
104
+ {purposeLabel}
105
+ </p>
106
+ <p className="text-xs text-muted-foreground truncate">
107
+ {step.completed.signerName}, {step.completed.signerRole} • {formatTime(step.completed.signedAt)}
108
+ </p>
109
+ </div>
110
+ </div>
111
+ )
112
+ }
113
+
114
+ if (step.isPending) {
115
+ return (
116
+ <div key={idx} className="flex items-start gap-3 p-4 bg-muted/20">
117
+ <Clock className="h-5 w-5 text-amber-500 shrink-0 mt-0.5" />
118
+ <div className="min-w-0 flex-1 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
119
+ <div className="space-y-1">
120
+ <p className="text-sm font-medium text-foreground">
121
+ {purposeLabel}
122
+ </p>
123
+ <p className="text-xs text-muted-foreground">
124
+ {step.signAuth?.allowed
125
+ ? "Awaiting your signature"
126
+ : step.signAuth?.reason || "Awaiting review"}
127
+ </p>
128
+ </div>
129
+ {step.signAuth?.allowed && onSignClick && (
130
+ <Button
131
+ size="sm"
132
+ onClick={() => onSignClick(step.requirement.purpose)}
133
+ className="shrink-0 h-8 gap-1.5"
134
+ >
135
+ <PenSquare className="h-3.5 w-3.5" />
136
+ Sign
137
+ </Button>
138
+ )}
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+
144
+ // Not yet required
145
+ return (
146
+ <div key={idx} className="flex items-start gap-3 p-4 opacity-60">
147
+ <Circle className="h-5 w-5 text-muted-foreground shrink-0 mt-0.5" />
148
+ <div className="min-w-0 flex-1 space-y-1">
149
+ <p className="text-sm font-medium text-foreground">
150
+ {purposeLabel}
151
+ </p>
152
+ <p className="text-xs text-muted-foreground">
153
+ Not yet required (waiting for previous steps)
154
+ </p>
155
+ </div>
156
+ </div>
157
+ )
158
+ })}
159
+ </div>
160
+ </CardContent>
161
+ </Card>
162
+ )
163
+ }
164
+
@@ -0,0 +1,141 @@
1
+ 'use client';
2
+
3
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
4
+ import { Badge } from '@/components/ui/badge';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Building2, Phone, PhoneCall, Wrench, Shield, CheckCircle2 } from 'lucide-react';
7
+ import Link from 'next/link';
8
+ import { EngineerDutyStatus } from '@/lib/db/schema/engineer-profiles';
9
+
10
+ interface DepartmentCoverageProps {
11
+ coverage: {
12
+ id: string;
13
+ name: string;
14
+ code: string;
15
+ deviceCount: number;
16
+ primaryEngineer: {
17
+ id: string;
18
+ fullName: string;
19
+ jobTitle: string;
20
+ extension: string | null;
21
+ mobileNumber: string | null;
22
+ dutyStatus: EngineerDutyStatus;
23
+ } | null;
24
+ }[];
25
+ }
26
+
27
+ export function DepartmentCoverage({ coverage }: DepartmentCoverageProps) {
28
+ return (
29
+ <div className="space-y-4">
30
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
31
+ <div>
32
+ <h3 className="text-base font-semibold">Hospital Department Engineering Assignments</h3>
33
+ <p className="text-xs text-muted-foreground">
34
+ Each hospital department is assigned a designated biomedical engineer for routine inspections, PM compliance, and rapid fault resolution.
35
+ </p>
36
+ </div>
37
+ </div>
38
+
39
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-5">
40
+ {coverage.map((dept) => {
41
+ const eng = dept.primaryEngineer;
42
+
43
+ return (
44
+ <Card key={dept.id} className="border-border/70 shadow-sm flex flex-col justify-between hover:border-primary/40 transition-colors">
45
+ <CardHeader className="pb-3">
46
+ <div className="flex items-start justify-between gap-2">
47
+ <div>
48
+ <Badge variant="outline" className="text-[10px] font-mono mb-1">
49
+ {dept.code}
50
+ </Badge>
51
+ <CardTitle className="text-base font-semibold leading-tight">{dept.name}</CardTitle>
52
+ <span className="text-xs text-muted-foreground flex items-center gap-1 mt-1">
53
+ <Wrench className="w-3 h-3 text-primary" />
54
+ {dept.deviceCount} Medical Equipment Assets
55
+ </span>
56
+ </div>
57
+ </div>
58
+ </CardHeader>
59
+
60
+ <CardContent className="space-y-3 text-xs flex-1">
61
+ <div className="p-3 rounded-lg bg-muted/40 border border-border/50 space-y-2">
62
+ <span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider block">
63
+ Designated BME Lead
64
+ </span>
65
+
66
+ {eng ? (
67
+ <div>
68
+ <div className="flex items-center justify-between">
69
+ <p className="font-semibold text-sm">{eng.fullName}</p>
70
+ {eng.dutyStatus === 'on_duty' ? (
71
+ <Badge className="bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/30 text-[10px] py-0 px-1.5 font-normal">
72
+ 🟢 On Duty
73
+ </Badge>
74
+ ) : eng.dutyStatus === 'on_call' ? (
75
+ <Badge className="bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/30 text-[10px] py-0 px-1.5 font-normal">
76
+ 📞 On Call
77
+ </Badge>
78
+ ) : (
79
+ <Badge variant="outline" className="text-muted-foreground text-[10px] py-0 px-1.5">
80
+ ⚪ Off Duty
81
+ </Badge>
82
+ )}
83
+ </div>
84
+ <p className="text-[11px] text-muted-foreground mt-0.5">{eng.jobTitle}</p>
85
+
86
+ <div className="flex items-center gap-3 mt-2 text-xs pt-2 border-t border-border/40">
87
+ {eng.extension && (
88
+ <span className="flex items-center gap-1 font-medium text-foreground">
89
+ <PhoneCall className="w-3 h-3 text-primary" />
90
+ Ext: {eng.extension}
91
+ </span>
92
+ )}
93
+ {eng.mobileNumber && (
94
+ <a
95
+ href={`tel:${eng.mobileNumber}`}
96
+ className="flex items-center gap-1 text-primary hover:underline font-medium"
97
+ >
98
+ <Phone className="w-3 h-3" />
99
+ {eng.mobileNumber}
100
+ </a>
101
+ )}
102
+ </div>
103
+ </div>
104
+ ) : (
105
+ <p className="text-muted-foreground italic text-xs">General biomedical team pool</p>
106
+ )}
107
+ </div>
108
+ </CardContent>
109
+
110
+ <div className="p-3 pt-0 flex gap-2">
111
+ <Button asChild variant="outline" size="sm" className="flex-1 text-xs h-8">
112
+ <Link href={`/departments/${dept.id}`}>
113
+ <Building2 className="w-3 h-3 mr-1" />
114
+ View Dept
115
+ </Link>
116
+ </Button>
117
+
118
+ {eng?.mobileNumber ? (
119
+ <Button asChild size="sm" className="flex-1 text-xs h-8 gap-1.5">
120
+ <a href={`tel:${eng.mobileNumber}`}>
121
+ <PhoneCall className="w-3 h-3" />
122
+ Contact Lead
123
+ </a>
124
+ </Button>
125
+ ) : (
126
+ <Button asChild size="sm" className="flex-1 text-xs h-8 gap-1.5">
127
+ <Link href={`/tickets/new?departmentId=${dept.id}`}>
128
+ <Wrench className="w-3 h-3" />
129
+ Report Fault
130
+ </Link>
131
+ </Button>
132
+ )}
133
+ </div>
134
+ </Card>
135
+ );
136
+ })}
137
+ </div>
138
+ </div>
139
+ );
140
+ }
141
+
@@ -0,0 +1,242 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ } from '@/components/ui/dialog';
12
+ import { Button } from '@/components/ui/button';
13
+ import { Input } from '@/components/ui/input';
14
+ import { Label } from '@/components/ui/label';
15
+ import { Textarea } from '@/components/ui/textarea';
16
+ import { EngineerTeamMember, updateEngineerProfile } from '@/lib/actions/engineers';
17
+ import { toast } from 'sonner';
18
+
19
+ interface EditEngineerDialogProps {
20
+ engineer: EngineerTeamMember | null;
21
+ open: boolean;
22
+ onOpenChange: (open: boolean) => void;
23
+ onSuccess: () => void;
24
+ }
25
+
26
+ const ALL_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
27
+
28
+ export function EditEngineerDialog({
29
+ engineer,
30
+ open,
31
+ onOpenChange,
32
+ onSuccess,
33
+ }: EditEngineerDialogProps) {
34
+ const [jobTitle, setJobTitle] = useState(engineer?.jobTitle || '');
35
+ const [specialization, setSpecialization] = useState(engineer?.specialization || '');
36
+ const [extension, setExtension] = useState(engineer?.extension || '');
37
+ const [mobileNumber, setMobileNumber] = useState(engineer?.mobileNumber || '');
38
+ const [shiftHours, setShiftHours] = useState(engineer?.shiftHours || '08:00 AM - 04:00 PM');
39
+ const [workingDays, setWorkingDays] = useState<string[]>(engineer?.workingDays || []);
40
+ const [onCallDays, setOnCallDays] = useState<string[]>(engineer?.onCallDays || []);
41
+ const [notes, setNotes] = useState(engineer?.notes || '');
42
+ const [isSubmitting, setIsSubmitting] = useState(false);
43
+
44
+ useEffect(() => {
45
+ if (engineer) {
46
+ setJobTitle(engineer.jobTitle || '');
47
+ setSpecialization(engineer.specialization || '');
48
+ setExtension(engineer.extension || '');
49
+ setMobileNumber(engineer.mobileNumber || '');
50
+ setShiftHours(engineer.shiftHours || '08:00 AM - 04:00 PM');
51
+ setWorkingDays(engineer.workingDays || []);
52
+ setOnCallDays(engineer.onCallDays || []);
53
+ setNotes(engineer.notes || '');
54
+ }
55
+ }, [engineer]);
56
+
57
+ if (!engineer) return null;
58
+
59
+ const toggleWorkingDay = (day: string) => {
60
+ setWorkingDays((prev) =>
61
+ prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
62
+ );
63
+ };
64
+
65
+ const toggleOnCallDay = (day: string) => {
66
+ setOnCallDays((prev) =>
67
+ prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
68
+ );
69
+ };
70
+
71
+ const handleSubmit = async (e: React.FormEvent) => {
72
+ e.preventDefault();
73
+ if (!engineer) return;
74
+ setIsSubmitting(true);
75
+
76
+ try {
77
+ const res = await updateEngineerProfile(engineer.id, {
78
+ jobTitle,
79
+ specialization,
80
+ extension,
81
+ mobileNumber,
82
+ shiftHours,
83
+ workingDays,
84
+ onCallDays,
85
+ notes,
86
+ });
87
+
88
+ if (res.success) {
89
+ toast.success(`Updated profile for ${engineer.fullName}`);
90
+ onOpenChange(false);
91
+ onSuccess();
92
+ } else {
93
+ toast.error(res.error || 'Failed to update engineer profile');
94
+ }
95
+ } catch {
96
+ toast.error('An unexpected error occurred');
97
+ } finally {
98
+ setIsSubmitting(false);
99
+ }
100
+ };
101
+
102
+ return (
103
+ <Dialog open={open} onOpenChange={onOpenChange}>
104
+ <DialogContent className="max-w-md max-h-[90vh] overflow-y-auto">
105
+ <DialogHeader>
106
+ <DialogTitle>Edit Engineer Schedule & Info</DialogTitle>
107
+ <DialogDescription>
108
+ Update contact info, shift hours, and working days for <strong>{engineer.fullName}</strong>.
109
+ </DialogDescription>
110
+ </DialogHeader>
111
+
112
+ <form onSubmit={handleSubmit} className="space-y-4 text-xs">
113
+ <div className="grid grid-cols-2 gap-3">
114
+ <div className="space-y-1.5">
115
+ <Label className="text-xs">Job Title</Label>
116
+ <Input
117
+ value={jobTitle}
118
+ onChange={(e) => setJobTitle(e.target.value)}
119
+ placeholder="e.g. Lead Biomedical Engineer"
120
+ required
121
+ className="h-8 text-xs"
122
+ />
123
+ </div>
124
+ <div className="space-y-1.5">
125
+ <Label className="text-xs">Specialization</Label>
126
+ <Input
127
+ value={specialization}
128
+ onChange={(e) => setSpecialization(e.target.value)}
129
+ placeholder="e.g. Critical Care Systems"
130
+ className="h-8 text-xs"
131
+ />
132
+ </div>
133
+ </div>
134
+
135
+ <div className="grid grid-cols-2 gap-3">
136
+ <div className="space-y-1.5">
137
+ <Label className="text-xs">Internal Phone Extension</Label>
138
+ <Input
139
+ value={extension}
140
+ onChange={(e) => setExtension(e.target.value)}
141
+ placeholder="e.g. 201"
142
+ className="h-8 text-xs font-mono"
143
+ />
144
+ </div>
145
+ <div className="space-y-1.5">
146
+ <Label className="text-xs">Direct Mobile Number</Label>
147
+ <Input
148
+ value={mobileNumber}
149
+ onChange={(e) => setMobileNumber(e.target.value)}
150
+ placeholder="e.g. +249 91 234 5678"
151
+ className="h-8 text-xs font-mono"
152
+ />
153
+ </div>
154
+ </div>
155
+
156
+ <div className="space-y-1.5">
157
+ <Label className="text-xs">Shift Hours</Label>
158
+ <Input
159
+ value={shiftHours}
160
+ onChange={(e) => setShiftHours(e.target.value)}
161
+ placeholder="e.g. 08:00 AM - 04:00 PM"
162
+ className="h-8 text-xs"
163
+ />
164
+ </div>
165
+
166
+ <div className="space-y-1.5">
167
+ <Label className="text-xs font-semibold">Scheduled Working Days (On-Site)</Label>
168
+ <div className="flex flex-wrap gap-1.5">
169
+ {ALL_DAYS.map((day) => {
170
+ const checked = workingDays.includes(day);
171
+ return (
172
+ <button
173
+ type="button"
174
+ key={day}
175
+ onClick={() => toggleWorkingDay(day)}
176
+ className={`px-2.5 py-1 rounded text-xs font-medium border transition-colors ${
177
+ checked
178
+ ? 'bg-primary text-primary-foreground border-primary'
179
+ : 'bg-muted/40 text-muted-foreground border-border/70 hover:bg-muted'
180
+ }`}
181
+ >
182
+ {day}
183
+ </button>
184
+ );
185
+ })}
186
+ </div>
187
+ </div>
188
+
189
+ <div className="space-y-1.5">
190
+ <Label className="text-xs font-semibold">Emergency On-Call Days</Label>
191
+ <div className="flex flex-wrap gap-1.5">
192
+ {ALL_DAYS.map((day) => {
193
+ const checked = onCallDays.includes(day);
194
+ return (
195
+ <button
196
+ type="button"
197
+ key={day}
198
+ onClick={() => toggleOnCallDay(day)}
199
+ className={`px-2.5 py-1 rounded text-xs font-medium border transition-colors ${
200
+ checked
201
+ ? 'bg-amber-500 text-white border-amber-600'
202
+ : 'bg-muted/40 text-muted-foreground border-border/70 hover:bg-muted'
203
+ }`}
204
+ >
205
+ {day} 📞
206
+ </button>
207
+ );
208
+ })}
209
+ </div>
210
+ </div>
211
+
212
+ <div className="space-y-1.5">
213
+ <Label className="text-xs">Clinical Engineering Notes / Certifications</Label>
214
+ <Textarea
215
+ value={notes}
216
+ onChange={(e) => setNotes(e.target.value)}
217
+ placeholder="e.g. Lead responder for OR 1 & 2. Certified on Dräger anesthesia workstations."
218
+ rows={2}
219
+ className="text-xs resize-none"
220
+ />
221
+ </div>
222
+
223
+ <DialogFooter className="pt-2">
224
+ <Button
225
+ type="button"
226
+ variant="outline"
227
+ size="sm"
228
+ onClick={() => onOpenChange(false)}
229
+ disabled={isSubmitting}
230
+ >
231
+ Cancel
232
+ </Button>
233
+ <Button type="submit" size="sm" disabled={isSubmitting}>
234
+ {isSubmitting ? 'Saving...' : 'Save Changes'}
235
+ </Button>
236
+ </DialogFooter>
237
+ </form>
238
+ </DialogContent>
239
+ </Dialog>
240
+ );
241
+ }
242
+