@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,89 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogHeader,
7
+ DialogTitle,
8
+ DialogDescription,
9
+ } from '@/components/ui/dialog';
10
+ import { Wrench, Stethoscope, ArrowRight } from 'lucide-react';
11
+
12
+ export type UserTypeSelection = 'engineer' | 'department';
13
+
14
+ export interface AddUserTypeDialogProps {
15
+ open: boolean;
16
+ onOpenChange: (open: boolean) => void;
17
+ onSelect: (type: UserTypeSelection) => void;
18
+ }
19
+
20
+ export function AddUserTypeDialog({
21
+ open,
22
+ onOpenChange,
23
+ onSelect,
24
+ }: AddUserTypeDialogProps) {
25
+ return (
26
+ <Dialog open={open} onOpenChange={onOpenChange}>
27
+ <DialogContent className="sm:max-w-2xl">
28
+ <DialogHeader className="text-left space-y-1">
29
+ <DialogTitle className="text-xl font-bold tracking-tight">
30
+ Add New User
31
+ </DialogTitle>
32
+ <DialogDescription className="text-sm text-muted-foreground">
33
+ Select the account type you would like to create. Each user type includes tailored permissions and workflow settings.
34
+ </DialogDescription>
35
+ </DialogHeader>
36
+
37
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2">
38
+ {/* Biomedical Engineer Option */}
39
+ <button
40
+ type="button"
41
+ onClick={() => onSelect('engineer')}
42
+ className="group relative flex flex-col justify-between rounded-xl border border-border bg-card p-6 text-left transition-all duration-200 hover:border-blue-500/50 hover:bg-blue-50/40 hover:shadow-md dark:hover:border-blue-500/50 dark:hover:bg-blue-950/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 cursor-pointer"
43
+ >
44
+ <div>
45
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-blue-100 text-blue-600 transition-colors group-hover:bg-blue-600 group-hover:text-white dark:bg-blue-950/70 dark:text-blue-400 dark:group-hover:bg-blue-600 dark:group-hover:text-white shadow-xs">
46
+ <Wrench className="h-6 w-6 transition-transform duration-200 group-hover:scale-110" />
47
+ </div>
48
+ <h3 className="text-base font-semibold text-foreground transition-colors group-hover:text-blue-600 dark:group-hover:text-blue-400">
49
+ Biomedical Engineer
50
+ </h3>
51
+ <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
52
+ Add an engineer to the biomedical team roster with duty scheduling, department coverage, and technical specialization.
53
+ </p>
54
+ </div>
55
+
56
+ <div className="mt-6 flex items-center text-xs font-semibold text-blue-600 dark:text-blue-400 transition-colors">
57
+ <span>Continue as Engineer</span>
58
+ <ArrowRight className="ml-1.5 h-4 w-4 transition-transform duration-200 group-hover:translate-x-1" />
59
+ </div>
60
+ </button>
61
+
62
+ {/* Department Staff Option */}
63
+ <button
64
+ type="button"
65
+ onClick={() => onSelect('department')}
66
+ className="group relative flex flex-col justify-between rounded-xl border border-border bg-card p-6 text-left transition-all duration-200 hover:border-purple-500/50 hover:bg-purple-50/40 hover:shadow-md dark:hover:border-purple-500/50 dark:hover:bg-purple-950/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-purple-500 focus-visible:ring-offset-2 cursor-pointer"
67
+ >
68
+ <div>
69
+ <div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-purple-100 text-purple-600 transition-colors group-hover:bg-purple-600 group-hover:text-white dark:bg-purple-950/70 dark:text-purple-400 dark:group-hover:bg-purple-600 dark:group-hover:text-white shadow-xs">
70
+ <Stethoscope className="h-6 w-6 transition-transform duration-200 group-hover:scale-110" />
71
+ </div>
72
+ <h3 className="text-base font-semibold text-foreground transition-colors group-hover:text-purple-600 dark:group-hover:text-purple-400">
73
+ Department Staff
74
+ </h3>
75
+ <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
76
+ Add clinical or administrative staff to a hospital department for equipment reporting and ticket management.
77
+ </p>
78
+ </div>
79
+
80
+ <div className="mt-6 flex items-center text-xs font-semibold text-purple-600 dark:text-purple-400 transition-colors">
81
+ <span>Continue as Staff</span>
82
+ <ArrowRight className="ml-1.5 h-4 w-4 transition-transform duration-200 group-hover:translate-x-1" />
83
+ </div>
84
+ </button>
85
+ </div>
86
+ </DialogContent>
87
+ </Dialog>
88
+ );
89
+ }
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Download, Loader2 } from 'lucide-react';
6
+ import { exportAuditLogs, type AuditLogFilters } from '@/lib/actions/audit-logs';
7
+
8
+ export interface AuditLogExportProps {
9
+ filters: AuditLogFilters;
10
+ }
11
+
12
+ function escapeCsvField(val: unknown): string {
13
+ if (val === null || val === undefined) return '""';
14
+ const str = String(val).replace(/"/g, '""');
15
+ return `"${str}"`;
16
+ }
17
+
18
+ /**
19
+ * Client component to export audit log records matching the active filters to a CSV file.
20
+ */
21
+ export function AuditLogExport({ filters }: AuditLogExportProps) {
22
+ const [isExporting, setIsExporting] = useState(false);
23
+
24
+ const handleExport = async () => {
25
+ setIsExporting(true);
26
+ try {
27
+ const res = await exportAuditLogs(filters);
28
+ if (!res.success || !res.data) {
29
+ throw new Error(res.error || 'Failed to fetch audit logs for export');
30
+ }
31
+
32
+ const rows = res.data;
33
+ const headers = [
34
+ 'Timestamp',
35
+ 'Actor Name',
36
+ 'Actor Email',
37
+ 'Action',
38
+ 'Entity Type',
39
+ 'Entity ID',
40
+ 'Reason',
41
+ 'IP Address',
42
+ ];
43
+
44
+ const csvRows = [headers.join(',')];
45
+
46
+ for (const log of rows) {
47
+ const timestamp = log.timestamp
48
+ ? new Date(log.timestamp).toISOString()
49
+ : '';
50
+ const actorName = log.actorUser?.fullName || 'System';
51
+ const actorEmail = log.actorUser?.email || '';
52
+ const action = log.actionType || '';
53
+ const entityType = log.entityType || '';
54
+ const entityId = log.entityId || '';
55
+ const reason = log.changeReason || '';
56
+ const ipAddress = log.ipAddress || '';
57
+
58
+ const rowValues = [
59
+ escapeCsvField(timestamp),
60
+ escapeCsvField(actorName),
61
+ escapeCsvField(actorEmail),
62
+ escapeCsvField(action),
63
+ escapeCsvField(entityType),
64
+ escapeCsvField(entityId),
65
+ escapeCsvField(reason),
66
+ escapeCsvField(ipAddress),
67
+ ];
68
+
69
+ csvRows.push(rowValues.join(','));
70
+ }
71
+
72
+ const csvContent = csvRows.join('\r\n');
73
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
74
+ const url = URL.createObjectURL(blob);
75
+ const link = document.createElement('a');
76
+
77
+ const timestampSlug = new Date().toISOString().replace(/[:.]/g, '-');
78
+ link.setAttribute('href', url);
79
+ link.setAttribute('download', `audit-logs-${timestampSlug}.csv`);
80
+ document.body.appendChild(link);
81
+ link.click();
82
+ document.body.removeChild(link);
83
+ URL.revokeObjectURL(url);
84
+ } catch (error) {
85
+ console.error('Audit log export error:', error);
86
+ alert('Failed to export audit logs. Please try again.');
87
+ } finally {
88
+ setIsExporting(false);
89
+ }
90
+ };
91
+
92
+ return (
93
+ <Button
94
+ variant="outline"
95
+ size="sm"
96
+ onClick={handleExport}
97
+ disabled={isExporting}
98
+ className="h-9 gap-1.5 font-medium"
99
+ >
100
+ {isExporting ? (
101
+ <>
102
+ <Loader2 className="h-4 w-4 animate-spin" />
103
+ <span>Exporting...</span>
104
+ </>
105
+ ) : (
106
+ <>
107
+ <Download className="h-4 w-4" />
108
+ <span>Export CSV</span>
109
+ </>
110
+ )}
111
+ </Button>
112
+ );
113
+ }
@@ -0,0 +1,255 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback, useEffect, useTransition } from 'react';
4
+ import { useRouter, useSearchParams } from 'next/navigation';
5
+ import { Input } from '@/components/ui/input';
6
+ import { Button } from '@/components/ui/button';
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '@/components/ui/select';
14
+ import { Badge } from '@/components/ui/badge';
15
+ import { Card, CardContent } from '@/components/ui/card';
16
+ import { Search, X, Filter, RotateCcw } from 'lucide-react';
17
+
18
+ export interface AuditLogFiltersProps {
19
+ entityTypes: string[];
20
+ actors: { id: string; fullName: string; email: string }[];
21
+ actionTypes?: string[];
22
+ }
23
+
24
+ const DEFAULT_ACTION_TYPES = [
25
+ 'CREATE',
26
+ 'UPDATE',
27
+ 'DEACTIVATE',
28
+ 'ARCHIVE',
29
+ 'TRANSFER',
30
+ 'PURGE',
31
+ 'SIGN',
32
+ 'POLICY_CHANGE',
33
+ 'LOGIN',
34
+ 'LOGOUT',
35
+ 'FAILED_LOGIN',
36
+ ];
37
+
38
+ /**
39
+ * Filter controls for the Audit Log management dashboard.
40
+ * Interacts with URL search params to drive server-side filtering.
41
+ */
42
+ export function AuditLogFilters({
43
+ entityTypes,
44
+ actors,
45
+ actionTypes = DEFAULT_ACTION_TYPES,
46
+ }: AuditLogFiltersProps) {
47
+ const router = useRouter();
48
+ const searchParams = useSearchParams();
49
+ const [isPending, startTransition] = useTransition();
50
+
51
+ const currentSearch = searchParams.get('search') || '';
52
+ const currentEntityType = searchParams.get('entityType') || 'all';
53
+ const currentActionType = searchParams.get('actionType') || 'all';
54
+ const currentActorId = searchParams.get('actorId') || 'all';
55
+ const currentStartDate = searchParams.get('startDate') || '';
56
+ const currentEndDate = searchParams.get('endDate') || '';
57
+
58
+ const [searchInput, setSearchInput] = useState(currentSearch);
59
+
60
+ // Sync internal search state when search param changes externally
61
+ useEffect(() => {
62
+ setSearchInput(currentSearch);
63
+ }, [currentSearch]);
64
+
65
+ const updateParam = useCallback(
66
+ (key: string, value: string | null) => {
67
+ const params = new URLSearchParams(searchParams.toString());
68
+ if (value && value !== 'all') {
69
+ params.set(key, value);
70
+ } else {
71
+ params.delete(key);
72
+ }
73
+ // Reset to page 1 whenever filters change
74
+ params.delete('page');
75
+
76
+ startTransition(() => {
77
+ router.push(`?${params.toString()}`);
78
+ });
79
+ },
80
+ [router, searchParams]
81
+ );
82
+
83
+ // Debounced search input handler (300ms)
84
+ useEffect(() => {
85
+ if (searchInput === currentSearch) return;
86
+
87
+ const timer = setTimeout(() => {
88
+ updateParam('search', searchInput.trim() || null);
89
+ }, 300);
90
+
91
+ return () => clearTimeout(timer);
92
+ }, [searchInput, currentSearch, updateParam]);
93
+
94
+ const handleClearFilters = () => {
95
+ setSearchInput('');
96
+ startTransition(() => {
97
+ router.push(window.location.pathname);
98
+ });
99
+ };
100
+
101
+ const activeFilterCount = [
102
+ currentSearch,
103
+ currentEntityType !== 'all' ? currentEntityType : '',
104
+ currentActionType !== 'all' ? currentActionType : '',
105
+ currentActorId !== 'all' ? currentActorId : '',
106
+ currentStartDate,
107
+ currentEndDate,
108
+ ].filter(Boolean).length;
109
+
110
+ return (
111
+ <Card className="border shadow-sm">
112
+ <CardContent className="p-4 space-y-4">
113
+ {/* Header / Filter Status */}
114
+ <div className="flex flex-wrap items-center justify-between gap-2 border-b pb-3">
115
+ <div className="flex items-center gap-2">
116
+ <Filter className="h-4 w-4 text-muted-foreground" />
117
+ <span className="text-sm font-medium">Filter Audit Records</span>
118
+ {activeFilterCount > 0 && (
119
+ <Badge variant="secondary" className="px-2 py-0.5 text-xs font-semibold">
120
+ {activeFilterCount} active
121
+ </Badge>
122
+ )}
123
+ {isPending && (
124
+ <span className="text-xs text-muted-foreground animate-pulse">
125
+ Updating...
126
+ </span>
127
+ )}
128
+ </div>
129
+ {activeFilterCount > 0 && (
130
+ <Button
131
+ variant="ghost"
132
+ size="sm"
133
+ onClick={handleClearFilters}
134
+ className="h-8 px-2 text-xs text-muted-foreground hover:text-foreground"
135
+ >
136
+ <RotateCcw className="h-3.5 w-3.5 mr-1" />
137
+ Clear Filters
138
+ </Button>
139
+ )}
140
+ </div>
141
+
142
+ {/* Filter Inputs Grid */}
143
+ <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
144
+ {/* Text Search */}
145
+ <div className="relative lg:col-span-2">
146
+ <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground pointer-events-none" />
147
+ <Input
148
+ type="text"
149
+ placeholder="Search entity, reason, IP..."
150
+ value={searchInput}
151
+ onChange={(e) => setSearchInput(e.target.value)}
152
+ className="pl-8 pr-8 h-9 text-sm"
153
+ />
154
+ {searchInput && (
155
+ <button
156
+ type="button"
157
+ onClick={() => {
158
+ setSearchInput('');
159
+ updateParam('search', null);
160
+ }}
161
+ className="absolute right-2.5 top-2.5 text-muted-foreground hover:text-foreground"
162
+ aria-label="Clear search"
163
+ >
164
+ <X className="h-4 w-4" />
165
+ </button>
166
+ )}
167
+ </div>
168
+
169
+ {/* Action Type Dropdown */}
170
+ <div>
171
+ <Select
172
+ value={currentActionType}
173
+ onValueChange={(val) => updateParam('actionType', val)}
174
+ >
175
+ <SelectTrigger className="h-9 text-sm">
176
+ <SelectValue placeholder="Action Type" />
177
+ </SelectTrigger>
178
+ <SelectContent>
179
+ <SelectItem value="all">All Actions</SelectItem>
180
+ {actionTypes.map((action) => (
181
+ <SelectItem key={action} value={action}>
182
+ {action}
183
+ </SelectItem>
184
+ ))}
185
+ </SelectContent>
186
+ </Select>
187
+ </div>
188
+
189
+ {/* Entity Type Dropdown */}
190
+ <div>
191
+ <Select
192
+ value={currentEntityType}
193
+ onValueChange={(val) => updateParam('entityType', val)}
194
+ >
195
+ <SelectTrigger className="h-9 text-sm">
196
+ <SelectValue placeholder="Entity Type" />
197
+ </SelectTrigger>
198
+ <SelectContent>
199
+ <SelectItem value="all">All Entities</SelectItem>
200
+ {entityTypes.map((type) => (
201
+ <SelectItem key={type} value={type}>
202
+ {type}
203
+ </SelectItem>
204
+ ))}
205
+ </SelectContent>
206
+ </Select>
207
+ </div>
208
+
209
+ {/* Actor Dropdown */}
210
+ <div>
211
+ <Select
212
+ value={currentActorId}
213
+ onValueChange={(val) => updateParam('actorId', val)}
214
+ >
215
+ <SelectTrigger className="h-9 text-sm truncate">
216
+ <SelectValue placeholder="All Users" />
217
+ </SelectTrigger>
218
+ <SelectContent className="max-h-60">
219
+ <SelectItem value="all">All Actors</SelectItem>
220
+ {actors.map((actor) => (
221
+ <SelectItem key={actor.id} value={actor.id}>
222
+ {actor.fullName || actor.email}
223
+ </SelectItem>
224
+ ))}
225
+ </SelectContent>
226
+ </Select>
227
+ </div>
228
+
229
+ {/* Date Range Inputs */}
230
+ <div className="flex items-center gap-1.5 sm:col-span-2 md:col-span-3 lg:col-span-1">
231
+ <div className="flex-1">
232
+ <Input
233
+ type="date"
234
+ value={currentStartDate}
235
+ onChange={(e) => updateParam('startDate', e.target.value || null)}
236
+ className="h-9 text-xs px-2"
237
+ title="Start Date"
238
+ />
239
+ </div>
240
+ <span className="text-xs text-muted-foreground">to</span>
241
+ <div className="flex-1">
242
+ <Input
243
+ type="date"
244
+ value={currentEndDate}
245
+ onChange={(e) => updateParam('endDate', e.target.value || null)}
246
+ className="h-9 text-xs px-2"
247
+ title="End Date"
248
+ />
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </CardContent>
253
+ </Card>
254
+ );
255
+ }