@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,49 @@
1
+ import { z } from 'zod';
2
+
3
+ export const signatureRequirementSchema = z.object({
4
+ purpose: z.enum(['accept', 'perform', 'review', 'approve', 'release', 'resolve', 'close']),
5
+ allowedRoles: z.array(z.string()).min(1, 'At least one role must be allowed'),
6
+ required: z.boolean(),
7
+ });
8
+
9
+ export const signaturePolicyActionSchema = z.enum([
10
+ 'complete_corrective',
11
+ 'complete_pm',
12
+ 'complete_calibration',
13
+ 'complete_testing',
14
+ 'release_device',
15
+ 'change_device_status',
16
+ 'resolve_ticket',
17
+ 'close_ticket',
18
+ 'approve_exception',
19
+ ]);
20
+
21
+ export const signaturePolicySchema = z.object({
22
+ id: z.string().uuid().optional(),
23
+ organizationId: z.string().uuid(),
24
+ hospitalId: z.string().uuid().nullable().optional(),
25
+ actionType: signaturePolicyActionSchema,
26
+ criticalityLevel: z.enum(['critical', 'high', 'medium', 'low']).nullable().optional(),
27
+ deviceCategoryId: z.string().uuid().nullable().optional(),
28
+ requiredSignatures: z.array(signatureRequirementSchema).min(1, 'At least one signature requirement is required'),
29
+ requireIndependentReview: z.boolean().default(false),
30
+ performerCanRelease: z.boolean().default(true),
31
+ requireReauth: z.boolean().default(true),
32
+ effectiveDate: z.date().or(z.string()).optional(),
33
+ isActive: z.boolean().default(true),
34
+ policyVersion: z.string().default('1.0'),
35
+ });
36
+
37
+ export const createSignaturePolicySchema = signaturePolicySchema.omit({
38
+ id: true,
39
+ organizationId: true,
40
+ });
41
+
42
+ export const updateSignaturePolicySchema = createSignaturePolicySchema.partial();
43
+
44
+ export const signActionSchema = z.object({
45
+ signaturePurpose: z.enum(['accept', 'perform', 'review', 'approve', 'release', 'resolve', 'close', 'reject', 'amend', 'void']),
46
+ password: z.string().min(1, 'Password is required for signature authentication'),
47
+ comments: z.string().max(2000).optional(),
48
+ });
49
+
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+
3
+ export const localizationSettingsSchema = z.object({
4
+ timezone: z.string().min(1, 'Timezone is required').default('UTC'),
5
+ dateFormat: z.enum(['YYYY-MM-DD', 'DD/MM/YYYY', 'MM/DD/YYYY']).default('YYYY-MM-DD'),
6
+ timeFormat: z.enum(['24h', '12h']).default('24h'),
7
+ language: z.enum(['en', 'ar']).default('en'),
8
+ });
9
+
10
+ export const securitySettingsSchema = z.object({
11
+ sessionTimeoutMinutes: z.coerce.number().int().min(5).max(1440).default(30),
12
+ passwordExpiryDays: z.coerce.number().int().min(0).max(365).default(90),
13
+ requireReAuthForSignatures: z.boolean().default(true),
14
+ maxLoginAttempts: z.coerce.number().int().min(3).max(10).default(5),
15
+ });
16
+
17
+ export const retentionSettingsSchema = z.object({
18
+ auditLogRetentionDays: z.coerce.number().int().min(30).max(3650).default(365), // 1 year default
19
+ pmAlertWindowDays: z.coerce.number().int().min(1).max(90).default(14), // 14 days lead time
20
+ attachmentQuotaMb: z.coerce.number().int().min(100).max(100000).default(5000), // 5GB
21
+ });
22
+
23
+ export const featuresSettingsSchema = z.object({
24
+ engineerRoundEnabled: z.boolean().default(false),
25
+ });
26
+
27
+ export const systemSettingsSchema = z.object({
28
+ localization: localizationSettingsSchema.default({
29
+ timezone: 'UTC',
30
+ dateFormat: 'YYYY-MM-DD',
31
+ timeFormat: '24h',
32
+ language: 'en',
33
+ }),
34
+ security: securitySettingsSchema.default({
35
+ sessionTimeoutMinutes: 30,
36
+ passwordExpiryDays: 90,
37
+ requireReAuthForSignatures: true,
38
+ maxLoginAttempts: 5,
39
+ }),
40
+ retention: retentionSettingsSchema.default({
41
+ auditLogRetentionDays: 365,
42
+ pmAlertWindowDays: 14,
43
+ attachmentQuotaMb: 5000,
44
+ }),
45
+ features: featuresSettingsSchema.default({
46
+ engineerRoundEnabled: false,
47
+ }),
48
+ });
49
+
50
+ export type SystemSettings = z.infer<typeof systemSettingsSchema>;
51
+ export type LocalizationSettings = z.infer<typeof localizationSettingsSchema>;
52
+ export type SecuritySettings = z.infer<typeof securitySettingsSchema>;
53
+ export type RetentionSettings = z.infer<typeof retentionSettingsSchema>;
54
+ export type FeaturesSettings = z.infer<typeof featuresSettingsSchema>;
55
+
@@ -0,0 +1,125 @@
1
+ import { z } from 'zod';
2
+
3
+ export const createTicketSchema = z.object({
4
+ organizationId: z.string().uuid(),
5
+ deviceId: z.string().uuid().optional(),
6
+ hospitalId: z.string().uuid(),
7
+ departmentId: z.string().uuid(),
8
+ locationId: z.string().uuid().optional(),
9
+ source: z.enum(['qr_scan', 'department_device_selection', 'device_profile', 'manual_entry', 'import']),
10
+ ticketType: z.enum([
11
+ 'device_problem', 'urgent_equipment_concern', 'maintenance_request',
12
+ 'inspection_request', 'calibration_request', 'pm_followup'
13
+ ]).default('device_problem'),
14
+ title: z.string().min(5).max(200),
15
+ description: z.string().min(10).max(2000),
16
+ reportedImpact: z.enum(['device_usable', 'device_not_usable', 'patient_care_affected']),
17
+ reportedProblemCategory: z.enum([
18
+ 'power_issue', 'device_not_starting', 'display_interface', 'alarm_problem',
19
+ 'sensor_accessory', 'electrical_concern', 'mechanical_damage',
20
+ 'software_configuration', 'performance_concern', 'maintenance_request', 'other'
21
+ ]).optional(),
22
+ reportedLocationCorrection: z.string().max(200).optional(),
23
+ requesterContactName: z.string().max(100).optional(),
24
+ requesterContactPhone: z.string().max(30).optional(),
25
+ roundVisitId: z.string().uuid().optional(),
26
+ });
27
+
28
+ export const triageTicketSchema = z.object({
29
+ priorityCode: z.enum(['p1_critical', 'p2_high', 'p3_normal', 'p4_low']),
30
+ priorityReason: z.string().max(500).optional(),
31
+ selectedMaintenanceType: z.enum([
32
+ 'preventive_maintenance', 'corrective_maintenance', 'troubleshooting',
33
+ 'inspection', 'calibration', 'electrical_safety_testing',
34
+ 'performance_testing', 'installation_commissioning',
35
+ 'software_configuration', 'decommissioning'
36
+ ]).optional(),
37
+ selectedTechnicalCategory: z.enum([
38
+ 'electrical_power', 'display_user_interface', 'alarm_safety',
39
+ 'sensor_measurement', 'mechanical', 'software_configuration',
40
+ 'performance_output', 'accessory_consumable', 'physical_damage',
41
+ 'communication_network', 'no_fault_found', 'other'
42
+ ]).optional(),
43
+ assignedEngineerUserId: z.string().uuid().optional(),
44
+ internalTriageNote: z.string().max(1000).optional(),
45
+ });
46
+
47
+ export const assignTicketSchema = z.object({
48
+ assignedEngineerUserId: z.string().uuid(),
49
+ assignedTeamId: z.string().uuid().optional(),
50
+ handoffNote: z.string().max(500).optional(),
51
+ });
52
+
53
+ export const acceptTicketSchema = z.object({
54
+ signaturePassword: z.string().optional(),
55
+ signatureComments: z.string().max(500).optional(),
56
+ note: z.string().max(500).optional(),
57
+ }).optional().default({});
58
+
59
+ export const updateTicketDeviceStatusSchema = z.object({
60
+ deviceStatusCode: z.enum([
61
+ 'operational', 'operational_with_limitations', 'under_maintenance',
62
+ 'under_repair', 'waiting_for_parts', 'awaiting_release', 'out_of_service', 'standby'
63
+ ]),
64
+ notes: z.string().min(3, 'Please provide an update note (minimum 3 characters)').max(1000),
65
+ partName: z.string().max(200).optional(),
66
+ supplierDetails: z.string().max(500).optional(),
67
+ limitationsNote: z.string().max(1000).optional(),
68
+ });
69
+
70
+ export const RESOLUTION_DEVICE_STATUSES = [
71
+ 'operational', 'operational_with_limitations', 'awaiting_release',
72
+ 'standby', 'out_of_service', 'decommissioned'
73
+ ] as const;
74
+
75
+ export const resolveTicketSchema = z.object({
76
+ resolutionSummary: z.string().min(10, 'Resolution summary must be at least 10 characters').max(2000),
77
+ finalDeviceStatusCode: z.enum(RESOLUTION_DEVICE_STATUSES),
78
+ limitationsNote: z.string().max(1000).optional(),
79
+ signaturePassword: z.string().min(1, 'Password is required to sign ticket resolution').optional(),
80
+ signatureComments: z.string().max(500).optional(),
81
+ });
82
+
83
+ export const closeTicketSchema = z.object({
84
+ closureReason: z.string().max(500).optional(),
85
+ signaturePassword: z.string().min(1, 'Password is required to sign ticket closure').optional(),
86
+ signatureComments: z.string().max(500).optional(),
87
+ });
88
+
89
+ export const cancelTicketSchema = z.object({
90
+ cancellationReason: z.string().min(5).max(500),
91
+ });
92
+
93
+ export const reopenTicketSchema = z.object({
94
+ reopenReason: z.string().min(5).max(500),
95
+ });
96
+
97
+ export const createTicketCommentSchema = z.object({
98
+ body: z.string().min(1).max(5000),
99
+ visibility: z.enum(['public', 'internal']).default('public'),
100
+ });
101
+
102
+ export const ticketStatusTransitionSchema = z.object({
103
+ targetStatus: z.enum([
104
+ 'new', 'acknowledged', 'in_triage', 'in_progress',
105
+ 'waiting_requester', 'waiting_parts_vendor',
106
+ 'resolved', 'closed', 'cancelled'
107
+ ]),
108
+ reason: z.string().max(500).optional(),
109
+ waitingDependencyType: z.string().max(100).optional(),
110
+ });
111
+
112
+ export const ticketFilterSchema = z.object({
113
+ search: z.string().optional(),
114
+ organizationId: z.string().uuid().optional(),
115
+ hospitalId: z.string().uuid().optional(),
116
+ departmentId: z.string().uuid().optional(),
117
+ statusCode: z.string().optional(),
118
+ priorityCode: z.string().optional(),
119
+ assignedEngineerUserId: z.string().uuid().optional(),
120
+ reportedByUserId: z.string().uuid().optional(),
121
+ deviceId: z.string().uuid().optional(),
122
+ page: z.number().int().positive().optional(),
123
+ pageSize: z.number().int().positive().max(100).optional(),
124
+ });
125
+
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+
3
+ export const inviteUserSchema = z.object({
4
+ fullName: z.string().min(2).max(100),
5
+ email: z.string().email(),
6
+ username: z.string().min(2).max(50).optional().nullable(),
7
+ jobTitle: z.string().optional(),
8
+ employeeIdentifier: z.string().optional(),
9
+ phone: z.string().optional(),
10
+ password: z.string().min(6).optional().nullable(),
11
+ organizationId: z.string().uuid(),
12
+ roleIds: z.array(z.string().uuid()),
13
+ hospitalId: z.string().uuid().optional().nullable(),
14
+ departmentId: z.string().uuid().optional().nullable(),
15
+ specialization: z.string().optional().nullable(),
16
+ dutyStatus: z.enum(['on_duty', 'on_call', 'in_maintenance', 'off_duty']).optional().nullable(),
17
+ });
18
+
19
+ export const addUserSchema = inviteUserSchema;
20
+
21
+ export const updateUserProfileSchema = z.object({
22
+ fullName: z.string().min(2).max(100).optional(),
23
+ phone: z.string().optional(),
24
+ jobTitle: z.string().optional(),
25
+ employeeIdentifier: z.string().optional(),
26
+ });
27
+
28
+ export const assignRoleSchema = z.object({
29
+ userId: z.string().uuid(),
30
+ roleId: z.string().uuid(),
31
+ effectiveFrom: z.date().optional(),
32
+ effectiveTo: z.date().optional(),
33
+ });
34
+
35
+ export const assignScopeSchema = z.object({
36
+ userId: z.string().uuid(),
37
+ organizationId: z.string().uuid(),
38
+ scopeType: z.enum(['organization', 'hospital', 'department', 'location']),
39
+ hospitalId: z.string().uuid().optional(),
40
+ departmentId: z.string().uuid().optional(),
41
+ locationId: z.string().uuid().optional(),
42
+ });
43
+
@@ -0,0 +1,45 @@
1
+ import { NextResponse, type NextRequest } from 'next/server';
2
+
3
+ export function middleware(req: NextRequest) {
4
+ // Check for NextAuth / Auth.js session cookies (Edge-compatible, no pg/crypto imports)
5
+ const sessionToken =
6
+ req.cookies.get('authjs.session-token')?.value ||
7
+ req.cookies.get('__Secure-authjs.session-token')?.value ||
8
+ req.cookies.get('next-auth.session-token')?.value ||
9
+ req.cookies.get('__Secure-next-auth.session-token')?.value;
10
+
11
+ const isLoggedIn = !!sessionToken;
12
+ const { nextUrl } = req;
13
+
14
+ const isApiAuthRoute = nextUrl.pathname.startsWith('/api/auth');
15
+ const isPublicRoute = nextUrl.pathname === '/login' || nextUrl.pathname.startsWith('/scan');
16
+
17
+ if (isApiAuthRoute) {
18
+ return NextResponse.next();
19
+ }
20
+
21
+ if (isPublicRoute) {
22
+ if (isLoggedIn && nextUrl.pathname === '/login') {
23
+ return NextResponse.redirect(new URL('/dashboard', nextUrl));
24
+ }
25
+ return NextResponse.next();
26
+ }
27
+
28
+ if (!isLoggedIn) {
29
+ let callbackUrl = nextUrl.pathname;
30
+ if (nextUrl.search) {
31
+ callbackUrl += nextUrl.search;
32
+ }
33
+ const encodedCallbackUrl = encodeURIComponent(callbackUrl);
34
+ return NextResponse.redirect(new URL(`/login?callbackUrl=${encodedCallbackUrl}`, nextUrl));
35
+ }
36
+
37
+ return NextResponse.next();
38
+ }
39
+
40
+ export const config = {
41
+ matcher: [
42
+ '/((?!api|_next/static|_next/image|favicon.ico|manifest.webmanifest|sw.js|icons).*)',
43
+ ],
44
+ };
45
+
@@ -0,0 +1,76 @@
1
+ import type { Config } from "tailwindcss";
2
+ import tailwindcssAnimate from "tailwindcss-animate";
3
+
4
+ const config: Config = {
5
+ darkMode: ["class"],
6
+ content: [
7
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
8
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
9
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
10
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
11
+ ],
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ background: 'hsl(var(--background))',
16
+ foreground: 'hsl(var(--foreground))',
17
+ card: {
18
+ DEFAULT: 'hsl(var(--card))',
19
+ foreground: 'hsl(var(--card-foreground))'
20
+ },
21
+ popover: {
22
+ DEFAULT: 'hsl(var(--popover))',
23
+ foreground: 'hsl(var(--popover-foreground))'
24
+ },
25
+ primary: {
26
+ DEFAULT: 'hsl(var(--primary))',
27
+ foreground: 'hsl(var(--primary-foreground))'
28
+ },
29
+ secondary: {
30
+ DEFAULT: 'hsl(var(--secondary))',
31
+ foreground: 'hsl(var(--secondary-foreground))'
32
+ },
33
+ muted: {
34
+ DEFAULT: 'hsl(var(--muted))',
35
+ foreground: 'hsl(var(--muted-foreground))'
36
+ },
37
+ accent: {
38
+ DEFAULT: 'hsl(var(--accent))',
39
+ foreground: 'hsl(var(--accent-foreground))'
40
+ },
41
+ destructive: {
42
+ DEFAULT: 'hsl(var(--destructive))',
43
+ foreground: 'hsl(var(--destructive-foreground))'
44
+ },
45
+ border: 'hsl(var(--border))',
46
+ input: 'hsl(var(--input))',
47
+ ring: 'hsl(var(--ring))',
48
+ chart: {
49
+ '1': 'hsl(var(--chart-1))',
50
+ '2': 'hsl(var(--chart-2))',
51
+ '3': 'hsl(var(--chart-3))',
52
+ '4': 'hsl(var(--chart-4))',
53
+ '5': 'hsl(var(--chart-5))'
54
+ },
55
+ sidebar: {
56
+ DEFAULT: 'hsl(var(--sidebar-background))',
57
+ foreground: 'hsl(var(--sidebar-foreground))',
58
+ primary: 'hsl(var(--sidebar-primary))',
59
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
60
+ accent: 'hsl(var(--sidebar-accent))',
61
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
62
+ border: 'hsl(var(--sidebar-border))',
63
+ ring: 'hsl(var(--sidebar-ring))'
64
+ }
65
+ },
66
+ borderRadius: {
67
+ lg: 'var(--radius)',
68
+ md: 'calc(var(--radius) - 2px)',
69
+ sm: 'calc(var(--radius) - 4px)'
70
+ }
71
+ }
72
+ },
73
+ plugins: [tailwindcssAnimate],
74
+ };
75
+ export default config;
76
+
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src"],
26
+ "exclude": ["node_modules"]
27
+ }
28
+