@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,119 @@
1
+ import { listMaintenanceTasks } from "@/lib/actions/maintenance-tasks"
2
+ import { PageHeader } from "@/components/shared/page-header"
3
+ import { DataTable } from "@/components/shared/data-table"
4
+ import { MaintenanceNav } from "@/components/maintenance/maintenance-nav"
5
+ import {
6
+ MaintenanceStatusBadge,
7
+ MaintenanceTypeBadge,
8
+ PriorityBadge,
9
+ } from "@/components/maintenance/maintenance-badges"
10
+ import { Button } from "@/components/ui/button"
11
+ import { Plus } from "lucide-react"
12
+ import Link from "next/link"
13
+
14
+ export default async function MaintenanceTasksPage() {
15
+ const result = await listMaintenanceTasks({ page: 1, pageSize: 100 })
16
+ const tasks = result?.success && Array.isArray(result.data) ? result.data : []
17
+
18
+ const columns = [
19
+ {
20
+ key: "taskNumber",
21
+ header: "Task #",
22
+ render: (item: any) => (
23
+ <Link
24
+ href={`/maintenance/tasks/${item.id}`}
25
+ className="text-primary hover:underline font-mono text-sm font-medium"
26
+ >
27
+ {item.taskNumber}
28
+ </Link>
29
+ ),
30
+ },
31
+ {
32
+ key: "title",
33
+ header: "Title",
34
+ render: (item: any) => (
35
+ <Link
36
+ href={`/maintenance/tasks/${item.id}`}
37
+ className="font-medium hover:underline line-clamp-1"
38
+ >
39
+ {item.title}
40
+ </Link>
41
+ ),
42
+ },
43
+ {
44
+ key: "device",
45
+ header: "Device",
46
+ render: (item: any) => (
47
+ <div>
48
+ <span className="text-sm font-medium">
49
+ {item.device?.name || "No device"}
50
+ </span>
51
+ {item.device?.assetNumber && (
52
+ <span className="text-xs text-muted-foreground block">
53
+ Asset: {item.device.assetNumber}
54
+ </span>
55
+ )}
56
+ </div>
57
+ ),
58
+ },
59
+ {
60
+ key: "maintenanceType",
61
+ header: "Maintenance Type",
62
+ render: (item: any) => (
63
+ <MaintenanceTypeBadge type={item.maintenanceType} />
64
+ ),
65
+ },
66
+ {
67
+ key: "priorityCode",
68
+ header: "Priority",
69
+ render: (item: any) => (
70
+ <PriorityBadge priority={item.priorityCode} showIcon />
71
+ ),
72
+ },
73
+ {
74
+ key: "statusCode",
75
+ header: "Status",
76
+ render: (item: any) => (
77
+ <MaintenanceStatusBadge status={item.statusCode} showIcon />
78
+ ),
79
+ },
80
+ {
81
+ key: "assignee",
82
+ header: "Assignee",
83
+ render: (item: any) => (
84
+ <span className="text-sm">
85
+ {item.assignedEngineer?.fullName || "Unassigned"}
86
+ </span>
87
+ ),
88
+ },
89
+ {
90
+ key: "dueDate",
91
+ header: "Due Date",
92
+ render: (item: any) => (
93
+ <span className="text-xs text-muted-foreground">
94
+ {item.dueDate ? new Date(item.dueDate).toLocaleDateString() : "—"}
95
+ </span>
96
+ ),
97
+ },
98
+ ]
99
+
100
+ return (
101
+ <div className="space-y-6">
102
+ <PageHeader
103
+ title="Maintenance Tasks"
104
+ description="View, filter, and manage maintenance work orders and execution stages"
105
+ />
106
+
107
+ <MaintenanceNav />
108
+
109
+ <DataTable
110
+ data={tasks}
111
+ columns={columns}
112
+ searchable={true}
113
+ searchKey="title"
114
+ searchPlaceholder="Search tasks by title, number..."
115
+ />
116
+ </div>
117
+ )
118
+ }
119
+
@@ -0,0 +1,310 @@
1
+ import { auth } from '@/lib/auth';
2
+ import { redirect } from 'next/navigation';
3
+ import Link from 'next/link';
4
+ import { hasRole } from '@/lib/auth/rbac';
5
+ import { Card, CardContent } from '@/components/ui/card';
6
+ import { Button } from '@/components/ui/button';
7
+ import { Badge } from '@/components/ui/badge';
8
+ import {
9
+ Monitor,
10
+ QrCode,
11
+ TicketCheck,
12
+ PlusCircle,
13
+ AlertTriangle,
14
+ Wrench,
15
+ ClipboardList,
16
+ Calendar,
17
+ CheckSquare,
18
+ BarChart3,
19
+ Bell,
20
+ Users,
21
+ Building2,
22
+ Building,
23
+ ShieldCheck,
24
+ ChevronRight,
25
+ User,
26
+ Route,
27
+ } from 'lucide-react';
28
+ import { SignOutButton } from '@/components/shared/sign-out-button';
29
+ import { MoreItemBadge } from '@/components/layout/more-item-badge';
30
+ import { ThemeSelectorCard } from '@/components/theme/theme-selector-card';
31
+
32
+ export default async function MorePage() {
33
+ const session = await auth();
34
+ if (!session?.user) redirect('/login');
35
+
36
+ const user = session.user as {
37
+ id: string;
38
+ fullName?: string;
39
+ email?: string;
40
+ roles?: string[];
41
+ };
42
+
43
+ const roles = user.roles || [];
44
+ const isAdmin = hasRole(roles, 'SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN');
45
+ const isBiomed = hasRole(roles, 'BIOMED_ENG', 'BIOMED_TECH', 'BIOMED_MGR');
46
+ const isStaffOnly = hasRole(roles, 'STAFF', 'DEPT_MGR') && !isBiomed && !isAdmin;
47
+
48
+ const sections = [
49
+ {
50
+ title: 'Equipment & Helpdesk',
51
+ items: [
52
+ ...(isStaffOnly
53
+ ? [
54
+ {
55
+ label: 'My Department',
56
+ description: 'View your department equipment and status',
57
+ href: '/my-department',
58
+ icon: Building2,
59
+ color: 'text-sky-500 bg-sky-50 dark:bg-sky-950',
60
+ },
61
+ ]
62
+ : [
63
+ {
64
+ label: 'Device Inventory',
65
+ description: 'Browse, search, and manage all biomedical equipment',
66
+ href: '/devices',
67
+ icon: Monitor,
68
+ color: 'text-blue-500 bg-blue-50 dark:bg-blue-950',
69
+ },
70
+ {
71
+ label: 'Departments',
72
+ description: 'Explore hospital departments and departmental equipment',
73
+ href: '/departments',
74
+ icon: Building2,
75
+ color: 'text-sky-500 bg-sky-50 dark:bg-sky-950',
76
+ },
77
+ ]),
78
+ {
79
+ label: 'Scan QR Code',
80
+ description: 'Quick scan device label with camera',
81
+ href: '/scan',
82
+ icon: QrCode,
83
+ color: 'text-emerald-500 bg-emerald-50 dark:bg-emerald-950',
84
+ },
85
+ {
86
+ label: isStaffOnly ? 'My Tickets' : 'Helpdesk Tickets',
87
+ description: isStaffOnly
88
+ ? 'Track your reported device problems'
89
+ : 'View service tickets, fault reports, and status',
90
+ href: '/tickets',
91
+ icon: TicketCheck,
92
+ color: 'text-orange-500 bg-orange-50 dark:bg-orange-950',
93
+ badgeKey: 'tickets',
94
+ },
95
+ {
96
+ label: 'Report a Problem',
97
+ description: 'Submit an equipment issue or service request',
98
+ href: '/tickets/create',
99
+ icon: PlusCircle,
100
+ color: 'text-red-500 bg-red-50 dark:bg-red-950',
101
+ },
102
+ ...(isStaffOnly
103
+ ? [
104
+ {
105
+ label: 'Our Engineering Team',
106
+ description: 'View biomedical engineers, contact info, and schedules',
107
+ href: '/engineering-team',
108
+ icon: User,
109
+ color: 'text-purple-500 bg-purple-50 dark:bg-purple-950',
110
+ },
111
+ ]
112
+ : []),
113
+ ...(isBiomed || isAdmin
114
+ ? [
115
+ {
116
+ label: 'Engineering Team',
117
+ description: 'Biomedical engineering duty roster, shifts, and on-call schedules',
118
+ href: '/team',
119
+ icon: Users,
120
+ color: 'text-purple-500 bg-purple-50 dark:bg-purple-950',
121
+ },
122
+ {
123
+ label: 'Triage Queue',
124
+ description: 'Review, prioritize, and assign new incoming tickets',
125
+ href: '/tickets/triage',
126
+ icon: AlertTriangle,
127
+ color: 'text-amber-500 bg-amber-50 dark:bg-amber-950',
128
+ badgeKey: 'triage',
129
+ },
130
+ ]
131
+ : []),
132
+ ],
133
+ },
134
+ ...(!isStaffOnly
135
+ ? [
136
+ {
137
+ title: 'Maintenance & Work Orders',
138
+ items: [
139
+ {
140
+ label: "Engineer's Daily Round",
141
+ description: 'Start or continue daily department round and inspections',
142
+ href: '/rounds',
143
+ icon: Route,
144
+ color: 'text-emerald-500 bg-emerald-50 dark:bg-emerald-950',
145
+ },
146
+ {
147
+ label: 'Maintenance',
148
+ description: 'Hub for work orders, PM plans, and safety checklists',
149
+ href: '/maintenance',
150
+ icon: Wrench,
151
+ color: 'text-indigo-500 bg-indigo-50 dark:bg-indigo-950',
152
+ badgeKey: 'maintenance',
153
+ },
154
+ {
155
+ label: 'Work Orders / Tasks',
156
+ description: 'Assigned maintenance tasks and due schedules',
157
+ href: '/maintenance/tasks',
158
+ icon: ClipboardList,
159
+ color: 'text-cyan-500 bg-cyan-50 dark:bg-cyan-950',
160
+ badgeKey: 'workOrders',
161
+ },
162
+ {
163
+ label: 'PM Plans & Schedules',
164
+ description: 'Recurring inspection and calibration schedules',
165
+ href: '/maintenance/plans',
166
+ icon: Calendar,
167
+ color: 'text-violet-500 bg-violet-50 dark:bg-violet-950',
168
+ badgeKey: 'pmPlans',
169
+ },
170
+ {
171
+ label: 'Checklists',
172
+ description: 'Standardized safety and inspection checklists',
173
+ href: '/maintenance/checklists',
174
+ icon: CheckSquare,
175
+ color: 'text-teal-500 bg-teal-50 dark:bg-teal-950',
176
+ },
177
+ ],
178
+ },
179
+ ]
180
+ : []),
181
+ {
182
+ title: 'Analytics & Notifications',
183
+ items: [
184
+ {
185
+ label: 'Reports & Compliance',
186
+ description: 'Inventory, tickets, PM compliance, workload & cost reports',
187
+ href: '/reports',
188
+ icon: BarChart3,
189
+ color: 'text-rose-500 bg-rose-50 dark:bg-rose-950',
190
+ },
191
+ {
192
+ label: 'Notifications',
193
+ description: 'Alerts, updates, and notification settings',
194
+ href: '/notifications',
195
+ icon: Bell,
196
+ color: 'text-amber-500 bg-amber-50 dark:bg-amber-950',
197
+ badgeKey: 'notifications',
198
+ },
199
+ ],
200
+ },
201
+ ...(isAdmin
202
+ ? [
203
+ {
204
+ title: 'System Administration',
205
+ items: [
206
+ {
207
+ label: 'Users & Roles',
208
+ description: 'Manage staff, engineers, and permissions',
209
+ href: '/admin/users',
210
+ icon: Users,
211
+ color: 'text-purple-500 bg-purple-50 dark:bg-purple-950',
212
+ },
213
+ {
214
+ label: 'General Settings',
215
+ description: 'Manage health system organization and hospital facilities',
216
+ href: '/admin/general',
217
+ icon: Building2,
218
+ color: 'text-blue-500 bg-blue-50 dark:bg-blue-950',
219
+ },
220
+ {
221
+ label: 'Audit Logs',
222
+ description: 'Electronic signatures and traceability logs',
223
+ href: '/admin/audit-logs',
224
+ icon: ClipboardList,
225
+ color: 'text-stone-500 bg-stone-50 dark:bg-stone-950',
226
+ },
227
+ ],
228
+ },
229
+ ]
230
+ : []),
231
+ ];
232
+
233
+ return (
234
+ <div className="space-y-6 max-w-2xl mx-auto pb-8">
235
+ {/* User Card */}
236
+ <Card className="border shadow-sm hover:border-primary/30 transition">
237
+ <CardContent className="p-4 sm:p-5 flex items-center justify-between gap-4">
238
+ <Link href="/profile" className="flex items-center gap-4 flex-1 min-w-0">
239
+ <div className="w-12 h-12 rounded-full bg-primary/10 text-primary flex items-center justify-center font-bold text-lg shrink-0">
240
+ {user.fullName?.charAt(0) || <User className="w-6 h-6" />}
241
+ </div>
242
+ <div className="flex flex-col flex-1 min-w-0">
243
+ <h2 className="font-semibold text-base truncate">{user.fullName || 'User'}</h2>
244
+ <p className="text-xs text-muted-foreground truncate">{user.email}</p>
245
+ <div className="flex flex-wrap gap-1 mt-1.5">
246
+ {roles.map((role) => (
247
+ <Badge key={role} variant="secondary" className="text-[10px] px-1.5 py-0">
248
+ {role.replace(/_/g, ' ')}
249
+ </Badge>
250
+ ))}
251
+ </div>
252
+ </div>
253
+ </Link>
254
+ <Button asChild variant="outline" size="sm" className="shrink-0 text-xs">
255
+ <Link href="/profile">My Profile</Link>
256
+ </Button>
257
+ </CardContent>
258
+ </Card>
259
+
260
+ {/* Sections */}
261
+ {sections.map((section) => (
262
+ <div key={section.title} className="space-y-2">
263
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground px-1">
264
+ {section.title}
265
+ </h3>
266
+ <div className="bg-card rounded-xl border divide-y overflow-hidden shadow-sm">
267
+ {section.items.map((item) => {
268
+ const Icon = item.icon;
269
+ return (
270
+ <Link
271
+ key={item.href}
272
+ href={item.href}
273
+ className="flex items-center justify-between p-3.5 sm:p-4 hover:bg-muted/50 transition-colors gap-3"
274
+ >
275
+ <div className="flex items-center gap-3 min-w-0">
276
+ <div className={`p-2.5 rounded-lg shrink-0 ${item.color}`}>
277
+ <Icon className="w-5 h-5" />
278
+ </div>
279
+ <div className="flex flex-col min-w-0">
280
+ <span className="font-medium text-sm leading-snug">{item.label}</span>
281
+ <span className="text-xs text-muted-foreground truncate">
282
+ {item.description}
283
+ </span>
284
+ </div>
285
+ </div>
286
+ <div className="flex items-center gap-2 shrink-0">
287
+ <MoreItemBadge badgeKey={(item as any).badgeKey} />
288
+ <ChevronRight className="w-4 h-4 text-muted-foreground shrink-0" />
289
+ </div>
290
+ </Link>
291
+ );
292
+ })}
293
+ </div>
294
+ </div>
295
+ ))}
296
+
297
+ {/* Theme Settings */}
298
+ <ThemeSelectorCard />
299
+
300
+ {/* Account Session & Sign Out */}
301
+ <div className="pt-2">
302
+ <SignOutButton
303
+ variant="outline"
304
+ className="w-full text-destructive hover:bg-destructive/10 hover:text-destructive border-destructive/20 h-11 text-sm font-medium"
305
+ label="Sign Out of Account"
306
+ />
307
+ </div>
308
+ </div>
309
+ );
310
+ }
@@ -0,0 +1,151 @@
1
+ import { auth } from '@/lib/auth';
2
+ import { redirect } from 'next/navigation';
3
+ import { db } from '@/lib/db';
4
+ import { devices, departments, userAccessScopes, serviceTickets, hospitals } from '@/lib/db/schema';
5
+ import { eq, and, inArray, ne, not, desc, sql } from 'drizzle-orm';
6
+ import { PageHeader } from '@/components/shared/page-header';
7
+ import { MyDepartmentView } from '@/components/departments/my-department-view';
8
+
9
+ export const dynamic = 'force-dynamic';
10
+
11
+ export default async function MyDepartmentPage() {
12
+ const session = await auth();
13
+ if (!session?.user) redirect('/login');
14
+
15
+ const user = session.user as {
16
+ id: string;
17
+ organizationId: string | null;
18
+ roles: string[];
19
+ };
20
+
21
+ const orgId = user.organizationId;
22
+ if (!orgId) redirect('/dashboard');
23
+
24
+ // Get user's department scopes
25
+ const scopes = await db.query.userAccessScopes.findMany({
26
+ where: eq(userAccessScopes.userId, user.id),
27
+ });
28
+
29
+ const deptIds = scopes
30
+ .map((s) => s.departmentId)
31
+ .filter(Boolean) as string[];
32
+
33
+ // If user has no department scopes, try to find their department from the first scope
34
+ // or redirect to dashboard
35
+ if (deptIds.length === 0) {
36
+ return (
37
+ <div className="space-y-6 max-w-3xl mx-auto">
38
+ <PageHeader
39
+ title="My Department"
40
+ description="Your department equipment and status"
41
+ />
42
+ <div className="text-center py-12">
43
+ <p className="text-muted-foreground text-sm">
44
+ You are not currently assigned to a department.
45
+ </p>
46
+ <p className="text-muted-foreground text-xs mt-1">
47
+ Contact your administrator to assign you to a department.
48
+ </p>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ // Get department info
55
+ const dept = await db.query.departments.findFirst({
56
+ where: eq(departments.id, deptIds[0]),
57
+ with: {
58
+ hospital: true,
59
+ },
60
+ });
61
+
62
+ // Get department devices with related data
63
+ const deptDevices = await db.query.devices.findMany({
64
+ where: and(
65
+ eq(devices.organizationId, orgId),
66
+ inArray(devices.departmentId, deptIds)
67
+ ),
68
+ with: {
69
+ department: true,
70
+ location: true,
71
+ deviceCategory: true,
72
+ manufacturer: true,
73
+ },
74
+ orderBy: [desc(devices.currentStatusCode), desc(devices.updatedAt)],
75
+ });
76
+
77
+ // Get active tickets for department devices
78
+ const deviceIds = deptDevices.map((d) => d.id);
79
+ let activeTickets: any[] = [];
80
+ if (deviceIds.length > 0) {
81
+ activeTickets = await db.query.serviceTickets.findMany({
82
+ where: and(
83
+ eq(serviceTickets.organizationId, orgId),
84
+ inArray(serviceTickets.deviceId, deviceIds),
85
+ not(
86
+ inArray(serviceTickets.statusCode, [
87
+ 'resolved',
88
+ 'closed',
89
+ 'cancelled',
90
+ ])
91
+ )
92
+ ),
93
+ columns: {
94
+ id: true,
95
+ ticketNumber: true,
96
+ title: true,
97
+ statusCode: true,
98
+ deviceId: true,
99
+ },
100
+ });
101
+ }
102
+
103
+ // Compute summary stats
104
+ const stats = {
105
+ total: deptDevices.length,
106
+ operational: deptDevices.filter(
107
+ (d) => d.currentStatusCode === 'operational'
108
+ ).length,
109
+ underMaintenance: deptDevices.filter((d) =>
110
+ [
111
+ 'under_repair',
112
+ 'under_maintenance',
113
+ 'waiting_for_parts',
114
+ 'under_investigation',
115
+ ].includes(d.currentStatusCode)
116
+ ).length,
117
+ outOfService: deptDevices.filter((d) =>
118
+ ['out_of_service', 'decommissioned'].includes(d.currentStatusCode)
119
+ ).length,
120
+ };
121
+
122
+ // Build ticket map keyed by deviceId
123
+ const ticketsByDevice: Record<string, typeof activeTickets> = {};
124
+ activeTickets.forEach((t) => {
125
+ if (t.deviceId) {
126
+ if (!ticketsByDevice[t.deviceId]) ticketsByDevice[t.deviceId] = [];
127
+ ticketsByDevice[t.deviceId].push(t);
128
+ }
129
+ });
130
+
131
+ return (
132
+ <MyDepartmentView
133
+ department={{
134
+ name: dept?.name || 'My Department',
135
+ hospitalName: (dept as any)?.hospital?.name || '',
136
+ }}
137
+ devices={deptDevices.map((d) => ({
138
+ id: d.id,
139
+ name: d.name,
140
+ assetNumber: d.assetNumber,
141
+ currentStatusCode: d.currentStatusCode,
142
+ locationName:
143
+ (d as any).location?.name || d.exactLocationDescription || '',
144
+ categoryName: (d as any).deviceCategory?.name || '',
145
+ manufacturerName: (d as any).manufacturer?.name || '',
146
+ activeTickets: ticketsByDevice[d.id] || [],
147
+ }))}
148
+ stats={stats}
149
+ />
150
+ );
151
+ }
@@ -0,0 +1,103 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Card, CardContent } from '@/components/ui/card';
5
+ import { Button } from '@/components/ui/button';
6
+ import { NotificationItem } from '@/components/notifications/notification-item';
7
+ import { markAsRead, markAllAsRead } from '@/lib/actions/notifications';
8
+ import { Bell, CheckCheck, Filter } from 'lucide-react';
9
+
10
+ export function NotificationList({ initialItems }: { initialItems: any[] }) {
11
+ const [items, setItems] = useState<any[]>(initialItems);
12
+ const [filterUnreadOnly, setFilterUnreadOnly] = useState(false);
13
+
14
+ const handleMarkRead = async (id: string) => {
15
+ await markAsRead(id);
16
+ setItems((prev) =>
17
+ prev.map((item) =>
18
+ item.id === id ? { ...item, readAt: new Date() } : item
19
+ )
20
+ );
21
+ };
22
+
23
+ const handleMarkAllRead = async () => {
24
+ await markAllAsRead();
25
+ setItems((prev) =>
26
+ prev.map((item) => ({ ...item, readAt: item.readAt || new Date() }))
27
+ );
28
+ };
29
+
30
+ const filteredItems = filterUnreadOnly
31
+ ? items.filter((item) => !item.readAt)
32
+ : items;
33
+
34
+ const unreadCount = items.filter((item) => !item.readAt).length;
35
+
36
+ return (
37
+ <div className="space-y-4">
38
+ <div className="flex flex-wrap items-center justify-between gap-2 pb-2">
39
+ <div className="flex items-center gap-2">
40
+ <Button
41
+ variant={filterUnreadOnly ? 'default' : 'outline'}
42
+ size="sm"
43
+ onClick={() => setFilterUnreadOnly(!filterUnreadOnly)}
44
+ className="text-xs h-8 min-h-[36px]"
45
+ >
46
+ <Filter className="h-3.5 w-3.5 mr-1" />
47
+ {filterUnreadOnly ? 'Showing Unread' : 'All Notifications'}
48
+ </Button>
49
+ {unreadCount > 0 && (
50
+ <span className="text-xs text-muted-foreground">
51
+ {unreadCount} unread
52
+ </span>
53
+ )}
54
+ </div>
55
+
56
+ {unreadCount > 0 && (
57
+ <Button
58
+ variant="ghost"
59
+ size="sm"
60
+ onClick={handleMarkAllRead}
61
+ className="text-xs h-8 min-h-[36px] gap-1"
62
+ >
63
+ <CheckCheck className="h-3.5 w-3.5" />
64
+ Mark all read
65
+ </Button>
66
+ )}
67
+ </div>
68
+
69
+ {filteredItems.length === 0 ? (
70
+ <Card>
71
+ <CardContent className="py-12 text-center">
72
+ <Bell className="h-10 w-10 mx-auto text-muted-foreground/50 mb-3" />
73
+ <h3 className="font-medium text-base">No notifications</h3>
74
+ <p className="text-sm text-muted-foreground mt-1">
75
+ {filterUnreadOnly
76
+ ? 'You have caught up with all unread notifications.'
77
+ : 'You do not have any notifications yet.'}
78
+ </p>
79
+ </CardContent>
80
+ </Card>
81
+ ) : (
82
+ <Card>
83
+ <CardContent className="p-2 sm:p-4 divide-y">
84
+ {filteredItems.map((item) => (
85
+ <NotificationItem
86
+ key={item.id}
87
+ id={item.id}
88
+ notificationType={item.notificationType}
89
+ title={item.title}
90
+ body={item.body}
91
+ actionUrl={item.actionUrl}
92
+ readAt={item.readAt}
93
+ createdAt={item.createdAt}
94
+ onMarkRead={handleMarkRead}
95
+ />
96
+ ))}
97
+ </CardContent>
98
+ </Card>
99
+ )}
100
+ </div>
101
+ );
102
+ }
103
+