@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,91 @@
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { usePathname } from 'next/navigation'
5
+ import { Home, QrCode, PlusCircle, ClipboardList, Menu, TicketCheck, Inbox, Monitor, Route, Wrench } from 'lucide-react'
6
+ import { cn } from '@/lib/utils'
7
+ import { NavBadge, type NavBadgeConfig } from './nav-badge'
8
+ import { useNavCounts } from '@/components/providers/nav-counts-provider'
9
+
10
+ interface MobileTab {
11
+ href: string;
12
+ label: string;
13
+ icon: any;
14
+ badgeKey?: 'notifications' | 'triage' | 'tickets' | 'maintenance' | 'workOrders' | 'pmPlans' | string;
15
+ badgeConfig?: Partial<NavBadgeConfig>;
16
+ }
17
+
18
+ interface MobileNavProps {
19
+ roles?: string[];
20
+ }
21
+
22
+ export function MobileNav({ roles = [] }: MobileNavProps) {
23
+ const pathname = usePathname()
24
+ const { resolveBadge } = useNavCounts()
25
+
26
+ const staffRoles = ['STAFF', 'DEPT_MGR'];
27
+ const engineerRoles = ['BIOMED_ENG', 'BIOMED_TECH'];
28
+ const adminManagerRoles = ['SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN', 'BIOMED_MGR', 'AUDITOR'];
29
+
30
+ const hasStaff = roles.some(r => staffRoles.includes(r));
31
+ const hasEngineer = roles.some(r => engineerRoles.includes(r));
32
+ const hasAdminManager = roles.some(r => adminManagerRoles.includes(r));
33
+ const isStaffOnly = hasStaff && !hasEngineer && !hasAdminManager;
34
+
35
+ const staffTabs: MobileTab[] = [
36
+ { href: '/dashboard', label: 'Home', icon: Home },
37
+ { href: '/scan', label: 'Scan', icon: QrCode },
38
+ { href: '/tickets/create', label: 'Report', icon: PlusCircle },
39
+ { href: '/tickets', label: 'Tickets', icon: TicketCheck, badgeKey: 'tickets' },
40
+ { href: '/more', label: 'More', icon: Menu, badgeKey: 'notifications' },
41
+ ]
42
+
43
+ // Engineer-optimized: Quick access to Daily Round, Scan, and Tickets
44
+ const engineerTabs: MobileTab[] = [
45
+ { href: '/dashboard', label: 'Home', icon: Home },
46
+ { href: '/rounds', label: 'Round', icon: Route },
47
+ { href: '/scan', label: 'Scan', icon: QrCode },
48
+ { href: '/tickets', label: 'Tickets', icon: TicketCheck, badgeKey: 'tickets' },
49
+ { href: '/more', label: 'More', icon: Menu, badgeKey: 'notifications' },
50
+ ]
51
+
52
+ const fullTabs: MobileTab[] = [
53
+ { href: '/dashboard', label: 'Home', icon: Home },
54
+ { href: '/scan', label: 'Scan', icon: QrCode },
55
+ { href: '/tickets/create', label: 'Report', icon: PlusCircle },
56
+ { href: '/maintenance', label: 'Maintenance', icon: Wrench, badgeKey: 'maintenance' },
57
+ { href: '/more', label: 'More', icon: Menu, badgeKey: 'notifications' },
58
+ ]
59
+
60
+ const tabs = isStaffOnly ? staffTabs : hasEngineer && !hasAdminManager ? engineerTabs : fullTabs;
61
+
62
+ return (
63
+ <div className="md:hidden fixed bottom-0 left-0 right-0 z-50 bg-card border-t border-border pb-[env(safe-area-inset-bottom)]">
64
+ <nav className="flex items-center justify-around h-16">
65
+ {tabs.map((tab) => {
66
+ const active = pathname === tab.href || (pathname.startsWith(tab.href + '/') && tab.href !== '/dashboard')
67
+ const badge = resolveBadge(tab.badgeKey, tab.badgeConfig, { isCollapsed: true })
68
+
69
+ return (
70
+ <Link
71
+ key={tab.href}
72
+ href={tab.href}
73
+ className={cn(
74
+ "flex flex-col items-center justify-center w-full h-full min-w-[44px] min-h-[44px] gap-1 relative",
75
+ active ? "text-primary" : "text-muted-foreground hover:text-foreground"
76
+ )}
77
+ >
78
+ <div className="relative flex items-center justify-center shrink-0">
79
+ <tab.icon className={cn("h-5 w-5", active && "fill-primary/20")} />
80
+ {badge && (
81
+ <NavBadge {...badge} position="icon-corner" />
82
+ )}
83
+ </div>
84
+ <span className="text-[10px] font-medium leading-none">{tab.label}</span>
85
+ </Link>
86
+ )
87
+ })}
88
+ </nav>
89
+ </div>
90
+ )
91
+ }
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useNavCounts } from '@/components/providers/nav-counts-provider';
5
+ import { NavBadge } from '@/components/layout/nav-badge';
6
+
7
+ export function MoreItemBadge({ badgeKey }: { badgeKey?: string }) {
8
+ const { resolveBadge } = useNavCounts();
9
+ if (!badgeKey) return null;
10
+
11
+ const badge = resolveBadge(badgeKey, undefined, { isCollapsed: false });
12
+ if (!badge) return null;
13
+
14
+ return <NavBadge {...badge} position="inline" className="ml-auto mr-2" />;
15
+ }
@@ -0,0 +1,72 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { cn } from '@/lib/utils';
5
+ import {
6
+ navBadgeVariants,
7
+ formatNavBadge,
8
+ type NavBadgeConfig,
9
+ type NavBadgeType,
10
+ type NavBadgeVariant,
11
+ type NavBadgePosition,
12
+ type FormattedNavBadge,
13
+ } from '@/lib/utils/nav-badge';
14
+
15
+ export {
16
+ navBadgeVariants,
17
+ formatNavBadge,
18
+ type NavBadgeConfig,
19
+ type NavBadgeType,
20
+ type NavBadgeVariant,
21
+ type NavBadgePosition,
22
+ type FormattedNavBadge,
23
+ };
24
+
25
+ export interface NavBadgeProps extends NavBadgeConfig {
26
+ children?: React.ReactNode;
27
+ }
28
+
29
+ export function NavBadge({
30
+ enabled = true,
31
+ type = 'count',
32
+ value,
33
+ variant = 'default',
34
+ position = 'trailing',
35
+ maxCount = 99,
36
+ hideWhenZero = true,
37
+ pulse = false,
38
+ ariaLabel,
39
+ className,
40
+ }: NavBadgeProps) {
41
+ const formatted = formatNavBadge({
42
+ enabled,
43
+ type,
44
+ value,
45
+ maxCount,
46
+ hideWhenZero,
47
+ ariaLabel,
48
+ });
49
+
50
+ if (!formatted.visible) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ <span
56
+ className={cn(
57
+ navBadgeVariants({ variant, position, type }),
58
+ pulse && 'animate-pulse',
59
+ className
60
+ )}
61
+ role="status"
62
+ aria-label={formatted.accessibleDescription}
63
+ >
64
+ <span aria-hidden="true" className={formatted.isDot ? 'sr-only' : undefined}>
65
+ {formatted.display}
66
+ </span>
67
+ {formatted.accessibleDescription && (
68
+ <span className="sr-only">{formatted.accessibleDescription}</span>
69
+ )}
70
+ </span>
71
+ );
72
+ }
@@ -0,0 +1,297 @@
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { usePathname } from 'next/navigation'
5
+ import { useState, useEffect } from 'react'
6
+ import {
7
+ LayoutDashboard, QrCode, Monitor, TicketCheck, Wrench, BarChart3,
8
+ Bell, Settings, Activity, ChevronLeft, ChevronRight, AlertTriangle,
9
+ ClipboardList, Calendar, CheckSquare, Building2, UsersRound, PlusCircle,
10
+ Inbox, LogOut, Route
11
+ } from 'lucide-react'
12
+ import { signOut } from 'next-auth/react'
13
+ import { cn } from '@/lib/utils'
14
+ import { Button } from '@/components/ui/button'
15
+ import { Avatar, AvatarFallback } from '@/components/ui/avatar'
16
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
17
+ import { ScrollArea } from '@/components/ui/scroll-area'
18
+ import { NavBadge, type NavBadgeConfig } from './nav-badge'
19
+ import { useNavCounts } from '@/components/providers/nav-counts-provider'
20
+ import { ThemeToggle } from '@/components/theme/theme-toggle'
21
+
22
+ interface SidebarProps {
23
+ user: { name: string; role: string; email: string; roles: string[] }
24
+ }
25
+
26
+ export interface SidebarLink {
27
+ href: string;
28
+ label: string;
29
+ icon: any;
30
+ badgeKey?: 'notifications' | 'triage' | 'tickets' | 'maintenance' | 'workOrders' | 'pmPlans' | string;
31
+ badgeConfig?: Partial<NavBadgeConfig>;
32
+ }
33
+
34
+ // Staff-specific nav: simplified, focused on their department
35
+ const STAFF_LINKS: SidebarLink[] = [
36
+ { href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
37
+ { href: '/my-department', label: 'My Department', icon: Building2 },
38
+ { href: '/scan', label: 'Scan QR', icon: QrCode },
39
+ { href: '/tickets/create', label: 'Report Problem', icon: PlusCircle },
40
+ { href: '/tickets', label: 'My Tickets', icon: TicketCheck, badgeKey: 'tickets' },
41
+ { href: '/engineering-team', label: 'Engineering Team', icon: UsersRound },
42
+ { href: '/notifications', label: 'Notifications', icon: Bell, badgeKey: 'notifications' },
43
+ ]
44
+
45
+ // Engineer/Technician nav: workflow-optimized for BME daily work
46
+ const ENGINEER_LINKS: SidebarLink[] = [
47
+ { href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
48
+ { href: '/rounds', label: 'Daily Round', icon: Route },
49
+ { href: '/scan', label: 'Scan QR', icon: QrCode },
50
+ { href: '/tickets', label: 'Helpdesk Tickets', icon: TicketCheck, badgeKey: 'tickets' },
51
+ { href: '/devices', label: 'Devices', icon: Monitor },
52
+ { href: '/departments', label: 'Departments', icon: Building2 },
53
+ { href: '/team', label: 'Engineering Team', icon: UsersRound },
54
+ { href: '/maintenance', label: 'Maintenance', icon: Wrench, badgeKey: 'maintenance' },
55
+ { href: '/reports', label: 'Reports', icon: BarChart3 },
56
+ { href: '/notifications', label: 'Notifications', icon: Bell, badgeKey: 'notifications' },
57
+ ]
58
+
59
+ // Full nav for managers and admins (includes team management + admin access)
60
+ const FULL_LINKS: SidebarLink[] = [
61
+ { href: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
62
+ { href: '/rounds', label: 'Daily Round', icon: Route },
63
+ { href: '/scan', label: 'Scan QR', icon: QrCode },
64
+ { href: '/devices', label: 'Devices', icon: Monitor },
65
+ { href: '/departments', label: 'Departments', icon: Building2 },
66
+ { href: '/team', label: 'Engineering Team', icon: UsersRound },
67
+ { href: '/tickets', label: 'Helpdesk Tickets', icon: TicketCheck, badgeKey: 'tickets' },
68
+ { href: '/maintenance', label: 'Maintenance', icon: Wrench, badgeKey: 'maintenance' },
69
+ { href: '/reports', label: 'Reports', icon: BarChart3 },
70
+ { href: '/notifications', label: 'Notifications', icon: Bell, badgeKey: 'notifications' },
71
+ ]
72
+
73
+ function getSidebarLinks(roles: string[]): SidebarLink[] {
74
+ const staffRoles = ['STAFF', 'DEPT_MGR'];
75
+ const engineerRoles = ['BIOMED_ENG', 'BIOMED_TECH'];
76
+ const adminManagerRoles = ['SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN', 'BIOMED_MGR', 'AUDITOR'];
77
+
78
+ const hasStaff = roles.some(r => staffRoles.includes(r));
79
+ const hasEngineer = roles.some(r => engineerRoles.includes(r));
80
+ const hasAdminManager = roles.some(r => adminManagerRoles.includes(r));
81
+
82
+ // Admin/Manager roles get full nav (includes admin link)
83
+ if (hasAdminManager) {
84
+ return FULL_LINKS;
85
+ }
86
+ // Engineer/Technician get workflow-optimized nav
87
+ if (hasEngineer) {
88
+ return ENGINEER_LINKS;
89
+ }
90
+ // Staff-only users get simplified nav
91
+ if (hasStaff) {
92
+ return STAFF_LINKS;
93
+ }
94
+ return FULL_LINKS;
95
+ }
96
+
97
+ export function Sidebar({ user }: SidebarProps) {
98
+ const pathname = usePathname()
99
+ const [collapsed, setCollapsed] = useState(false)
100
+ const [mounted, setMounted] = useState(false)
101
+ const { resolveBadge } = useNavCounts()
102
+
103
+ useEffect(() => {
104
+ setMounted(true)
105
+ }, [])
106
+ const isAdmin = user.roles.some(r => ['SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN'].includes(r))
107
+ const links = getSidebarLinks(user.roles)
108
+
109
+ return (
110
+ <aside
111
+ className={cn(
112
+ "hidden md:flex flex-col fixed inset-y-0 left-0 z-50 bg-card border-r border-border transition-all duration-300",
113
+ collapsed ? "w-16" : "w-64"
114
+ )}
115
+ >
116
+ <div className="flex items-center h-16 shrink-0 px-4 border-b border-border justify-between">
117
+ <Link href="/dashboard" className="flex items-center gap-2 text-primary overflow-hidden">
118
+ <Activity className="h-6 w-6 shrink-0" />
119
+ {!collapsed && <span className="text-lg font-bold tracking-tight truncate">BEMMS</span>}
120
+ </Link>
121
+ <Button
122
+ variant="ghost"
123
+ size="icon"
124
+ className="h-8 w-8 hidden md:flex"
125
+ onClick={() => setCollapsed(!collapsed)}
126
+ >
127
+ {collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
128
+ </Button>
129
+ </div>
130
+
131
+ <ScrollArea className="flex-1 py-4">
132
+ <nav className="space-y-1 px-2">
133
+ <TooltipProvider delayDuration={0}>
134
+ {links.map((link) => {
135
+ const active = pathname === link.href ||
136
+ (link.href !== '/tickets' && link.href !== '/maintenance' && pathname.startsWith(link.href + '/')) ||
137
+ (link.href === '/tickets' && pathname.startsWith('/tickets')) ||
138
+ (link.href === '/maintenance' && pathname.startsWith('/maintenance'))
139
+
140
+ const badge = resolveBadge(link.badgeKey, link.badgeConfig, { isCollapsed: collapsed })
141
+
142
+ const navLink = (
143
+ <Link
144
+ href={link.href}
145
+ className={cn(
146
+ "flex items-center gap-3 px-3 py-2 rounded-md transition-colors text-sm font-medium",
147
+ active ? "bg-primary text-primary-foreground" : "hover:bg-muted text-muted-foreground hover:text-foreground",
148
+ collapsed && "justify-center px-0"
149
+ )}
150
+ >
151
+ <div className="relative flex items-center justify-center shrink-0">
152
+ <link.icon className={cn("h-5 w-5 shrink-0", active ? "text-primary-foreground" : "")} />
153
+ {collapsed && badge && (
154
+ <NavBadge {...badge} position="icon-corner" />
155
+ )}
156
+ </div>
157
+ {!collapsed && (
158
+ <>
159
+ <span className="truncate">{link.label}</span>
160
+ {badge && (
161
+ <NavBadge
162
+ {...badge}
163
+ position="trailing"
164
+ className={active && badge.variant === 'secondary' ? 'bg-primary-foreground/20 text-primary-foreground' : undefined}
165
+ />
166
+ )}
167
+ </>
168
+ )}
169
+ </Link>
170
+ )
171
+
172
+ return (
173
+ <div key={link.href}>
174
+ {mounted && collapsed ? (
175
+ <Tooltip>
176
+ <TooltipTrigger asChild>{navLink}</TooltipTrigger>
177
+ <TooltipContent side="right" className="flex items-center gap-2">
178
+ <span>{link.label}</span>
179
+ {badge && <NavBadge {...badge} position="inline" />}
180
+ </TooltipContent>
181
+ </Tooltip>
182
+ ) : (
183
+ navLink
184
+ )}
185
+ </div>
186
+ )
187
+ })}
188
+ {isAdmin && (() => {
189
+ const adminLink = (
190
+ <Link
191
+ href="/admin"
192
+ className={cn(
193
+ "flex items-center gap-3 px-3 py-2 mt-4 rounded-md transition-colors text-sm font-medium",
194
+ pathname.startsWith('/admin') ? "bg-primary text-primary-foreground" : "hover:bg-muted text-muted-foreground hover:text-foreground",
195
+ collapsed && "justify-center px-0"
196
+ )}
197
+ >
198
+ <Settings className="h-5 w-5 shrink-0" />
199
+ {!collapsed && <span>Administration</span>}
200
+ </Link>
201
+ )
202
+
203
+ return (
204
+ <div key="/admin">
205
+ {mounted && collapsed ? (
206
+ <Tooltip>
207
+ <TooltipTrigger asChild>{adminLink}</TooltipTrigger>
208
+ <TooltipContent side="right">Administration</TooltipContent>
209
+ </Tooltip>
210
+ ) : (
211
+ adminLink
212
+ )}
213
+ </div>
214
+ )
215
+ })()}
216
+ </TooltipProvider>
217
+ </nav>
218
+ </ScrollArea>
219
+
220
+ <div className="border-t border-border p-3">
221
+ {collapsed ? (
222
+ <div className="flex flex-col items-center gap-2">
223
+ <Link
224
+ href="/profile"
225
+ className="hover:opacity-80 transition rounded-lg p-1 hover:bg-muted/50"
226
+ title="View My Profile"
227
+ >
228
+ <Avatar className="h-8 w-8">
229
+ <AvatarFallback className="bg-primary/10 text-primary text-xs">
230
+ {user.name.substring(0, 2).toUpperCase()}
231
+ </AvatarFallback>
232
+ </Avatar>
233
+ </Link>
234
+ <ThemeToggle />
235
+ {mounted && (
236
+ <TooltipProvider>
237
+ <Tooltip>
238
+ <TooltipTrigger asChild>
239
+ <Button
240
+ variant="ghost"
241
+ size="icon"
242
+ className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
243
+ onClick={() => signOut({ callbackUrl: '/login' })}
244
+ >
245
+ <LogOut className="h-4 w-4" />
246
+ <span className="sr-only">Log out</span>
247
+ </Button>
248
+ </TooltipTrigger>
249
+ <TooltipContent side="right">Log out</TooltipContent>
250
+ </Tooltip>
251
+ </TooltipProvider>
252
+ )}
253
+ </div>
254
+ ) : (
255
+ <div className="flex items-center justify-between gap-2">
256
+ <Link
257
+ href="/profile"
258
+ className="flex items-center gap-3 hover:opacity-80 transition rounded-lg p-1.5 -m-1 hover:bg-muted/50 flex-1 min-w-0"
259
+ title="View My Profile"
260
+ >
261
+ <Avatar className="h-8 w-8 shrink-0">
262
+ <AvatarFallback className="bg-primary/10 text-primary text-xs">
263
+ {user.name.substring(0, 2).toUpperCase()}
264
+ </AvatarFallback>
265
+ </Avatar>
266
+ <div className="flex flex-col overflow-hidden">
267
+ <span className="text-sm font-medium truncate leading-tight">{user.name}</span>
268
+ <span className="text-[11px] text-muted-foreground truncate leading-tight">{user.role}</span>
269
+ </div>
270
+ </Link>
271
+ <div className="flex items-center gap-1 shrink-0">
272
+ <ThemeToggle />
273
+ {mounted && (
274
+ <TooltipProvider>
275
+ <Tooltip>
276
+ <TooltipTrigger asChild>
277
+ <Button
278
+ variant="ghost"
279
+ size="icon"
280
+ className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 shrink-0"
281
+ onClick={() => signOut({ callbackUrl: '/login' })}
282
+ >
283
+ <LogOut className="h-4 w-4" />
284
+ <span className="sr-only">Log out</span>
285
+ </Button>
286
+ </TooltipTrigger>
287
+ <TooltipContent side="top">Log out</TooltipContent>
288
+ </Tooltip>
289
+ </TooltipProvider>
290
+ )}
291
+ </div>
292
+ </div>
293
+ )}
294
+ </div>
295
+ </aside>
296
+ )
297
+ }