@omersx/bemms 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +203 -2
  3. package/drizzle.config.ts +11 -0
  4. package/next.config.ts +51 -0
  5. package/package.json +99 -3
  6. package/postcss.config.mjs +7 -0
  7. package/public/hospital_demo_devices.csv +32 -0
  8. package/public/icons/icon-192.png +0 -0
  9. package/public/icons/icon-512.png +0 -0
  10. package/public/icons/icon-maskable-512.png +0 -0
  11. package/public/manifest.webmanifest +31 -0
  12. package/public/sw.js +177 -0
  13. package/src/app/(auth)/layout.tsx +20 -0
  14. package/src/app/(auth)/login/page.tsx +135 -0
  15. package/src/app/(dashboard)/admin/audit-logs/page.tsx +178 -0
  16. package/src/app/(dashboard)/admin/data/page.tsx +37 -0
  17. package/src/app/(dashboard)/admin/departments/[deptId]/edit/page.tsx +206 -0
  18. package/src/app/(dashboard)/admin/departments/[deptId]/page.tsx +71 -0
  19. package/src/app/(dashboard)/admin/departments/new/page.tsx +6 -0
  20. package/src/app/(dashboard)/admin/departments/page.tsx +6 -0
  21. package/src/app/(dashboard)/admin/general/page.tsx +374 -0
  22. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/edit/page.tsx +304 -0
  23. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/page.tsx +94 -0
  24. package/src/app/(dashboard)/admin/hospitals/new/page.tsx +302 -0
  25. package/src/app/(dashboard)/admin/hospitals/page.tsx +6 -0
  26. package/src/app/(dashboard)/admin/layout.tsx +49 -0
  27. package/src/app/(dashboard)/admin/organizations/[orgId]/edit/page.tsx +172 -0
  28. package/src/app/(dashboard)/admin/organizations/[orgId]/page.tsx +82 -0
  29. package/src/app/(dashboard)/admin/organizations/new/page.tsx +150 -0
  30. package/src/app/(dashboard)/admin/organizations/page.tsx +6 -0
  31. package/src/app/(dashboard)/admin/page.tsx +258 -0
  32. package/src/app/(dashboard)/admin/roles/page.tsx +153 -0
  33. package/src/app/(dashboard)/admin/settings/page.tsx +69 -0
  34. package/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +153 -0
  35. package/src/app/(dashboard)/admin/users/[userId]/page.tsx +317 -0
  36. package/src/app/(dashboard)/admin/users/invite/page.tsx +3 -0
  37. package/src/app/(dashboard)/admin/users/new/page.tsx +5 -0
  38. package/src/app/(dashboard)/admin/users/page.tsx +23 -0
  39. package/src/app/(dashboard)/dashboard/page.tsx +462 -0
  40. package/src/app/(dashboard)/departments/[deptId]/page.tsx +39 -0
  41. package/src/app/(dashboard)/departments/new/page.tsx +330 -0
  42. package/src/app/(dashboard)/departments/page.tsx +52 -0
  43. package/src/app/(dashboard)/devices/[id]/edit/page.tsx +131 -0
  44. package/src/app/(dashboard)/devices/[id]/page.tsx +408 -0
  45. package/src/app/(dashboard)/devices/[id]/qr/page.tsx +54 -0
  46. package/src/app/(dashboard)/devices/new/page.tsx +382 -0
  47. package/src/app/(dashboard)/devices/page.tsx +80 -0
  48. package/src/app/(dashboard)/engineering-team/page.tsx +41 -0
  49. package/src/app/(dashboard)/layout.tsx +43 -0
  50. package/src/app/(dashboard)/maintenance/checklists/page.tsx +57 -0
  51. package/src/app/(dashboard)/maintenance/page.tsx +231 -0
  52. package/src/app/(dashboard)/maintenance/plans/page.tsx +142 -0
  53. package/src/app/(dashboard)/maintenance/tasks/[id]/page.tsx +997 -0
  54. package/src/app/(dashboard)/maintenance/tasks/page.tsx +119 -0
  55. package/src/app/(dashboard)/more/page.tsx +310 -0
  56. package/src/app/(dashboard)/my-department/page.tsx +151 -0
  57. package/src/app/(dashboard)/notifications/notification-list.tsx +103 -0
  58. package/src/app/(dashboard)/notifications/page.tsx +59 -0
  59. package/src/app/(dashboard)/profile/page.tsx +27 -0
  60. package/src/app/(dashboard)/reports/costs/page.tsx +127 -0
  61. package/src/app/(dashboard)/reports/history/page.tsx +105 -0
  62. package/src/app/(dashboard)/reports/inventory/page.tsx +116 -0
  63. package/src/app/(dashboard)/reports/maintenance/page.tsx +136 -0
  64. package/src/app/(dashboard)/reports/page.tsx +120 -0
  65. package/src/app/(dashboard)/reports/tickets/page.tsx +139 -0
  66. package/src/app/(dashboard)/reports/workload/page.tsx +135 -0
  67. package/src/app/(dashboard)/rounds/[id]/page.tsx +197 -0
  68. package/src/app/(dashboard)/rounds/history/page.tsx +82 -0
  69. package/src/app/(dashboard)/rounds/page.tsx +107 -0
  70. package/src/app/(dashboard)/scan/[code]/page.tsx +154 -0
  71. package/src/app/(dashboard)/scan/page.tsx +73 -0
  72. package/src/app/(dashboard)/team/page.tsx +42 -0
  73. package/src/app/(dashboard)/tickets/[id]/page.tsx +465 -0
  74. package/src/app/(dashboard)/tickets/create/page.tsx +722 -0
  75. package/src/app/(dashboard)/tickets/page.tsx +55 -0
  76. package/src/app/(dashboard)/tickets/triage/page.tsx +161 -0
  77. package/src/app/api/admin/data/export/route.ts +316 -0
  78. package/src/app/api/auth/[...nextauth]/route.ts +3 -0
  79. package/src/app/api/health/route.ts +46 -0
  80. package/src/app/api/notifications/sse/route.ts +91 -0
  81. package/src/app/api/notifications/subscribe/route.ts +54 -0
  82. package/src/app/api/notifications/vapid-key/route.ts +7 -0
  83. package/src/app/api/reports/export/route.ts +102 -0
  84. package/src/app/api/tickets/[id]/attachments/route.ts +129 -0
  85. package/src/app/api/tickets/[id]/live-sync/route.ts +52 -0
  86. package/src/app/error.tsx +54 -0
  87. package/src/app/globals.css +89 -0
  88. package/src/app/layout.tsx +59 -0
  89. package/src/app/not-found.tsx +32 -0
  90. package/src/app/page.tsx +5 -0
  91. package/src/components/admin/add-department-user-dialog.tsx +759 -0
  92. package/src/components/admin/add-engineer-dialog.tsx +773 -0
  93. package/src/components/admin/add-user-type-dialog.tsx +89 -0
  94. package/src/components/admin/audit-log-export.tsx +113 -0
  95. package/src/components/admin/audit-log-filters.tsx +255 -0
  96. package/src/components/admin/audit-log-table.tsx +383 -0
  97. package/src/components/admin/data-management/danger-zone.tsx +301 -0
  98. package/src/components/admin/data-management/data-management-tabs.tsx +61 -0
  99. package/src/components/admin/data-management/export-center.tsx +301 -0
  100. package/src/components/admin/data-management/import-wizard.tsx +558 -0
  101. package/src/components/admin/settings-forms/features-form.tsx +115 -0
  102. package/src/components/admin/settings-forms/health-panel.tsx +182 -0
  103. package/src/components/admin/settings-forms/localization-form.tsx +203 -0
  104. package/src/components/admin/settings-forms/retention-form.tsx +164 -0
  105. package/src/components/admin/settings-forms/security-form.tsx +186 -0
  106. package/src/components/admin/system-settings-tabs.tsx +88 -0
  107. package/src/components/admin/user-role-dialog.tsx +293 -0
  108. package/src/components/admin/user-status-dialog.tsx +166 -0
  109. package/src/components/admin/users-page-client.tsx +514 -0
  110. package/src/components/dashboard/action-buttons.tsx +93 -0
  111. package/src/components/dashboard/admin-alerts.tsx +86 -0
  112. package/src/components/dashboard/availability-alerts.tsx +65 -0
  113. package/src/components/dashboard/maintenance-due-list.tsx +87 -0
  114. package/src/components/dashboard/round-widget.tsx +110 -0
  115. package/src/components/dashboard/stat-card.tsx +38 -0
  116. package/src/components/dashboard/ticket-list-compact.tsx +86 -0
  117. package/src/components/departments/department-cards-view.tsx +244 -0
  118. package/src/components/departments/department-detail-view.tsx +467 -0
  119. package/src/components/departments/department-visuals.tsx +190 -0
  120. package/src/components/departments/my-department-view.tsx +333 -0
  121. package/src/components/devices/change-status-dialog.tsx +146 -0
  122. package/src/components/devices/device-action-buttons.tsx +125 -0
  123. package/src/components/devices/device-availability-banner.tsx +59 -0
  124. package/src/components/devices/device-badges.tsx +55 -0
  125. package/src/components/devices/device-documents-tab.tsx +93 -0
  126. package/src/components/devices/device-maintenance-tab.tsx +113 -0
  127. package/src/components/devices/device-mini-qr.tsx +109 -0
  128. package/src/components/devices/device-qr-card.tsx +187 -0
  129. package/src/components/devices/device-signatures-tab.tsx +607 -0
  130. package/src/components/devices/device-tickets-tab.tsx +139 -0
  131. package/src/components/devices/device-timelines.tsx +124 -0
  132. package/src/components/devices/qr-camera-scanner.tsx +167 -0
  133. package/src/components/devices/qr-code-display.tsx +139 -0
  134. package/src/components/devices/quick-add-category-dialog.tsx +135 -0
  135. package/src/components/devices/quick-add-manufacturer-dialog.tsx +212 -0
  136. package/src/components/devices/transfer-device-dialog.tsx +190 -0
  137. package/src/components/layout/header.tsx +80 -0
  138. package/src/components/layout/mobile-nav.tsx +91 -0
  139. package/src/components/layout/more-item-badge.tsx +15 -0
  140. package/src/components/layout/nav-badge.tsx +72 -0
  141. package/src/components/layout/sidebar.tsx +297 -0
  142. package/src/components/maintenance/maintenance-badges.tsx +579 -0
  143. package/src/components/maintenance/maintenance-nav.tsx +107 -0
  144. package/src/components/maintenance/parts-costs-manager.tsx +909 -0
  145. package/src/components/maintenance/signature-card.tsx +413 -0
  146. package/src/components/maintenance/task-action-bar.tsx +955 -0
  147. package/src/components/notifications/notification-bell.tsx +367 -0
  148. package/src/components/notifications/notification-item.tsx +93 -0
  149. package/src/components/notifications/notification-preferences.tsx +182 -0
  150. package/src/components/profile/user-profile-view.tsx +626 -0
  151. package/src/components/providers/nav-counts-provider.tsx +235 -0
  152. package/src/components/providers/session-provider.tsx +7 -0
  153. package/src/components/providers/theme-provider.tsx +124 -0
  154. package/src/components/pwa/install-prompt.tsx +82 -0
  155. package/src/components/pwa/sw-register.tsx +43 -0
  156. package/src/components/reports/export-button.tsx +62 -0
  157. package/src/components/reports/filter-chips.tsx +47 -0
  158. package/src/components/reports/summary-cards.tsx +40 -0
  159. package/src/components/reports/transparency-header.tsx +54 -0
  160. package/src/components/rounds/department-visit-sheet.tsx +199 -0
  161. package/src/components/rounds/mention-textarea.tsx +187 -0
  162. package/src/components/rounds/round-calendar.tsx +233 -0
  163. package/src/components/rounds/round-department-list.tsx +281 -0
  164. package/src/components/rounds/round-history-client.tsx +218 -0
  165. package/src/components/rounds/start-round-card.tsx +184 -0
  166. package/src/components/shared/confirm-dialog.tsx +67 -0
  167. package/src/components/shared/data-table.tsx +104 -0
  168. package/src/components/shared/page-header.tsx +20 -0
  169. package/src/components/shared/sign-out-button.tsx +52 -0
  170. package/src/components/shared/status-badge.tsx +42 -0
  171. package/src/components/shared/table-search.tsx +55 -0
  172. package/src/components/signatures/sign-action-dialog.tsx +253 -0
  173. package/src/components/signatures/signature-history.tsx +121 -0
  174. package/src/components/signatures/signature-status-panel.tsx +164 -0
  175. package/src/components/team/department-coverage.tsx +141 -0
  176. package/src/components/team/edit-engineer-dialog.tsx +242 -0
  177. package/src/components/team/engineer-card.tsx +180 -0
  178. package/src/components/team/engineer-details-dialog.tsx +432 -0
  179. package/src/components/team/schedule-calendar.tsx +204 -0
  180. package/src/components/team/staff-engineer-team-view.tsx +368 -0
  181. package/src/components/team/team-header-stats.tsx +88 -0
  182. package/src/components/team/team-page-client.tsx +216 -0
  183. package/src/components/theme/theme-selector-card.tsx +63 -0
  184. package/src/components/theme/theme-toggle.tsx +65 -0
  185. package/src/components/tickets/staff-status-label.tsx +58 -0
  186. package/src/components/tickets/staff-ticket-card.tsx +109 -0
  187. package/src/components/tickets/staff-ticket-progress-tracker.tsx +405 -0
  188. package/src/components/tickets/staff-tickets-view.tsx +182 -0
  189. package/src/components/tickets/ticket-action-buttons.tsx +873 -0
  190. package/src/components/tickets/ticket-auto-refresh.tsx +197 -0
  191. package/src/components/tickets/ticket-badges.tsx +43 -0
  192. package/src/components/tickets/ticket-card.tsx +197 -0
  193. package/src/components/tickets/ticket-cards-view.tsx +395 -0
  194. package/src/components/tickets/ticket-comment-section.tsx +684 -0
  195. package/src/components/tickets/ticket-dispatch-status.tsx +184 -0
  196. package/src/components/tickets/ticket-list-auto-refresh.tsx +55 -0
  197. package/src/components/tickets/ticket-reporter-profile.tsx +235 -0
  198. package/src/components/tickets/ticket-signatures-panel.tsx +32 -0
  199. package/src/components/tickets/ticket-timeline.tsx +131 -0
  200. package/src/components/tickets/ticket-triage-panel.tsx +124 -0
  201. package/src/components/tickets/tickets-nav.tsx +107 -0
  202. package/src/components/ui/avatar.tsx +43 -0
  203. package/src/components/ui/badge.tsx +34 -0
  204. package/src/components/ui/button.tsx +58 -0
  205. package/src/components/ui/card.tsx +77 -0
  206. package/src/components/ui/checkbox.tsx +31 -0
  207. package/src/components/ui/dialog.tsx +123 -0
  208. package/src/components/ui/dropdown-menu.tsx +126 -0
  209. package/src/components/ui/form.tsx +179 -0
  210. package/src/components/ui/input.tsx +24 -0
  211. package/src/components/ui/label.tsx +27 -0
  212. package/src/components/ui/popover.tsx +34 -0
  213. package/src/components/ui/progress.tsx +36 -0
  214. package/src/components/ui/scroll-area.tsx +47 -0
  215. package/src/components/ui/select.tsx +160 -0
  216. package/src/components/ui/separator.tsx +25 -0
  217. package/src/components/ui/sheet.tsx +131 -0
  218. package/src/components/ui/switch.tsx +30 -0
  219. package/src/components/ui/table.tsx +121 -0
  220. package/src/components/ui/tabs.tsx +62 -0
  221. package/src/components/ui/textarea.tsx +23 -0
  222. package/src/components/ui/tooltip.tsx +27 -0
  223. package/src/lib/actions/__tests__/devices.test.ts +176 -0
  224. package/src/lib/actions/__tests__/signatures.test.ts +303 -0
  225. package/src/lib/actions/__tests__/tickets.test.ts +243 -0
  226. package/src/lib/actions/audit-logs.ts +298 -0
  227. package/src/lib/actions/checklists.ts +429 -0
  228. package/src/lib/actions/dashboard.ts +358 -0
  229. package/src/lib/actions/data-management.ts +723 -0
  230. package/src/lib/actions/departments.ts +369 -0
  231. package/src/lib/actions/device-categories.ts +117 -0
  232. package/src/lib/actions/devices.ts +748 -0
  233. package/src/lib/actions/engineer-rounds.ts +973 -0
  234. package/src/lib/actions/engineers.ts +285 -0
  235. package/src/lib/actions/hospitals.ts +148 -0
  236. package/src/lib/actions/locations.ts +115 -0
  237. package/src/lib/actions/maintenance-plans.ts +316 -0
  238. package/src/lib/actions/maintenance-tasks.ts +415 -0
  239. package/src/lib/actions/manufacturers.ts +132 -0
  240. package/src/lib/actions/nav-counts.ts +144 -0
  241. package/src/lib/actions/notifications.ts +136 -0
  242. package/src/lib/actions/organizations.ts +99 -0
  243. package/src/lib/actions/qr.ts +147 -0
  244. package/src/lib/actions/report-history.ts +77 -0
  245. package/src/lib/actions/reports.ts +192 -0
  246. package/src/lib/actions/roles.ts +32 -0
  247. package/src/lib/actions/signature-auth.ts +377 -0
  248. package/src/lib/actions/signatures.ts +532 -0
  249. package/src/lib/actions/system-settings.ts +274 -0
  250. package/src/lib/actions/tickets.ts +1445 -0
  251. package/src/lib/actions/users.ts +740 -0
  252. package/src/lib/audit/index.ts +103 -0
  253. package/src/lib/auth/__tests__/rbac.test.ts +33 -0
  254. package/src/lib/auth/index.ts +338 -0
  255. package/src/lib/auth/password.ts +10 -0
  256. package/src/lib/auth/rbac.ts +103 -0
  257. package/src/lib/auth/types.ts +57 -0
  258. package/src/lib/constants/departments.ts +16 -0
  259. package/src/lib/constants/manufacturers.ts +32 -0
  260. package/src/lib/db/index.ts +23 -0
  261. package/src/lib/db/schema/audit-logs.ts +42 -0
  262. package/src/lib/db/schema/departments.ts +39 -0
  263. package/src/lib/db/schema/device-categories.ts +38 -0
  264. package/src/lib/db/schema/devices.ts +195 -0
  265. package/src/lib/db/schema/engineer-profiles.ts +63 -0
  266. package/src/lib/db/schema/engineer-rounds.ts +115 -0
  267. package/src/lib/db/schema/enums.ts +148 -0
  268. package/src/lib/db/schema/hospitals.ts +34 -0
  269. package/src/lib/db/schema/index.ts +24 -0
  270. package/src/lib/db/schema/locations.ts +46 -0
  271. package/src/lib/db/schema/maintenance-plans.ts +99 -0
  272. package/src/lib/db/schema/maintenance-records.ts +161 -0
  273. package/src/lib/db/schema/maintenance-tasks.ts +196 -0
  274. package/src/lib/db/schema/manufacturers.ts +60 -0
  275. package/src/lib/db/schema/notifications.ts +48 -0
  276. package/src/lib/db/schema/organizations.ts +24 -0
  277. package/src/lib/db/schema/report-history.ts +28 -0
  278. package/src/lib/db/schema/roles.ts +59 -0
  279. package/src/lib/db/schema/sessions.ts +46 -0
  280. package/src/lib/db/schema/signature-events.ts +48 -0
  281. package/src/lib/db/schema/signature-policies.ts +60 -0
  282. package/src/lib/db/schema/signatures.ts +87 -0
  283. package/src/lib/db/schema/tickets.ts +169 -0
  284. package/src/lib/db/schema/user-assignments.ts +85 -0
  285. package/src/lib/db/schema/users.ts +39 -0
  286. package/src/lib/db/seed.ts +131 -0
  287. package/src/lib/logger/__tests__/logger.test.ts +154 -0
  288. package/src/lib/logger/formatters.ts +159 -0
  289. package/src/lib/logger/index.ts +184 -0
  290. package/src/lib/logger/types.ts +48 -0
  291. package/src/lib/notifications/create.ts +71 -0
  292. package/src/lib/notifications/dispatcher.ts +490 -0
  293. package/src/lib/notifications/index.ts +7 -0
  294. package/src/lib/notifications/push-sender.ts +128 -0
  295. package/src/lib/notifications/push.ts +64 -0
  296. package/src/lib/notifications/triggers.ts +166 -0
  297. package/src/lib/utils/__tests__/crypto.test.ts +30 -0
  298. package/src/lib/utils/__tests__/export.test.ts +55 -0
  299. package/src/lib/utils/__tests__/nav-badge.test.ts +147 -0
  300. package/src/lib/utils/__tests__/staff-status.test.ts +50 -0
  301. package/src/lib/utils/attestations.ts +19 -0
  302. package/src/lib/utils/crypto.ts +18 -0
  303. package/src/lib/utils/csv-parser.ts +181 -0
  304. package/src/lib/utils/export.ts +45 -0
  305. package/src/lib/utils/nav-badge.ts +225 -0
  306. package/src/lib/utils/sound.ts +152 -0
  307. package/src/lib/utils/staff-status.ts +115 -0
  308. package/src/lib/utils.ts +6 -0
  309. package/src/lib/validators/__tests__/auth.test.ts +127 -0
  310. package/src/lib/validators/__tests__/data-management.test.ts +182 -0
  311. package/src/lib/validators/__tests__/devices-profile.test.ts +129 -0
  312. package/src/lib/validators/__tests__/devices.test.ts +98 -0
  313. package/src/lib/validators/__tests__/engineer-rounds.test.ts +170 -0
  314. package/src/lib/validators/__tests__/maintenance.test.ts +178 -0
  315. package/src/lib/validators/__tests__/signatures.test.ts +154 -0
  316. package/src/lib/validators/__tests__/system-settings.test.ts +119 -0
  317. package/src/lib/validators/__tests__/tickets.test.ts +176 -0
  318. package/src/lib/validators/__tests__/users.test.ts +92 -0
  319. package/src/lib/validators/auth.ts +32 -0
  320. package/src/lib/validators/data-management.ts +67 -0
  321. package/src/lib/validators/departments.ts +14 -0
  322. package/src/lib/validators/devices.ts +94 -0
  323. package/src/lib/validators/engineer-rounds.ts +70 -0
  324. package/src/lib/validators/hospitals.ts +17 -0
  325. package/src/lib/validators/locations.ts +15 -0
  326. package/src/lib/validators/maintenance.ts +211 -0
  327. package/src/lib/validators/organizations.ts +11 -0
  328. package/src/lib/validators/reports.ts +31 -0
  329. package/src/lib/validators/signatures.ts +49 -0
  330. package/src/lib/validators/system-settings.ts +55 -0
  331. package/src/lib/validators/tickets.ts +125 -0
  332. package/src/lib/validators/users.ts +43 -0
  333. package/src/middleware.ts +45 -0
  334. package/tailwind.config.ts +76 -0
  335. package/tsconfig.json +28 -0
