@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,462 @@
1
+ import { auth } from '@/lib/auth';
2
+ import { redirect } from 'next/navigation';
3
+ import { hasRole } from '@/lib/auth/rbac';
4
+ import {
5
+ getDashboardStats,
6
+ getMyTickets,
7
+ getDepartmentAlerts,
8
+ getTriageQueue,
9
+ getMyAssignedWork,
10
+ getMaintenanceDueSummary,
11
+ getAwaitingReview,
12
+ getDevicesOutOfService,
13
+ getAdminAlerts,
14
+ } from '@/lib/actions/dashboard';
15
+ import { getEngineersTeam } from '@/lib/actions/engineers';
16
+ import { StatCard } from '@/components/dashboard/stat-card';
17
+ import { ActionButtons } from '@/components/dashboard/action-buttons';
18
+ import { RoundWidget } from '@/components/dashboard/round-widget';
19
+ import { TicketListCompact } from '@/components/dashboard/ticket-list-compact';
20
+ import { MaintenanceDueList } from '@/components/dashboard/maintenance-due-list';
21
+ import { AvailabilityAlerts } from '@/components/dashboard/availability-alerts';
22
+ import { AdminAlerts } from '@/components/dashboard/admin-alerts';
23
+ import { Card, CardContent } from '@/components/ui/card';
24
+ import { Button } from '@/components/ui/button';
25
+ import Link from 'next/link';
26
+ import {
27
+ Monitor, TicketCheck, Wrench, AlertTriangle,
28
+ ShieldAlert, ClipboardCheck, Clock, UsersRound, MessageCircle,
29
+ } from 'lucide-react';
30
+
31
+ export default async function DashboardPage() {
32
+ const session = await auth();
33
+ if (!session?.user) redirect('/login');
34
+
35
+ const user = session.user as {
36
+ id: string;
37
+ fullName: string;
38
+ roles: string[];
39
+ organizationId: string | null;
40
+ };
41
+
42
+ const roles = user.roles || [];
43
+ const firstName = user.fullName?.split(' ')[0] || 'there';
44
+
45
+ const isBME = hasRole(roles, 'BIOMED_ENG', 'BIOMED_TECH', 'BIOMED_MGR');
46
+ const isManager = hasRole(roles, 'BIOMED_MGR', 'HOSP_ADMIN', 'ORG_ADMIN');
47
+ const isAdmin = hasRole(roles, 'SYS_ADMIN', 'ORG_ADMIN');
48
+ const isStaff = hasRole(roles, 'STAFF', 'DEPT_MGR');
49
+
50
+ // Fetch data based on role
51
+ const [stats] = await Promise.all([
52
+ getDashboardStats(),
53
+ ]);
54
+
55
+ return (
56
+ <div className="space-y-6 max-w-5xl mx-auto">
57
+ {/* Greeting */}
58
+ <div>
59
+ <h1 className="text-2xl md:text-3xl font-bold tracking-tight">
60
+ Welcome back, {firstName}
61
+ </h1>
62
+ <p className="text-muted-foreground mt-1 text-sm">
63
+ {roles.map(r => r.replace(/_/g, ' ')).join(' · ')}
64
+ </p>
65
+ </div>
66
+
67
+ {/* Staff Dashboard */}
68
+ {isStaff && <StaffDashboard stats={stats} userId={user.id} />}
69
+
70
+ {/* BME Dashboard */}
71
+ {isBME && !isManager && <BMEDashboard stats={stats} />}
72
+
73
+ {/* Manager Dashboard */}
74
+ {isManager && <ManagerDashboard stats={stats} />}
75
+
76
+ {/* Admin Dashboard */}
77
+ {isAdmin && <AdminDashboard stats={stats} />}
78
+
79
+ {/* Fallback if no specific role matched */}
80
+ {!isStaff && !isBME && !isManager && !isAdmin && (
81
+ <GenericDashboard stats={stats} />
82
+ )}
83
+ </div>
84
+ );
85
+ }
86
+
87
+ // ── Staff Dashboard ─────────────────────────────────────────────────────
88
+ async function StaffDashboard({ stats, userId }: { stats: any; userId: string }) {
89
+ const [myTickets, alerts, teamRes] = await Promise.all([
90
+ getMyTickets(5),
91
+ getDepartmentAlerts(),
92
+ getEngineersTeam(),
93
+ ]);
94
+
95
+ const engineers = teamRes.data || [];
96
+ const onDutyEngineers = engineers.filter((e: any) => e.dutyStatus === 'on_duty' || e.dutyStatus === 'on_call');
97
+ const responseNeededTickets = myTickets.filter((t: any) => t.statusCode === 'waiting_requester');
98
+
99
+ return (
100
+ <>
101
+ {/* Quick Actions - full width on mobile */}
102
+ <ActionButtons role="staff" />
103
+
104
+ {/* Action Required: Response Requested on Tickets */}
105
+ {responseNeededTickets.length > 0 && (
106
+ <Card className="border-amber-300 dark:border-amber-900 bg-amber-50/70 dark:bg-amber-950/20 shadow-sm overflow-hidden">
107
+ <CardContent className="p-4 flex items-start gap-3">
108
+ <MessageCircle className="w-5 h-5 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
109
+ <div className="flex-1 min-w-0">
110
+ <h4 className="font-semibold text-sm text-amber-900 dark:text-amber-200">
111
+ Action Required: Response Requested ({responseNeededTickets.length})
112
+ </h4>
113
+ <p className="text-xs text-amber-700 dark:text-amber-300 mt-0.5">
114
+ Biomedical Engineering needs additional information from you regarding reported issues.
115
+ </p>
116
+ <div className="mt-2 space-y-1">
117
+ {responseNeededTickets.map((t: any) => (
118
+ <Link
119
+ key={t.id}
120
+ href={`/tickets/${t.id}`}
121
+ className="block p-2 rounded-lg bg-card/80 border border-amber-200 dark:border-amber-800 text-xs hover:border-amber-400 transition"
122
+ >
123
+ <span className="font-mono font-bold">{t.ticketNumber}</span>: {t.title} → Tap to reply
124
+ </Link>
125
+ ))}
126
+ </div>
127
+ </div>
128
+ </CardContent>
129
+ </Card>
130
+ )}
131
+
132
+ {/* Department Availability Alerts */}
133
+ {alerts.length > 0 && (
134
+ <AvailabilityAlerts devices={alerts} />
135
+ )}
136
+
137
+ {/* Biomedical Engineering Coverage / On-Duty Team Widget */}
138
+ <Card className="border shadow-sm">
139
+ <CardContent className="p-4">
140
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
141
+ <div className="flex items-center gap-3">
142
+ <div className="p-2.5 rounded-lg bg-purple-50 dark:bg-purple-950 text-purple-700 dark:text-purple-300">
143
+ <UsersRound className="w-5 h-5" />
144
+ </div>
145
+ <div>
146
+ <h3 className="font-semibold text-sm">Biomedical Engineering Support</h3>
147
+ <p className="text-xs text-muted-foreground">
148
+ {onDutyEngineers.length > 0
149
+ ? `${onDutyEngineers.length} engineer(s) available in hospital today`
150
+ : 'Biomedical support on duty & on call'}
151
+ </p>
152
+ </div>
153
+ </div>
154
+ <Button asChild variant="outline" size="sm" className="self-start sm:self-auto text-xs">
155
+ <Link href="/engineering-team">View Team & Schedules →</Link>
156
+ </Button>
157
+ </div>
158
+ {onDutyEngineers.length > 0 && (
159
+ <div className="mt-3 pt-3 border-t grid sm:grid-cols-2 md:grid-cols-3 gap-2">
160
+ {onDutyEngineers.slice(0, 3).map((eng: any) => (
161
+ <div key={eng.id} className="flex items-center gap-2 p-2 rounded-lg bg-muted/40 text-xs">
162
+ <span className="w-2 h-2 rounded-full bg-green-500 shrink-0" />
163
+ <span className="font-medium truncate">{eng.fullName}</span>
164
+ {eng.extension ? (
165
+ <span className="text-muted-foreground ml-auto shrink-0 font-mono">Ext. {eng.extension}</span>
166
+ ) : eng.mobileNumber ? (
167
+ <span className="text-muted-foreground ml-auto shrink-0">{eng.mobileNumber}</span>
168
+ ) : null}
169
+ </div>
170
+ ))}
171
+ </div>
172
+ )}
173
+ </CardContent>
174
+ </Card>
175
+
176
+ {/* My Tickets */}
177
+ <TicketListCompact
178
+ tickets={myTickets}
179
+ title="My Recent Tickets"
180
+ emptyMessage="No tickets need your attention."
181
+ />
182
+
183
+ {/* Stats row */}
184
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
185
+ <StatCard
186
+ title="Open Tickets"
187
+ value={stats?.openTickets || 0}
188
+ icon={<TicketCheck className="h-4 w-4 text-orange-500" />}
189
+ href="/tickets"
190
+ />
191
+ <StatCard
192
+ title="Devices Available"
193
+ value={stats?.activeDevices || 0}
194
+ icon={<Monitor className="h-4 w-4 text-green-500" />}
195
+ href="/my-department"
196
+ />
197
+ </div>
198
+ </>
199
+ );
200
+ }
201
+
202
+ // ── BME Dashboard ────────────────────────────────────────────────────────
203
+ async function BMEDashboard({ stats }: { stats: any }) {
204
+ const [triage, assigned, maintenanceDue, review, outOfService] =
205
+ await Promise.all([
206
+ getTriageQueue(5),
207
+ getMyAssignedWork(5),
208
+ getMaintenanceDueSummary(),
209
+ getAwaitingReview(5),
210
+ getDevicesOutOfService(5),
211
+ ]);
212
+
213
+ return (
214
+ <>
215
+ {/* Critical alert cards */}
216
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
217
+ {(stats?.criticalTickets || 0) > 0 && (
218
+ <StatCard
219
+ title="Critical Tickets"
220
+ value={stats.criticalTickets}
221
+ icon={<ShieldAlert className="h-4 w-4 text-red-500" />}
222
+ href="/tickets?priority=p1_critical"
223
+ />
224
+ )}
225
+ <StatCard
226
+ title="Untriaged"
227
+ value={triage.length}
228
+ icon={<AlertTriangle className="h-4 w-4 text-amber-500" />}
229
+ href="/tickets/triage"
230
+ />
231
+ <StatCard
232
+ title="Due / Overdue"
233
+ value={`${maintenanceDue.due_today + maintenanceDue.overdue}`}
234
+ icon={<Clock className={`h-4 w-4 ${maintenanceDue.overdue > 0 ? 'text-red-500' : 'text-yellow-500'}`} />}
235
+ href="/maintenance/tasks?due=today"
236
+ trend={maintenanceDue.overdue > 0 ? `${maintenanceDue.overdue} overdue` : undefined}
237
+ />
238
+ <StatCard
239
+ title="Out of Service"
240
+ value={stats?.outOfService || 0}
241
+ icon={<AlertTriangle className="h-4 w-4 text-red-500" />}
242
+ href="/devices?status=out_of_service"
243
+ />
244
+ </div>
245
+
246
+ {/* Quick Actions */}
247
+ <ActionButtons role="bme" triageCount={triage.length} />
248
+
249
+ {/* Daily Round Widget */}
250
+ <RoundWidget />
251
+
252
+ {/* Assigned Work */}
253
+ <TicketListCompact
254
+ tickets={assigned
255
+ .filter((a) => a.type === 'ticket')
256
+ .map((a) => ({
257
+ id: a.id,
258
+ ticketNumber: a.number,
259
+ title: a.title,
260
+ statusCode: a.statusCode,
261
+ priorityCode: a.priorityCode,
262
+ reportedAt: a.date || new Date(),
263
+ deviceName: a.deviceName,
264
+ }))}
265
+ title="My Assigned Tickets"
266
+ emptyMessage="No assigned tickets."
267
+ />
268
+
269
+ {/* Maintenance Due */}
270
+ <MaintenanceDueList
271
+ tasks={assigned
272
+ .filter((a) => a.type === 'task')
273
+ .map((a) => ({
274
+ id: a.id,
275
+ taskNumber: a.number,
276
+ title: a.title,
277
+ statusCode: a.statusCode,
278
+ dueDate: a.date || null,
279
+ deviceName: a.deviceName,
280
+ maintenanceType: 'preventive_maintenance',
281
+ }))}
282
+ title="My Maintenance Tasks"
283
+ />
284
+
285
+ {/* Awaiting Review */}
286
+ {review.length > 0 && (
287
+ <MaintenanceDueList
288
+ tasks={review}
289
+ title={`Awaiting Review (${review.length})`}
290
+ />
291
+ )}
292
+
293
+ {/* Out of Service Devices */}
294
+ {outOfService.length > 0 && (
295
+ <AvailabilityAlerts devices={outOfService} />
296
+ )}
297
+ </>
298
+ );
299
+ }
300
+
301
+ // ── Manager Dashboard ────────────────────────────────────────────────────
302
+ async function ManagerDashboard({ stats }: { stats: any }) {
303
+ const [triage, review, maintenanceDue, outOfService] = await Promise.all([
304
+ getTriageQueue(5),
305
+ getAwaitingReview(5),
306
+ getMaintenanceDueSummary(),
307
+ getDevicesOutOfService(10),
308
+ ]);
309
+
310
+ const complianceRate = stats?.pmComplianceRate || 100;
311
+
312
+ return (
313
+ <>
314
+ {/* Exception-first metric cards */}
315
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
316
+ <StatCard
317
+ title="Critical Tickets"
318
+ value={stats?.criticalTickets || 0}
319
+ icon={<ShieldAlert className="h-4 w-4 text-red-500" />}
320
+ href="/tickets?priority=p1_critical"
321
+ />
322
+ <StatCard
323
+ title="Overdue Maintenance"
324
+ value={maintenanceDue.overdue}
325
+ icon={<Clock className={`h-4 w-4 ${maintenanceDue.overdue > 0 ? 'text-red-500' : 'text-green-500'}`} />}
326
+ href="/maintenance/tasks?due=overdue"
327
+ />
328
+ <StatCard
329
+ title="Out of Service"
330
+ value={stats?.outOfService || 0}
331
+ icon={<AlertTriangle className="h-4 w-4 text-red-500" />}
332
+ href="/devices?status=out_of_service"
333
+ />
334
+ <StatCard
335
+ title="PM Compliance"
336
+ value={`${complianceRate}%`}
337
+ icon={<ClipboardCheck className={`h-4 w-4 ${complianceRate >= 90 ? 'text-green-500' : 'text-amber-500'}`} />}
338
+ href="/reports/maintenance"
339
+ />
340
+ </div>
341
+
342
+ {/* Overall stats */}
343
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
344
+ <StatCard
345
+ title="Total Devices"
346
+ value={stats?.totalDevices || 0}
347
+ icon={<Monitor className="h-4 w-4 text-blue-500" />}
348
+ href="/devices"
349
+ />
350
+ <StatCard
351
+ title="Open Tickets"
352
+ value={stats?.openTickets || 0}
353
+ icon={<TicketCheck className="h-4 w-4 text-orange-500" />}
354
+ href="/tickets?status=open"
355
+ />
356
+ <StatCard
357
+ title="Untriaged"
358
+ value={triage.length}
359
+ icon={<AlertTriangle className="h-4 w-4 text-amber-500" />}
360
+ href="/tickets/triage"
361
+ />
362
+ <StatCard
363
+ title="Awaiting Review"
364
+ value={review.length}
365
+ icon={<Wrench className="h-4 w-4 text-purple-500" />}
366
+ href="/maintenance/tasks?status=awaiting_review"
367
+ />
368
+ </div>
369
+
370
+ {/* Daily Round Widget */}
371
+ <RoundWidget />
372
+
373
+ {/* Triage Queue */}
374
+ {triage.length > 0 && (
375
+ <TicketListCompact
376
+ tickets={triage}
377
+ title="Triage Queue"
378
+ emptyMessage="All tickets have been triaged."
379
+ />
380
+ )}
381
+
382
+ {/* Awaiting Review */}
383
+ {review.length > 0 && (
384
+ <MaintenanceDueList
385
+ tasks={review}
386
+ title="Awaiting Review / Release"
387
+ />
388
+ )}
389
+
390
+ {/* Out of Service */}
391
+ {outOfService.length > 0 && (
392
+ <AvailabilityAlerts devices={outOfService} />
393
+ )}
394
+ </>
395
+ );
396
+ }
397
+
398
+ // ── Admin Dashboard ──────────────────────────────────────────────────────
399
+ async function AdminDashboard({ stats }: { stats: any }) {
400
+ const adminAlerts = await getAdminAlerts();
401
+
402
+ return (
403
+ <>
404
+ {/* Admin Alerts */}
405
+ <AdminAlerts alerts={adminAlerts} />
406
+
407
+ {/* System Overview */}
408
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
409
+ <StatCard
410
+ title="Total Devices"
411
+ value={stats?.totalDevices || 0}
412
+ icon={<Monitor className="h-4 w-4 text-blue-500" />}
413
+ href="/devices"
414
+ />
415
+ <StatCard
416
+ title="Open Tickets"
417
+ value={stats?.openTickets || 0}
418
+ icon={<TicketCheck className="h-4 w-4 text-orange-500" />}
419
+ href="/tickets"
420
+ />
421
+ <StatCard
422
+ title="Out of Service"
423
+ value={stats?.outOfService || 0}
424
+ icon={<AlertTriangle className="h-4 w-4 text-red-500" />}
425
+ href="/devices?status=out_of_service"
426
+ />
427
+ <StatCard
428
+ title="PM Compliance"
429
+ value={`${stats?.pmComplianceRate || 100}%`}
430
+ icon={<ClipboardCheck className="h-4 w-4 text-green-500" />}
431
+ href="/reports/maintenance"
432
+ />
433
+ </div>
434
+
435
+ {/* Quick Actions */}
436
+ <ActionButtons role="admin" />
437
+ </>
438
+ );
439
+ }
440
+
441
+ // ── Generic Fallback ─────────────────────────────────────────────────────
442
+ function GenericDashboard({ stats }: { stats: any }) {
443
+ return (
444
+ <>
445
+ <ActionButtons role="staff" />
446
+ <div className="grid gap-4 grid-cols-2 lg:grid-cols-4">
447
+ <StatCard
448
+ title="Total Devices"
449
+ value={stats?.totalDevices || 0}
450
+ icon={<Monitor className="h-4 w-4 text-blue-500" />}
451
+ href="/devices"
452
+ />
453
+ <StatCard
454
+ title="Open Tickets"
455
+ value={stats?.openTickets || 0}
456
+ icon={<TicketCheck className="h-4 w-4 text-orange-500" />}
457
+ href="/tickets"
458
+ />
459
+ </div>
460
+ </>
461
+ );
462
+ }
@@ -0,0 +1,39 @@
1
+ import { auth } from '@/lib/auth';
2
+ import { redirect, notFound } from 'next/navigation';
3
+ import { getDepartmentDetails } from '@/lib/actions/departments';
4
+ import { DepartmentDetailView } from '@/components/departments/department-detail-view';
5
+ import { hasRole } from '@/lib/auth/rbac';
6
+
7
+ export const dynamic = 'force-dynamic';
8
+
9
+ export default async function DepartmentPage({
10
+ params,
11
+ }: {
12
+ params: Promise<{ deptId: string }>;
13
+ }) {
14
+ const session = await auth();
15
+ if (!session?.user) redirect('/login');
16
+
17
+ const { deptId } = await params;
18
+ const result = await getDepartmentDetails(deptId);
19
+
20
+ if (!result.success || !result.data) {
21
+ notFound();
22
+ }
23
+
24
+ const user = session.user as { roles?: string[] };
25
+ const isAdmin = hasRole(user.roles || [], 'SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN');
26
+
27
+ const { department, devices, tickets, locations, stats } = result.data;
28
+
29
+ return (
30
+ <DepartmentDetailView
31
+ department={department}
32
+ devices={devices}
33
+ tickets={tickets}
34
+ locations={locations}
35
+ stats={stats}
36
+ isAdmin={isAdmin}
37
+ />
38
+ );
39
+ }