@@ -0,0 +1,330 @@
1
+ "use client"
2
+
3
+ import { useState, useEffect } from "react"
4
+ import { useRouter } from "next/navigation"
5
+ import { createDepartment } from "@/lib/actions/departments"
6
+ import { getOrganizations } from "@/lib/actions/organizations"
7
+ import { getHospitals } from "@/lib/actions/hospitals"
8
+ import { getUsers } from "@/lib/actions/users"
9
+ import { PageHeader } from "@/components/shared/page-header"
10
+ import { Button } from "@/components/ui/button"
11
+ import { Input } from "@/components/ui/input"
12
+ import { Label } from "@/components/ui/label"
13
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
14
+ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
15
+ import { ArrowLeft, Building2, Loader2, Check } from "lucide-react"
16
+ import Link from "next/link"
17
+ import { toast } from "sonner"
18
+
19
+ const DEPARTMENT_TYPES = [
20
+ { value: "Clinical", label: "Clinical" },
21
+ { value: "Surgical", label: "Surgical" },
22
+ { value: "Intensive Care", label: "Intensive Care (ICU)" },
23
+ { value: "Emergency", label: "Emergency" },
24
+ { value: "Diagnostic & Imaging", label: "Diagnostic & Imaging" },
25
+ { value: "Laboratory", label: "Laboratory" },
26
+ { value: "Pharmacy", label: "Pharmacy" },
27
+ { value: "Biomedical Engineering", label: "Biomedical Engineering" },
28
+ { value: "Support Services", label: "Support Services" },
29
+ { value: "Administration", label: "Administration" },
30
+ { value: "Other", label: "Other" },
31
+ ]
32
+
33
+ export default function NewDepartmentPage() {
34
+ const router = useRouter()
35
+ const [loading, setLoading] = useState(false)
36
+ const [organizations, setOrganizations] = useState<any[]>([])
37
+ const [hospitals, setHospitals] = useState<any[]>([])
38
+ const [users, setUsers] = useState<any[]>([])
39
+
40
+ const [organizationId, setOrganizationId] = useState("")
41
+ const [hospitalId, setHospitalId] = useState("")
42
+ const [name, setName] = useState("")
43
+ const [code, setCode] = useState("")
44
+ const [departmentType, setDepartmentType] = useState("")
45
+ const [managerUserId, setManagerUserId] = useState<string>("none")
46
+
47
+ // Load organizations and users on mount
48
+ useEffect(() => {
49
+ async function loadInitialData() {
50
+ try {
51
+ const [orgRes, usersRes] = await Promise.all([
52
+ getOrganizations(),
53
+ getUsers(),
54
+ ])
55
+ if (orgRes?.success && Array.isArray(orgRes.data)) {
56
+ setOrganizations(orgRes.data)
57
+ // If only one org, auto-select it
58
+ if (orgRes.data.length === 1) {
59
+ setOrganizationId(orgRes.data[0].id)
60
+ }
61
+ }
62
+ if (usersRes?.success && Array.isArray(usersRes.data)) {
63
+ setUsers(usersRes.data)
64
+ }
65
+ } catch (err) {
66
+ console.error("Failed to load initial data:", err)
67
+ }
68
+ }
69
+ loadInitialData()
70
+ }, [])
71
+
72
+ // Load hospitals when organization is selected
73
+ useEffect(() => {
74
+ async function loadHospitals() {
75
+ if (organizationId) {
76
+ try {
77
+ const res = await getHospitals(organizationId)
78
+ if (res?.success && Array.isArray(res.data)) {
79
+ setHospitals(res.data)
80
+ // If only one hospital, auto-select it
81
+ if (res.data.length === 1) {
82
+ setHospitalId(res.data[0].id)
83
+ } else {
84
+ setHospitalId("")
85
+ }
86
+ }
87
+ } catch (err) {
88
+ console.error("Failed to load hospitals:", err)
89
+ }
90
+ } else {
91
+ setHospitals([])
92
+ setHospitalId("")
93
+ }
94
+ }
95
+ loadHospitals()
96
+ }, [organizationId])
97
+
98
+ // Auto-generate code from department name if code is empty
99
+ const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
100
+ const val = e.target.value
101
+ setName(val)
102
+ if (!code || code === name.slice(0, 4).toUpperCase()) {
103
+ setCode(val.replace(/[^a-zA-Z0-9]/g, "").slice(0, 6).toUpperCase())
104
+ }
105
+ }
106
+
107
+ const handleSubmit = async (e: React.FormEvent) => {
108
+ e.preventDefault()
109
+
110
+ if (!organizationId) {
111
+ toast.error("Please select an organization")
112
+ return
113
+ }
114
+ if (!hospitalId) {
115
+ toast.error("Please select a hospital")
116
+ return
117
+ }
118
+ if (!name.trim()) {
119
+ toast.error("Please enter a department name")
120
+ return
121
+ }
122
+ if (!code.trim()) {
123
+ toast.error("Please enter a department code")
124
+ return
125
+ }
126
+
127
+ setLoading(true)
128
+ try {
129
+ const res = await createDepartment({
130
+ organizationId,
131
+ hospitalId,
132
+ name: name.trim(),
133
+ code: code.trim().toUpperCase(),
134
+ departmentType: departmentType || undefined,
135
+ managerUserId: managerUserId === "none" ? undefined : managerUserId,
136
+ status: "active",
137
+ })
138
+
139
+ if (res.success) {
140
+ toast.success("Department created successfully!")
141
+ router.push("/departments")
142
+ router.refresh()
143
+ } else {
144
+ toast.error((res as any).error || "Failed to create department")
145
+ }
146
+ } catch (err: any) {
147
+ toast.error(err.message || "An unexpected error occurred")
148
+ } finally {
149
+ setLoading(false)
150
+ }
151
+ }
152
+
153
+ return (
154
+ <div className="space-y-6 max-w-2xl mx-auto pb-12">
155
+ <div className="flex items-center gap-2">
156
+ <Link
157
+ href="/departments"
158
+ className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
159
+ >
160
+ <ArrowLeft className="w-4 h-4 mr-1.5" />
161
+ Back to Departments
162
+ </Link>
163
+ </div>
164
+
165
+ <PageHeader
166
+ title="Add Department"
167
+ description="Create a new clinical or operational hospital department"
168
+ />
169
+
170
+ <Card className="shadow-xs border">
171
+ <CardHeader className="pb-4">
172
+ <CardTitle className="text-base flex items-center gap-2">
173
+ <Building2 className="w-5 h-5 text-primary" />
174
+ Department Details
175
+ </CardTitle>
176
+ <CardDescription>
177
+ Specify the organization, hospital location, and operational category for the department.
178
+ </CardDescription>
179
+ </CardHeader>
180
+ <CardContent>
181
+ <form onSubmit={handleSubmit} className="space-y-5">
182
+ {/* Organization Select */}
183
+ <div className="space-y-2">
184
+ <Label htmlFor="organization">
185
+ Organization <span className="text-destructive">*</span>
186
+ </Label>
187
+ <Select value={organizationId} onValueChange={setOrganizationId}>
188
+ <SelectTrigger id="organization">
189
+ <SelectValue placeholder="Select Organization" />
190
+ </SelectTrigger>
191
+ <SelectContent>
192
+ {organizations.map((org) => (
193
+ <SelectItem key={org.id} value={org.id}>
194
+ {org.name}
195
+ </SelectItem>
196
+ ))}
197
+ </SelectContent>
198
+ </Select>
199
+ </div>
200
+
201
+ {/* Hospital Select (cascading) */}
202
+ <div className="space-y-2">
203
+ <Label htmlFor="hospital">
204
+ Hospital <span className="text-destructive">*</span>
205
+ </Label>
206
+ <Select
207
+ value={hospitalId}
208
+ onValueChange={setHospitalId}
209
+ disabled={!organizationId || hospitals.length === 0}
210
+ >
211
+ <SelectTrigger id="hospital">
212
+ <SelectValue
213
+ placeholder={
214
+ !organizationId
215
+ ? "Select an organization first"
216
+ : hospitals.length === 0
217
+ ? "No hospitals available"
218
+ : "Select Hospital"
219
+ }
220
+ />
221
+ </SelectTrigger>
222
+ <SelectContent>
223
+ {hospitals.map((h) => (
224
+ <SelectItem key={h.id} value={h.id}>
225
+ {h.name}
226
+ </SelectItem>
227
+ ))}
228
+ </SelectContent>
229
+ </Select>
230
+ </div>
231
+
232
+ {/* Department Name */}
233
+ <div className="space-y-2">
234
+ <Label htmlFor="name">
235
+ Department Name <span className="text-destructive">*</span>
236
+ </Label>
237
+ <Input
238
+ id="name"
239
+ value={name}
240
+ onChange={handleNameChange}
241
+ placeholder="e.g. Intensive Care Unit, Cardiology, Radiology"
242
+ required
243
+ />
244
+ </div>
245
+
246
+ {/* Department Code */}
247
+ <div className="space-y-2">
248
+ <Label htmlFor="code">
249
+ Department Code <span className="text-destructive">*</span>
250
+ </Label>
251
+ <Input
252
+ id="code"
253
+ value={code}
254
+ onChange={(e) => setCode(e.target.value.toUpperCase())}
255
+ placeholder="e.g. ICU, CARD, RAD"
256
+ className="font-mono uppercase"
257
+ maxLength={20}
258
+ required
259
+ />
260
+ <p className="text-xs text-muted-foreground">
261
+ Short unique identifier for asset codes and reporting (2-20 characters).
262
+ </p>
263
+ </div>
264
+
265
+ {/* Department Type */}
266
+ <div className="space-y-2">
267
+ <Label htmlFor="type">Department Type</Label>
268
+ <Select value={departmentType} onValueChange={setDepartmentType}>
269
+ <SelectTrigger id="type">
270
+ <SelectValue placeholder="Select Department Type (optional)" />
271
+ </SelectTrigger>
272
+ <SelectContent>
273
+ {DEPARTMENT_TYPES.map((type) => (
274
+ <SelectItem key={type.value} value={type.value}>
275
+ {type.label}
276
+ </SelectItem>
277
+ ))}
278
+ </SelectContent>
279
+ </Select>
280
+ </div>
281
+
282
+ {/* Department Manager */}
283
+ <div className="space-y-2">
284
+ <Label htmlFor="manager">Department Manager (Head of Department)</Label>
285
+ <Select value={managerUserId} onValueChange={setManagerUserId}>
286
+ <SelectTrigger id="manager">
287
+ <SelectValue placeholder="Assign a Department Manager (optional)" />
288
+ </SelectTrigger>
289
+ <SelectContent>
290
+ <SelectItem value="none">-- No Manager (Unassigned) --</SelectItem>
291
+ {users.map((u) => (
292
+ <SelectItem key={u.id} value={u.id}>
293
+ {u.fullName || u.email} ({u.email})
294
+ </SelectItem>
295
+ ))}
296
+ </SelectContent>
297
+ </Select>
298
+ <p className="text-xs text-muted-foreground">
299
+ Optional. You can assign or change the department manager anytime later.
300
+ </p>
301
+ </div>
302
+
303
+ {/* Form Actions */}
304
+ <div className="flex items-center justify-end gap-3 pt-4 border-t">
305
+ <Link href="/departments">
306
+ <Button type="button" variant="outline" disabled={loading}>
307
+ Cancel
308
+ </Button>
309
+ </Link>
310
+ <Button type="submit" disabled={loading} className="gap-2">
311
+ {loading ? (
312
+ <>
313
+ <Loader2 className="w-4 h-4 animate-spin" />
314
+ Creating...
315
+ </>
316
+ ) : (
317
+ <>
318
+ <Check className="w-4 h-4" />
319
+ Create Department
320
+ </>
321
+ )}
322
+ </Button>
323
+ </div>
324
+ </form>
325
+ </CardContent>
326
+ </Card>
327
+ </div>
328
+ )
329
+ }
330
+
@@ -0,0 +1,52 @@
1
+ import { auth } from '@/lib/auth';
2
+ import { redirect } from 'next/navigation';
3
+ import { getDepartmentsOverview } from '@/lib/actions/departments';
4
+ import { PageHeader } from '@/components/shared/page-header';
5
+ import { DepartmentCardsView } from '@/components/departments/department-cards-view';
6
+ import { Button } from '@/components/ui/button';
7
+ import { Plus, QrCode } from 'lucide-react';
8
+ import Link from 'next/link';
9
+ import { hasRole } from '@/lib/auth/rbac';
10
+
11
+ export const dynamic = 'force-dynamic';
12
+
13
+ export default async function DepartmentsPage() {
14
+ const session = await auth();
15
+ if (!session?.user) redirect('/login');
16
+
17
+ const user = session.user as { roles?: string[] };
18
+ const isAdmin = hasRole(user.roles || [], 'SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN');
19
+
20
+ const result = await getDepartmentsOverview();
21
+ const departments = result.success && result.data ? result.data.departments : [];
22
+ const hospitals = result.success && result.data ? result.data.hospitals : [];
23
+
24
+ return (
25
+ <div className="space-y-6 max-w-7xl mx-auto">
26
+ <PageHeader
27
+ title="Departments"
28
+ description="Browse clinical departments, equipment health, and device allocations"
29
+ >
30
+ <div className="flex gap-2">
31
+ <Button asChild variant="outline" className="min-h-[44px]">
32
+ <Link href="/scan">
33
+ <QrCode className="w-4 h-4 mr-2" />
34
+ Scan QR
35
+ </Link>
36
+ </Button>
37
+ {isAdmin && (
38
+ <Button asChild className="min-h-[44px]">
39
+ <Link href="/departments/new">
40
+ <Plus className="w-4 h-4 mr-2" />
41
+ Add Department
42
+ </Link>
43
+ </Button>
44
+ )}
45
+ </div>
46
+ </PageHeader>
47
+
48
+ <DepartmentCardsView departments={departments} hospitals={hospitals} />
49
+ </div>
50
+ );
51
+ }
52
+
@@ -0,0 +1,131 @@
1
+ "use client"
2
+
3
+ import { updateDevice } from "@/lib/actions/devices"
4
+ import { getDeviceById } from "@/lib/actions/devices"
5
+ import { PageHeader } from "@/components/shared/page-header"
6
+ import { Button } from "@/components/ui/button"
7
+ import { Input } from "@/components/ui/input"
8
+ import { Label } from "@/components/ui/label"
9
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
10
+ import { useRouter } from "next/navigation"
11
+ import { useState, useEffect, use } from "react"
12
+
13
+ export default function EditDevicePage({ params }: { params: Promise<{ id: string }> }) {
14
+ const { id } = use(params)
15
+ const router = useRouter()
16
+ const [loading, setLoading] = useState(false)
17
+ const [error, setError] = useState("")
18
+ const [device, setDevice] = useState<any>(null)
19
+
20
+ useEffect(() => {
21
+ async function load() {
22
+ const res = await getDeviceById(id)
23
+ if (res?.success && res.data) setDevice(res.data)
24
+ }
25
+ load()
26
+ }, [id])
27
+
28
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
29
+ e.preventDefault()
30
+ setLoading(true)
31
+ setError("")
32
+ const fd = new FormData(e.currentTarget)
33
+ const data: any = {}
34
+ fd.forEach((v, k) => { if (v) data[k] = v })
35
+
36
+ const result = await updateDevice(id, data)
37
+ if (result?.success) {
38
+ router.push(`/devices/${id}`)
39
+ } else {
40
+ setError((result as any)?.error || "Failed to update device")
41
+ }
42
+ setLoading(false)
43
+ }
44
+
45
+ if (!device) return <div className="p-8 text-center text-muted-foreground">Loading...</div>
46
+
47
+ return (
48
+ <div className="space-y-6">
49
+ <PageHeader title={`Edit: ${device.name}`} description={`${device.internalCode}`} />
50
+ {error && <div className="bg-destructive/10 text-destructive p-3 rounded-md text-sm">{error}</div>}
51
+
52
+ <form onSubmit={handleSubmit}>
53
+ <div className="grid gap-6 md:grid-cols-2">
54
+ <Card>
55
+ <CardHeader><CardTitle>Device Identity</CardTitle></CardHeader>
56
+ <CardContent className="space-y-4">
57
+ <div>
58
+ <Label htmlFor="name">Device Name</Label>
59
+ <Input id="name" name="name" defaultValue={device.name} />
60
+ </div>
61
+ <div>
62
+ <Label htmlFor="assetNumber">Asset Number</Label>
63
+ <Input id="assetNumber" name="assetNumber" defaultValue={device.assetNumber} />
64
+ </div>
65
+ <div>
66
+ <Label htmlFor="serialNumber">Serial Number</Label>
67
+ <Input id="serialNumber" name="serialNumber" defaultValue={device.serialNumber || ""} />
68
+ </div>
69
+ <div>
70
+ <Label htmlFor="modelNameFree">Model Name</Label>
71
+ <Input id="modelNameFree" name="modelNameFree" defaultValue={device.modelNameFree || ""} />
72
+ </div>
73
+ </CardContent>
74
+ </Card>
75
+
76
+ <Card>
77
+ <CardHeader><CardTitle>Classification</CardTitle></CardHeader>
78
+ <CardContent className="space-y-4">
79
+ <div>
80
+ <Label htmlFor="criticalityLevel">Criticality Level</Label>
81
+ <select id="criticalityLevel" name="criticalityLevel" defaultValue={device.criticalityLevel || "medium"}
82
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm">
83
+ <option value="critical">Critical</option>
84
+ <option value="high">High</option>
85
+ <option value="medium">Medium</option>
86
+ <option value="low">Low</option>
87
+ </select>
88
+ </div>
89
+ <div>
90
+ <Label htmlFor="riskClassification">Risk Classification</Label>
91
+ <select id="riskClassification" name="riskClassification" defaultValue={device.riskClassification || ""}
92
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm">
93
+ <option value="">Select...</option>
94
+ <option value="class_i">Class I</option>
95
+ <option value="class_iia">Class IIa</option>
96
+ <option value="class_iib">Class IIb</option>
97
+ <option value="class_iii">Class III</option>
98
+ </select>
99
+ </div>
100
+ <div>
101
+ <Label htmlFor="exactLocationDescription">Exact Location</Label>
102
+ <Input id="exactLocationDescription" name="exactLocationDescription" defaultValue={device.exactLocationDescription || ""} />
103
+ </div>
104
+ </CardContent>
105
+ </Card>
106
+
107
+ <Card className="md:col-span-2">
108
+ <CardHeader><CardTitle>Warranty</CardTitle></CardHeader>
109
+ <CardContent className="grid gap-4 md:grid-cols-3">
110
+ <div>
111
+ <Label htmlFor="warrantyStartDate">Warranty Start</Label>
112
+ <Input id="warrantyStartDate" name="warrantyStartDate" type="date" defaultValue={device.warrantyStartDate || ""} />
113
+ </div>
114
+ <div>
115
+ <Label htmlFor="warrantyEndDate">Warranty End</Label>
116
+ <Input id="warrantyEndDate" name="warrantyEndDate" type="date" defaultValue={device.warrantyEndDate || ""} />
117
+ </div>
118
+ </CardContent>
119
+ </Card>
120
+ </div>
121
+
122
+ <div className="flex justify-end gap-3 mt-6">
123
+ <Button type="button" variant="outline" onClick={() => router.back()}>Cancel</Button>
124
+ <Button type="submit" disabled={loading}>
125
+ {loading ? "Saving..." : "Save Changes"}
126
+ </Button>
127
+ </div>
128
+ </form>
129
+ </div>
130
+ )
131
+ }