@omersx/bemms 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +203 -2
  3. package/drizzle.config.ts +11 -0
  4. package/next.config.ts +51 -0
  5. package/package.json +99 -3
  6. package/postcss.config.mjs +7 -0
  7. package/public/hospital_demo_devices.csv +32 -0
  8. package/public/icons/icon-192.png +0 -0
  9. package/public/icons/icon-512.png +0 -0
  10. package/public/icons/icon-maskable-512.png +0 -0
  11. package/public/manifest.webmanifest +31 -0
  12. package/public/sw.js +177 -0
  13. package/src/app/(auth)/layout.tsx +20 -0
  14. package/src/app/(auth)/login/page.tsx +135 -0
  15. package/src/app/(dashboard)/admin/audit-logs/page.tsx +178 -0
  16. package/src/app/(dashboard)/admin/data/page.tsx +37 -0
  17. package/src/app/(dashboard)/admin/departments/[deptId]/edit/page.tsx +206 -0
  18. package/src/app/(dashboard)/admin/departments/[deptId]/page.tsx +71 -0
  19. package/src/app/(dashboard)/admin/departments/new/page.tsx +6 -0
  20. package/src/app/(dashboard)/admin/departments/page.tsx +6 -0
  21. package/src/app/(dashboard)/admin/general/page.tsx +374 -0
  22. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/edit/page.tsx +304 -0
  23. package/src/app/(dashboard)/admin/hospitals/[hospitalId]/page.tsx +94 -0
  24. package/src/app/(dashboard)/admin/hospitals/new/page.tsx +302 -0
  25. package/src/app/(dashboard)/admin/hospitals/page.tsx +6 -0
  26. package/src/app/(dashboard)/admin/layout.tsx +49 -0
  27. package/src/app/(dashboard)/admin/organizations/[orgId]/edit/page.tsx +172 -0
  28. package/src/app/(dashboard)/admin/organizations/[orgId]/page.tsx +82 -0
  29. package/src/app/(dashboard)/admin/organizations/new/page.tsx +150 -0
  30. package/src/app/(dashboard)/admin/organizations/page.tsx +6 -0
  31. package/src/app/(dashboard)/admin/page.tsx +258 -0
  32. package/src/app/(dashboard)/admin/roles/page.tsx +153 -0
  33. package/src/app/(dashboard)/admin/settings/page.tsx +69 -0
  34. package/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +153 -0
  35. package/src/app/(dashboard)/admin/users/[userId]/page.tsx +317 -0
  36. package/src/app/(dashboard)/admin/users/invite/page.tsx +3 -0
  37. package/src/app/(dashboard)/admin/users/new/page.tsx +5 -0
  38. package/src/app/(dashboard)/admin/users/page.tsx +23 -0
  39. package/src/app/(dashboard)/dashboard/page.tsx +462 -0
  40. package/src/app/(dashboard)/departments/[deptId]/page.tsx +39 -0
  41. package/src/app/(dashboard)/departments/new/page.tsx +330 -0
  42. package/src/app/(dashboard)/departments/page.tsx +52 -0
  43. package/src/app/(dashboard)/devices/[id]/edit/page.tsx +131 -0
  44. package/src/app/(dashboard)/devices/[id]/page.tsx +408 -0
  45. package/src/app/(dashboard)/devices/[id]/qr/page.tsx +54 -0
  46. package/src/app/(dashboard)/devices/new/page.tsx +382 -0
  47. package/src/app/(dashboard)/devices/page.tsx +80 -0
  48. package/src/app/(dashboard)/engineering-team/page.tsx +41 -0
  49. package/src/app/(dashboard)/layout.tsx +43 -0
  50. package/src/app/(dashboard)/maintenance/checklists/page.tsx +57 -0
  51. package/src/app/(dashboard)/maintenance/page.tsx +231 -0
  52. package/src/app/(dashboard)/maintenance/plans/page.tsx +142 -0
  53. package/src/app/(dashboard)/maintenance/tasks/[id]/page.tsx +997 -0
  54. package/src/app/(dashboard)/maintenance/tasks/page.tsx +119 -0
  55. package/src/app/(dashboard)/more/page.tsx +310 -0
  56. package/src/app/(dashboard)/my-department/page.tsx +151 -0
  57. package/src/app/(dashboard)/notifications/notification-list.tsx +103 -0
  58. package/src/app/(dashboard)/notifications/page.tsx +59 -0
  59. package/src/app/(dashboard)/profile/page.tsx +27 -0
  60. package/src/app/(dashboard)/reports/costs/page.tsx +127 -0
  61. package/src/app/(dashboard)/reports/history/page.tsx +105 -0
  62. package/src/app/(dashboard)/reports/inventory/page.tsx +116 -0
  63. package/src/app/(dashboard)/reports/maintenance/page.tsx +136 -0
  64. package/src/app/(dashboard)/reports/page.tsx +120 -0
  65. package/src/app/(dashboard)/reports/tickets/page.tsx +139 -0
  66. package/src/app/(dashboard)/reports/workload/page.tsx +135 -0
  67. package/src/app/(dashboard)/rounds/[id]/page.tsx +197 -0
  68. package/src/app/(dashboard)/rounds/history/page.tsx +82 -0
  69. package/src/app/(dashboard)/rounds/page.tsx +107 -0
  70. package/src/app/(dashboard)/scan/[code]/page.tsx +154 -0
  71. package/src/app/(dashboard)/scan/page.tsx +73 -0
  72. package/src/app/(dashboard)/team/page.tsx +42 -0
  73. package/src/app/(dashboard)/tickets/[id]/page.tsx +465 -0
  74. package/src/app/(dashboard)/tickets/create/page.tsx +722 -0
  75. package/src/app/(dashboard)/tickets/page.tsx +55 -0
  76. package/src/app/(dashboard)/tickets/triage/page.tsx +161 -0
  77. package/src/app/api/admin/data/export/route.ts +316 -0
  78. package/src/app/api/auth/[...nextauth]/route.ts +3 -0
  79. package/src/app/api/health/route.ts +46 -0
  80. package/src/app/api/notifications/sse/route.ts +91 -0
  81. package/src/app/api/notifications/subscribe/route.ts +54 -0
  82. package/src/app/api/notifications/vapid-key/route.ts +7 -0
  83. package/src/app/api/reports/export/route.ts +102 -0
  84. package/src/app/api/tickets/[id]/attachments/route.ts +129 -0
  85. package/src/app/api/tickets/[id]/live-sync/route.ts +52 -0
  86. package/src/app/error.tsx +54 -0
  87. package/src/app/globals.css +89 -0
  88. package/src/app/layout.tsx +59 -0
  89. package/src/app/not-found.tsx +32 -0
  90. package/src/app/page.tsx +5 -0
  91. package/src/components/admin/add-department-user-dialog.tsx +759 -0
  92. package/src/components/admin/add-engineer-dialog.tsx +773 -0
  93. package/src/components/admin/add-user-type-dialog.tsx +89 -0
  94. package/src/components/admin/audit-log-export.tsx +113 -0
  95. package/src/components/admin/audit-log-filters.tsx +255 -0
  96. package/src/components/admin/audit-log-table.tsx +383 -0
  97. package/src/components/admin/data-management/danger-zone.tsx +301 -0
  98. package/src/components/admin/data-management/data-management-tabs.tsx +61 -0
  99. package/src/components/admin/data-management/export-center.tsx +301 -0
  100. package/src/components/admin/data-management/import-wizard.tsx +558 -0
  101. package/src/components/admin/settings-forms/features-form.tsx +115 -0
  102. package/src/components/admin/settings-forms/health-panel.tsx +182 -0
  103. package/src/components/admin/settings-forms/localization-form.tsx +203 -0
  104. package/src/components/admin/settings-forms/retention-form.tsx +164 -0
  105. package/src/components/admin/settings-forms/security-form.tsx +186 -0
  106. package/src/components/admin/system-settings-tabs.tsx +88 -0
  107. package/src/components/admin/user-role-dialog.tsx +293 -0
  108. package/src/components/admin/user-status-dialog.tsx +166 -0
  109. package/src/components/admin/users-page-client.tsx +514 -0
  110. package/src/components/dashboard/action-buttons.tsx +93 -0
  111. package/src/components/dashboard/admin-alerts.tsx +86 -0
  112. package/src/components/dashboard/availability-alerts.tsx +65 -0
  113. package/src/components/dashboard/maintenance-due-list.tsx +87 -0
  114. package/src/components/dashboard/round-widget.tsx +110 -0
  115. package/src/components/dashboard/stat-card.tsx +38 -0
  116. package/src/components/dashboard/ticket-list-compact.tsx +86 -0
  117. package/src/components/departments/department-cards-view.tsx +244 -0
  118. package/src/components/departments/department-detail-view.tsx +467 -0
  119. package/src/components/departments/department-visuals.tsx +190 -0
  120. package/src/components/departments/my-department-view.tsx +333 -0
  121. package/src/components/devices/change-status-dialog.tsx +146 -0
  122. package/src/components/devices/device-action-buttons.tsx +125 -0
  123. package/src/components/devices/device-availability-banner.tsx +59 -0
  124. package/src/components/devices/device-badges.tsx +55 -0
  125. package/src/components/devices/device-documents-tab.tsx +93 -0
  126. package/src/components/devices/device-maintenance-tab.tsx +113 -0
  127. package/src/components/devices/device-mini-qr.tsx +109 -0
  128. package/src/components/devices/device-qr-card.tsx +187 -0
  129. package/src/components/devices/device-signatures-tab.tsx +607 -0
  130. package/src/components/devices/device-tickets-tab.tsx +139 -0
  131. package/src/components/devices/device-timelines.tsx +124 -0
  132. package/src/components/devices/qr-camera-scanner.tsx +167 -0
  133. package/src/components/devices/qr-code-display.tsx +139 -0
  134. package/src/components/devices/quick-add-category-dialog.tsx +135 -0
  135. package/src/components/devices/quick-add-manufacturer-dialog.tsx +212 -0
  136. package/src/components/devices/transfer-device-dialog.tsx +190 -0
  137. package/src/components/layout/header.tsx +80 -0
  138. package/src/components/layout/mobile-nav.tsx +91 -0
  139. package/src/components/layout/more-item-badge.tsx +15 -0
  140. package/src/components/layout/nav-badge.tsx +72 -0
  141. package/src/components/layout/sidebar.tsx +297 -0
  142. package/src/components/maintenance/maintenance-badges.tsx +579 -0
  143. package/src/components/maintenance/maintenance-nav.tsx +107 -0
  144. package/src/components/maintenance/parts-costs-manager.tsx +909 -0
  145. package/src/components/maintenance/signature-card.tsx +413 -0
  146. package/src/components/maintenance/task-action-bar.tsx +955 -0
  147. package/src/components/notifications/notification-bell.tsx +367 -0
  148. package/src/components/notifications/notification-item.tsx +93 -0
  149. package/src/components/notifications/notification-preferences.tsx +182 -0
  150. package/src/components/profile/user-profile-view.tsx +626 -0
  151. package/src/components/providers/nav-counts-provider.tsx +235 -0
  152. package/src/components/providers/session-provider.tsx +7 -0
  153. package/src/components/providers/theme-provider.tsx +124 -0
  154. package/src/components/pwa/install-prompt.tsx +82 -0
  155. package/src/components/pwa/sw-register.tsx +43 -0
  156. package/src/components/reports/export-button.tsx +62 -0
  157. package/src/components/reports/filter-chips.tsx +47 -0
  158. package/src/components/reports/summary-cards.tsx +40 -0
  159. package/src/components/reports/transparency-header.tsx +54 -0
  160. package/src/components/rounds/department-visit-sheet.tsx +199 -0
  161. package/src/components/rounds/mention-textarea.tsx +187 -0
  162. package/src/components/rounds/round-calendar.tsx +233 -0
  163. package/src/components/rounds/round-department-list.tsx +281 -0
  164. package/src/components/rounds/round-history-client.tsx +218 -0
  165. package/src/components/rounds/start-round-card.tsx +184 -0
  166. package/src/components/shared/confirm-dialog.tsx +67 -0
  167. package/src/components/shared/data-table.tsx +104 -0
  168. package/src/components/shared/page-header.tsx +20 -0
  169. package/src/components/shared/sign-out-button.tsx +52 -0
  170. package/src/components/shared/status-badge.tsx +42 -0
  171. package/src/components/shared/table-search.tsx +55 -0
  172. package/src/components/signatures/sign-action-dialog.tsx +253 -0
  173. package/src/components/signatures/signature-history.tsx +121 -0
  174. package/src/components/signatures/signature-status-panel.tsx +164 -0
  175. package/src/components/team/department-coverage.tsx +141 -0
  176. package/src/components/team/edit-engineer-dialog.tsx +242 -0
  177. package/src/components/team/engineer-card.tsx +180 -0
  178. package/src/components/team/engineer-details-dialog.tsx +432 -0
  179. package/src/components/team/schedule-calendar.tsx +204 -0
  180. package/src/components/team/staff-engineer-team-view.tsx +368 -0
  181. package/src/components/team/team-header-stats.tsx +88 -0
  182. package/src/components/team/team-page-client.tsx +216 -0
  183. package/src/components/theme/theme-selector-card.tsx +63 -0
  184. package/src/components/theme/theme-toggle.tsx +65 -0
  185. package/src/components/tickets/staff-status-label.tsx +58 -0
  186. package/src/components/tickets/staff-ticket-card.tsx +109 -0
  187. package/src/components/tickets/staff-ticket-progress-tracker.tsx +405 -0
  188. package/src/components/tickets/staff-tickets-view.tsx +182 -0
  189. package/src/components/tickets/ticket-action-buttons.tsx +873 -0
  190. package/src/components/tickets/ticket-auto-refresh.tsx +197 -0
  191. package/src/components/tickets/ticket-badges.tsx +43 -0
  192. package/src/components/tickets/ticket-card.tsx +197 -0
  193. package/src/components/tickets/ticket-cards-view.tsx +395 -0
  194. package/src/components/tickets/ticket-comment-section.tsx +684 -0
  195. package/src/components/tickets/ticket-dispatch-status.tsx +184 -0
  196. package/src/components/tickets/ticket-list-auto-refresh.tsx +55 -0
  197. package/src/components/tickets/ticket-reporter-profile.tsx +235 -0
  198. package/src/components/tickets/ticket-signatures-panel.tsx +32 -0
  199. package/src/components/tickets/ticket-timeline.tsx +131 -0
  200. package/src/components/tickets/ticket-triage-panel.tsx +124 -0
  201. package/src/components/tickets/tickets-nav.tsx +107 -0
  202. package/src/components/ui/avatar.tsx +43 -0
  203. package/src/components/ui/badge.tsx +34 -0
  204. package/src/components/ui/button.tsx +58 -0
  205. package/src/components/ui/card.tsx +77 -0
  206. package/src/components/ui/checkbox.tsx +31 -0
  207. package/src/components/ui/dialog.tsx +123 -0
  208. package/src/components/ui/dropdown-menu.tsx +126 -0
  209. package/src/components/ui/form.tsx +179 -0
  210. package/src/components/ui/input.tsx +24 -0
  211. package/src/components/ui/label.tsx +27 -0
  212. package/src/components/ui/popover.tsx +34 -0
  213. package/src/components/ui/progress.tsx +36 -0
  214. package/src/components/ui/scroll-area.tsx +47 -0
  215. package/src/components/ui/select.tsx +160 -0
  216. package/src/components/ui/separator.tsx +25 -0
  217. package/src/components/ui/sheet.tsx +131 -0
  218. package/src/components/ui/switch.tsx +30 -0
  219. package/src/components/ui/table.tsx +121 -0
  220. package/src/components/ui/tabs.tsx +62 -0
  221. package/src/components/ui/textarea.tsx +23 -0
  222. package/src/components/ui/tooltip.tsx +27 -0
  223. package/src/lib/actions/__tests__/devices.test.ts +176 -0
  224. package/src/lib/actions/__tests__/signatures.test.ts +303 -0
  225. package/src/lib/actions/__tests__/tickets.test.ts +243 -0
  226. package/src/lib/actions/audit-logs.ts +298 -0
  227. package/src/lib/actions/checklists.ts +429 -0
  228. package/src/lib/actions/dashboard.ts +358 -0
  229. package/src/lib/actions/data-management.ts +723 -0
  230. package/src/lib/actions/departments.ts +369 -0
  231. package/src/lib/actions/device-categories.ts +117 -0
  232. package/src/lib/actions/devices.ts +748 -0
  233. package/src/lib/actions/engineer-rounds.ts +973 -0
  234. package/src/lib/actions/engineers.ts +285 -0
  235. package/src/lib/actions/hospitals.ts +148 -0
  236. package/src/lib/actions/locations.ts +115 -0
  237. package/src/lib/actions/maintenance-plans.ts +316 -0
  238. package/src/lib/actions/maintenance-tasks.ts +415 -0
  239. package/src/lib/actions/manufacturers.ts +132 -0
  240. package/src/lib/actions/nav-counts.ts +144 -0
  241. package/src/lib/actions/notifications.ts +136 -0
  242. package/src/lib/actions/organizations.ts +99 -0
  243. package/src/lib/actions/qr.ts +147 -0
  244. package/src/lib/actions/report-history.ts +77 -0
  245. package/src/lib/actions/reports.ts +192 -0
  246. package/src/lib/actions/roles.ts +32 -0
  247. package/src/lib/actions/signature-auth.ts +377 -0
  248. package/src/lib/actions/signatures.ts +532 -0
  249. package/src/lib/actions/system-settings.ts +274 -0
  250. package/src/lib/actions/tickets.ts +1445 -0
  251. package/src/lib/actions/users.ts +740 -0
  252. package/src/lib/audit/index.ts +103 -0
  253. package/src/lib/auth/__tests__/rbac.test.ts +33 -0
  254. package/src/lib/auth/index.ts +338 -0
  255. package/src/lib/auth/password.ts +10 -0
  256. package/src/lib/auth/rbac.ts +103 -0
  257. package/src/lib/auth/types.ts +57 -0
  258. package/src/lib/constants/departments.ts +16 -0
  259. package/src/lib/constants/manufacturers.ts +32 -0
  260. package/src/lib/db/index.ts +23 -0
  261. package/src/lib/db/schema/audit-logs.ts +42 -0
  262. package/src/lib/db/schema/departments.ts +39 -0
  263. package/src/lib/db/schema/device-categories.ts +38 -0
  264. package/src/lib/db/schema/devices.ts +195 -0
  265. package/src/lib/db/schema/engineer-profiles.ts +63 -0
  266. package/src/lib/db/schema/engineer-rounds.ts +115 -0
  267. package/src/lib/db/schema/enums.ts +148 -0
  268. package/src/lib/db/schema/hospitals.ts +34 -0
  269. package/src/lib/db/schema/index.ts +24 -0
  270. package/src/lib/db/schema/locations.ts +46 -0
  271. package/src/lib/db/schema/maintenance-plans.ts +99 -0
  272. package/src/lib/db/schema/maintenance-records.ts +161 -0
  273. package/src/lib/db/schema/maintenance-tasks.ts +196 -0
  274. package/src/lib/db/schema/manufacturers.ts +60 -0
  275. package/src/lib/db/schema/notifications.ts +48 -0
  276. package/src/lib/db/schema/organizations.ts +24 -0
  277. package/src/lib/db/schema/report-history.ts +28 -0
  278. package/src/lib/db/schema/roles.ts +59 -0
  279. package/src/lib/db/schema/sessions.ts +46 -0
  280. package/src/lib/db/schema/signature-events.ts +48 -0
  281. package/src/lib/db/schema/signature-policies.ts +60 -0
  282. package/src/lib/db/schema/signatures.ts +87 -0
  283. package/src/lib/db/schema/tickets.ts +169 -0
  284. package/src/lib/db/schema/user-assignments.ts +85 -0
  285. package/src/lib/db/schema/users.ts +39 -0
  286. package/src/lib/db/seed.ts +131 -0
  287. package/src/lib/logger/__tests__/logger.test.ts +154 -0
  288. package/src/lib/logger/formatters.ts +159 -0
  289. package/src/lib/logger/index.ts +184 -0
  290. package/src/lib/logger/types.ts +48 -0
  291. package/src/lib/notifications/create.ts +71 -0
  292. package/src/lib/notifications/dispatcher.ts +490 -0
  293. package/src/lib/notifications/index.ts +7 -0
  294. package/src/lib/notifications/push-sender.ts +128 -0
  295. package/src/lib/notifications/push.ts +64 -0
  296. package/src/lib/notifications/triggers.ts +166 -0
  297. package/src/lib/utils/__tests__/crypto.test.ts +30 -0
  298. package/src/lib/utils/__tests__/export.test.ts +55 -0
  299. package/src/lib/utils/__tests__/nav-badge.test.ts +147 -0
  300. package/src/lib/utils/__tests__/staff-status.test.ts +50 -0
  301. package/src/lib/utils/attestations.ts +19 -0
  302. package/src/lib/utils/crypto.ts +18 -0
  303. package/src/lib/utils/csv-parser.ts +181 -0
  304. package/src/lib/utils/export.ts +45 -0
  305. package/src/lib/utils/nav-badge.ts +225 -0
  306. package/src/lib/utils/sound.ts +152 -0
  307. package/src/lib/utils/staff-status.ts +115 -0
  308. package/src/lib/utils.ts +6 -0
  309. package/src/lib/validators/__tests__/auth.test.ts +127 -0
  310. package/src/lib/validators/__tests__/data-management.test.ts +182 -0
  311. package/src/lib/validators/__tests__/devices-profile.test.ts +129 -0
  312. package/src/lib/validators/__tests__/devices.test.ts +98 -0
  313. package/src/lib/validators/__tests__/engineer-rounds.test.ts +170 -0
  314. package/src/lib/validators/__tests__/maintenance.test.ts +178 -0
  315. package/src/lib/validators/__tests__/signatures.test.ts +154 -0
  316. package/src/lib/validators/__tests__/system-settings.test.ts +119 -0
  317. package/src/lib/validators/__tests__/tickets.test.ts +176 -0
  318. package/src/lib/validators/__tests__/users.test.ts +92 -0
  319. package/src/lib/validators/auth.ts +32 -0
  320. package/src/lib/validators/data-management.ts +67 -0
  321. package/src/lib/validators/departments.ts +14 -0
  322. package/src/lib/validators/devices.ts +94 -0
  323. package/src/lib/validators/engineer-rounds.ts +70 -0
  324. package/src/lib/validators/hospitals.ts +17 -0
  325. package/src/lib/validators/locations.ts +15 -0
  326. package/src/lib/validators/maintenance.ts +211 -0
  327. package/src/lib/validators/organizations.ts +11 -0
  328. package/src/lib/validators/reports.ts +31 -0
  329. package/src/lib/validators/signatures.ts +49 -0
  330. package/src/lib/validators/system-settings.ts +55 -0
  331. package/src/lib/validators/tickets.ts +125 -0
  332. package/src/lib/validators/users.ts +43 -0
  333. package/src/middleware.ts +45 -0
  334. package/tailwind.config.ts +76 -0
  335. package/tsconfig.json +28 -0
@@ -0,0 +1,182 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { Button } from '@/components/ui/button';
7
+ import { Activity, Database, Server, Cpu, Clock, RefreshCw, ShieldAlert, CheckCircle2, AlertCircle } from 'lucide-react';
8
+ import { getSystemHealth, type SystemHealthData } from '@/lib/actions/system-settings';
9
+ import { toast } from 'sonner';
10
+
11
+ interface HealthPanelProps {
12
+ initialData: SystemHealthData;
13
+ }
14
+
15
+ function formatUptime(seconds: number): string {
16
+ const days = Math.floor(seconds / (3600 * 24));
17
+ const hours = Math.floor((seconds % (3600 * 24)) / 3600);
18
+ const minutes = Math.floor((seconds % 3600) / 60);
19
+ const secs = Math.floor(seconds % 60);
20
+
21
+ const parts = [];
22
+ if (days > 0) parts.push(`${days}d`);
23
+ if (hours > 0 || days > 0) parts.push(`${hours}h`);
24
+ if (minutes > 0 || hours > 0 || days > 0) parts.push(`${minutes}m`);
25
+ parts.push(`${secs}s`);
26
+
27
+ return parts.join(' ');
28
+ }
29
+
30
+ export function HealthPanel({ initialData }: HealthPanelProps) {
31
+ const [health, setHealth] = useState<SystemHealthData>(initialData);
32
+ const [refreshing, setRefreshing] = useState(false);
33
+
34
+ const handleRefresh = async () => {
35
+ setRefreshing(true);
36
+ try {
37
+ const res = await getSystemHealth();
38
+ if (res.success && res.data) {
39
+ setHealth(res.data);
40
+ toast.success('Telemetry updated');
41
+ } else {
42
+ toast.error(res.error || 'Failed to refresh health status');
43
+ }
44
+ } catch {
45
+ toast.error('An error occurred while fetching health data');
46
+ } finally {
47
+ setRefreshing(false);
48
+ }
49
+ };
50
+
51
+ const isHealthy = health.status === 'healthy';
52
+
53
+ return (
54
+ <div className="space-y-6">
55
+ <Card>
56
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
57
+ <div>
58
+ <CardTitle className="flex items-center gap-2 text-xl">
59
+ <Activity className="h-5 w-5 text-primary" />
60
+ Operational Telemetry & System Health
61
+ </CardTitle>
62
+ <CardDescription>
63
+ Safe, read-only status of database connections, runtime environment, and background health.
64
+ </CardDescription>
65
+ </div>
66
+ <Button
67
+ variant="outline"
68
+ size="sm"
69
+ onClick={handleRefresh}
70
+ disabled={refreshing}
71
+ className="gap-2"
72
+ >
73
+ <RefreshCw className={`h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />
74
+ Refresh
75
+ </Button>
76
+ </CardHeader>
77
+
78
+ <CardContent className="space-y-6">
79
+ {/* Status Header Banner */}
80
+ <div
81
+ className={`p-4 rounded-lg flex items-center justify-between border ${
82
+ isHealthy
83
+ ? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-700 dark:text-emerald-400'
84
+ : 'bg-amber-500/10 border-amber-500/20 text-amber-700 dark:text-amber-400'
85
+ }`}
86
+ >
87
+ <div className="flex items-center gap-3">
88
+ {isHealthy ? (
89
+ <CheckCircle2 className="h-6 w-6 text-emerald-600 dark:text-emerald-400 shrink-0" />
90
+ ) : (
91
+ <AlertCircle className="h-6 w-6 text-amber-600 dark:text-amber-400 shrink-0" />
92
+ )}
93
+ <div>
94
+ <h4 className="font-semibold text-base">
95
+ {isHealthy ? 'All Systems Operational' : 'System Degraded'}
96
+ </h4>
97
+ <p className="text-xs opacity-90">
98
+ {isHealthy
99
+ ? 'PostgreSQL database connected and query latency within acceptable limits.'
100
+ : 'System is experiencing elevated latency or service degradation.'}
101
+ </p>
102
+ </div>
103
+ </div>
104
+ <Badge variant={isHealthy ? 'default' : 'destructive'} className="uppercase">
105
+ {health.status}
106
+ </Badge>
107
+ </div>
108
+
109
+ {/* Metrics Grid */}
110
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
111
+ {/* Database Metric */}
112
+ <div className="p-4 border rounded-lg bg-card space-y-1">
113
+ <div className="flex items-center justify-between text-muted-foreground">
114
+ <span className="text-xs font-medium">Database Latency</span>
115
+ <Database className="h-4 w-4" />
116
+ </div>
117
+ <div className="text-2xl font-bold">
118
+ {health.databasePingMs >= 0 ? `${health.databasePingMs}ms` : 'Error'}
119
+ </div>
120
+ <span className="text-xs text-emerald-600 dark:text-emerald-400 font-medium flex items-center gap-1">
121
+ <span className="h-2 w-2 rounded-full bg-emerald-500 inline-block" />
122
+ PostgreSQL 16 Connected
123
+ </span>
124
+ </div>
125
+
126
+ {/* Uptime Metric */}
127
+ <div className="p-4 border rounded-lg bg-card space-y-1">
128
+ <div className="flex items-center justify-between text-muted-foreground">
129
+ <span className="text-xs font-medium">Application Uptime</span>
130
+ <Clock className="h-4 w-4" />
131
+ </div>
132
+ <div className="text-2xl font-bold">{formatUptime(health.uptimeSeconds)}</div>
133
+ <span className="text-xs text-muted-foreground">
134
+ Environment: <span className="font-medium text-foreground">{health.environment}</span>
135
+ </span>
136
+ </div>
137
+
138
+ {/* Memory Metric */}
139
+ <div className="p-4 border rounded-lg bg-card space-y-1">
140
+ <div className="flex items-center justify-between text-muted-foreground">
141
+ <span className="text-xs font-medium">Memory (RSS / Heap)</span>
142
+ <Cpu className="h-4 w-4" />
143
+ </div>
144
+ <div className="text-2xl font-bold">{health.memoryUsage.rssMb} MB</div>
145
+ <span className="text-xs text-muted-foreground">
146
+ Heap Used: {health.memoryUsage.heapUsedMb} MB / {health.memoryUsage.heapTotalMb} MB
147
+ </span>
148
+ </div>
149
+
150
+ {/* Runtime Metric */}
151
+ <div className="p-4 border rounded-lg bg-card space-y-1">
152
+ <div className="flex items-center justify-between text-muted-foreground">
153
+ <span className="text-xs font-medium">Platform & Node</span>
154
+ <Server className="h-4 w-4" />
155
+ </div>
156
+ <div className="text-2xl font-bold">{health.runtime.nodeVersion}</div>
157
+ <span className="text-xs text-muted-foreground">
158
+ OS: {health.runtime.platform} ({health.runtime.arch})
159
+ </span>
160
+ </div>
161
+ </div>
162
+
163
+ {/* Section 14.2 Compliance Notice */}
164
+ <div className="p-4 border rounded-lg bg-muted/40 flex items-start gap-3">
165
+ <ShieldAlert className="h-5 w-5 text-muted-foreground shrink-0 mt-0.5" />
166
+ <div className="text-xs text-muted-foreground space-y-1">
167
+ <p className="font-medium text-foreground">
168
+ BEMMS Specification Section 14.2 Compliance Safeguard:
169
+ </p>
170
+ <p>
171
+ PostgreSQL credentials, application encryption keys, signing PIN secrets, and TLS certificates
172
+ are strictly isolated in protected server configuration (.env / Docker secrets). They are never
173
+ accessible or returned to application web sessions to prevent credential leakage.
174
+ </p>
175
+ </div>
176
+ </div>
177
+ </CardContent>
178
+ </Card>
179
+ </div>
180
+ );
181
+ }
182
+
@@ -0,0 +1,203 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Label } from '@/components/ui/label';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Button } from '@/components/ui/button';
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
9
+ import { Globe, Clock, Calendar, Languages, Loader2, Save } from 'lucide-react';
10
+ import { toast } from 'sonner';
11
+ import { updateOrganizationSettings } from '@/lib/actions/system-settings';
12
+ import type { LocalizationSettings } from '@/lib/validators/system-settings';
13
+
14
+ interface LocalizationFormProps {
15
+ initialData: LocalizationSettings;
16
+ organizationId: string;
17
+ }
18
+
19
+ const COMMON_TIMEZONES = [
20
+ { value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
21
+ { value: 'Africa/Cairo', label: 'Africa/Cairo (UTC+2 / UTC+3)' },
22
+ { value: 'Asia/Riyadh', label: 'Asia/Riyadh (UTC+3)' },
23
+ { value: 'Asia/Dubai', label: 'Asia/Dubai (UTC+4)' },
24
+ { value: 'Europe/London', label: 'Europe/London (UTC+0 / UTC+1)' },
25
+ { value: 'America/New_York', label: 'America/New_York (UTC-5 / UTC-4)' },
26
+ ];
27
+
28
+ export function LocalizationForm({ initialData, organizationId }: LocalizationFormProps) {
29
+ const [formData, setFormData] = useState<LocalizationSettings>(initialData);
30
+ const [changeReason, setChangeReason] = useState('');
31
+ const [saving, setSaving] = useState(false);
32
+
33
+ const handleSubmit = async (e: React.FormEvent) => {
34
+ e.preventDefault();
35
+ setSaving(true);
36
+
37
+ try {
38
+ const res = await updateOrganizationSettings({
39
+ organizationId,
40
+ section: 'localization',
41
+ settings: formData,
42
+ changeReason: changeReason.trim() || 'Updated localization and regional settings',
43
+ });
44
+
45
+ if (res.success) {
46
+ toast.success('Localization settings saved successfully');
47
+ setChangeReason('');
48
+ } else {
49
+ toast.error(res.error || 'Failed to update localization settings');
50
+ }
51
+ } catch {
52
+ toast.error('An unexpected error occurred');
53
+ } finally {
54
+ setSaving(false);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <form onSubmit={handleSubmit}>
60
+ <Card>
61
+ <CardHeader>
62
+ <CardTitle className="flex items-center gap-2 text-xl">
63
+ <Globe className="h-5 w-5 text-primary" />
64
+ Localization & Regional Preferences
65
+ </CardTitle>
66
+ <CardDescription>
67
+ Configure time display, calendar formats, and operational language for this facility.
68
+ </CardDescription>
69
+ </CardHeader>
70
+
71
+ <CardContent className="space-y-6">
72
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
73
+ {/* Timezone */}
74
+ <div className="space-y-2">
75
+ <Label htmlFor="timezone" className="flex items-center gap-2">
76
+ <Clock className="h-4 w-4 text-muted-foreground" />
77
+ Default Facility Timezone
78
+ </Label>
79
+ <Select
80
+ value={formData.timezone}
81
+ onValueChange={(val) => setFormData((prev) => ({ ...prev, timezone: val }))}
82
+ >
83
+ <SelectTrigger id="timezone">
84
+ <SelectValue placeholder="Select timezone" />
85
+ </SelectTrigger>
86
+ <SelectContent>
87
+ {COMMON_TIMEZONES.map((tz) => (
88
+ <SelectItem key={tz.value} value={tz.value}>
89
+ {tz.label}
90
+ </SelectItem>
91
+ ))}
92
+ </SelectContent>
93
+ </Select>
94
+ <p className="text-xs text-muted-foreground">
95
+ All maintenance schedules, PM occurrences, and audit events will display in this timezone.
96
+ </p>
97
+ </div>
98
+
99
+ {/* Language */}
100
+ <div className="space-y-2">
101
+ <Label htmlFor="language" className="flex items-center gap-2">
102
+ <Languages className="h-4 w-4 text-muted-foreground" />
103
+ Default Language
104
+ </Label>
105
+ <Select
106
+ value={formData.language}
107
+ onValueChange={(val: 'en' | 'ar') => setFormData((prev) => ({ ...prev, language: val }))}
108
+ >
109
+ <SelectTrigger id="language">
110
+ <SelectValue placeholder="Select language" />
111
+ </SelectTrigger>
112
+ <SelectContent>
113
+ <SelectItem value="en">English (US / UK)</SelectItem>
114
+ <SelectItem value="ar">العربية (Arabic - Future Support)</SelectItem>
115
+ </SelectContent>
116
+ </Select>
117
+ <p className="text-xs text-muted-foreground">
118
+ Application interface language preference (Section 14.1).
119
+ </p>
120
+ </div>
121
+
122
+ {/* Date Format */}
123
+ <div className="space-y-2">
124
+ <Label htmlFor="dateFormat" className="flex items-center gap-2">
125
+ <Calendar className="h-4 w-4 text-muted-foreground" />
126
+ Date Format
127
+ </Label>
128
+ <Select
129
+ value={formData.dateFormat}
130
+ onValueChange={(val: 'YYYY-MM-DD' | 'DD/MM/YYYY' | 'MM/DD/YYYY') =>
131
+ setFormData((prev) => ({ ...prev, dateFormat: val }))
132
+ }
133
+ >
134
+ <SelectTrigger id="dateFormat">
135
+ <SelectValue placeholder="Select date format" />
136
+ </SelectTrigger>
137
+ <SelectContent>
138
+ <SelectItem value="YYYY-MM-DD">YYYY-MM-DD (ISO 8601 - Recommended)</SelectItem>
139
+ <SelectItem value="DD/MM/YYYY">DD/MM/YYYY (UK / EU)</SelectItem>
140
+ <SelectItem value="MM/DD/YYYY">MM/DD/YYYY (US)</SelectItem>
141
+ </SelectContent>
142
+ </Select>
143
+ </div>
144
+
145
+ {/* Time Format */}
146
+ <div className="space-y-2">
147
+ <Label htmlFor="timeFormat" className="flex items-center gap-2">
148
+ <Clock className="h-4 w-4 text-muted-foreground" />
149
+ Time Display
150
+ </Label>
151
+ <Select
152
+ value={formData.timeFormat}
153
+ onValueChange={(val: '24h' | '12h') => setFormData((prev) => ({ ...prev, timeFormat: val }))}
154
+ >
155
+ <SelectTrigger id="timeFormat">
156
+ <SelectValue placeholder="Select time format" />
157
+ </SelectTrigger>
158
+ <SelectContent>
159
+ <SelectItem value="24h">24-hour (14:30) - Clinical Standard</SelectItem>
160
+ <SelectItem value="12h">12-hour (2:30 PM)</SelectItem>
161
+ </SelectContent>
162
+ </Select>
163
+ </div>
164
+ </div>
165
+
166
+ {/* Audit Trace Reason */}
167
+ <div className="space-y-2 pt-2 border-t">
168
+ <Label htmlFor="changeReason" className="text-sm font-medium">
169
+ Change Reason (21 CFR Part 11 Audit Trace)
170
+ </Label>
171
+ <Input
172
+ id="changeReason"
173
+ placeholder="e.g., Hospital shifted default timezone to local regional time"
174
+ value={changeReason}
175
+ onChange={(e) => setChangeReason(e.target.value)}
176
+ className="max-w-xl"
177
+ />
178
+ <p className="text-xs text-muted-foreground">
179
+ Optional documentation justification recorded in the immutable audit log.
180
+ </p>
181
+ </div>
182
+ </CardContent>
183
+
184
+ <CardFooter className="flex justify-end border-t pt-4">
185
+ <Button type="submit" disabled={saving} className="gap-2">
186
+ {saving ? (
187
+ <>
188
+ <Loader2 className="h-4 w-4 animate-spin" />
189
+ Saving...
190
+ </>
191
+ ) : (
192
+ <>
193
+ <Save className="h-4 w-4" />
194
+ Save Localization
195
+ </>
196
+ )}
197
+ </Button>
198
+ </CardFooter>
199
+ </Card>
200
+ </form>
201
+ );
202
+ }
203
+
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { Label } from '@/components/ui/label';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Button } from '@/components/ui/button';
8
+ import { Database, HardDrive, CalendarClock, Loader2, Save } from 'lucide-react';
9
+ import { toast } from 'sonner';
10
+ import { updateOrganizationSettings } from '@/lib/actions/system-settings';
11
+ import type { RetentionSettings } from '@/lib/validators/system-settings';
12
+
13
+ interface RetentionFormProps {
14
+ initialData: RetentionSettings;
15
+ organizationId: string;
16
+ }
17
+
18
+ export function RetentionForm({ initialData, organizationId }: RetentionFormProps) {
19
+ const [formData, setFormData] = useState<RetentionSettings>(initialData);
20
+ const [changeReason, setChangeReason] = useState('');
21
+ const [saving, setSaving] = useState(false);
22
+
23
+ const handleSubmit = async (e: React.FormEvent) => {
24
+ e.preventDefault();
25
+ setSaving(true);
26
+
27
+ try {
28
+ const res = await updateOrganizationSettings({
29
+ organizationId,
30
+ section: 'retention',
31
+ settings: formData,
32
+ changeReason: changeReason.trim() || 'Updated data retention and operational thresholds',
33
+ });
34
+
35
+ if (res.success) {
36
+ toast.success('Data retention settings saved successfully');
37
+ setChangeReason('');
38
+ } else {
39
+ toast.error(res.error || 'Failed to update retention settings');
40
+ }
41
+ } catch {
42
+ toast.error('An unexpected error occurred');
43
+ } finally {
44
+ setSaving(false);
45
+ }
46
+ };
47
+
48
+ return (
49
+ <form onSubmit={handleSubmit}>
50
+ <Card>
51
+ <CardHeader>
52
+ <CardTitle className="flex items-center gap-2 text-xl">
53
+ <Database className="h-5 w-5 text-primary" />
54
+ Data Retention & Operational Windows
55
+ </CardTitle>
56
+ <CardDescription>
57
+ Configure legal audit trail archiving periods, PM notification lead times, and attachment quotas.
58
+ </CardDescription>
59
+ </CardHeader>
60
+
61
+ <CardContent className="space-y-6">
62
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
63
+ {/* Audit Log Retention */}
64
+ <div className="space-y-2">
65
+ <Label htmlFor="auditRetention" className="flex items-center gap-2">
66
+ <Database className="h-4 w-4 text-muted-foreground" />
67
+ Audit Trail Retention (Days)
68
+ </Label>
69
+ <Input
70
+ id="auditRetention"
71
+ type="number"
72
+ min={30}
73
+ max={3650}
74
+ value={formData.auditLogRetentionDays}
75
+ onChange={(e) =>
76
+ setFormData((prev) => ({ ...prev, auditLogRetentionDays: parseInt(e.target.value) || 365 }))
77
+ }
78
+ />
79
+ <p className="text-xs text-muted-foreground">
80
+ Minimum duration immutable audit records are preserved for regulatory inspection (365 days = 1 year).
81
+ </p>
82
+ </div>
83
+
84
+ {/* PM Alert Window */}
85
+ <div className="space-y-2">
86
+ <Label htmlFor="pmAlertWindow" className="flex items-center gap-2">
87
+ <CalendarClock className="h-4 w-4 text-muted-foreground" />
88
+ Preventive Maintenance Alert Window (Days)
89
+ </Label>
90
+ <Input
91
+ id="pmAlertWindow"
92
+ type="number"
93
+ min={1}
94
+ max={90}
95
+ value={formData.pmAlertWindowDays}
96
+ onChange={(e) =>
97
+ setFormData((prev) => ({ ...prev, pmAlertWindowDays: parseInt(e.target.value) || 14 }))
98
+ }
99
+ />
100
+ <p className="text-xs text-muted-foreground">
101
+ Advance notification notice before scheduled PM occurrences become due (standard: 14 days).
102
+ </p>
103
+ </div>
104
+
105
+ {/* Attachment Quota */}
106
+ <div className="space-y-2">
107
+ <Label htmlFor="attachmentQuota" className="flex items-center gap-2">
108
+ <HardDrive className="h-4 w-4 text-muted-foreground" />
109
+ Facility Attachment Storage Quota (MB)
110
+ </Label>
111
+ <Input
112
+ id="attachmentQuota"
113
+ type="number"
114
+ min={100}
115
+ max={100000}
116
+ value={formData.attachmentQuotaMb}
117
+ onChange={(e) =>
118
+ setFormData((prev) => ({ ...prev, attachmentQuotaMb: parseInt(e.target.value) || 5000 }))
119
+ }
120
+ />
121
+ <p className="text-xs text-muted-foreground">
122
+ Total storage allocation for manuals, service sheets, and photo evidence (5000 MB = 5 GB).
123
+ </p>
124
+ </div>
125
+ </div>
126
+
127
+ {/* Audit Trace Reason */}
128
+ <div className="space-y-2 pt-2 border-t">
129
+ <Label htmlFor="changeReason" className="text-sm font-medium">
130
+ Change Reason (21 CFR Part 11 Audit Trace)
131
+ </Label>
132
+ <Input
133
+ id="changeReason"
134
+ placeholder="e.g., Clinical engineering policy update on PM warning lead times"
135
+ value={changeReason}
136
+ onChange={(e) => setChangeReason(e.target.value)}
137
+ className="max-w-xl"
138
+ />
139
+ <p className="text-xs text-muted-foreground">
140
+ Required rationale recorded in the immutable audit log upon modification.
141
+ </p>
142
+ </div>
143
+ </CardContent>
144
+
145
+ <CardFooter className="flex justify-end border-t pt-4">
146
+ <Button type="submit" disabled={saving} className="gap-2">
147
+ {saving ? (
148
+ <>
149
+ <Loader2 className="h-4 w-4 animate-spin" />
150
+ Saving...
151
+ </>
152
+ ) : (
153
+ <>
154
+ <Save className="h-4 w-4" />
155
+ Save Retention Settings
156
+ </>
157
+ )}
158
+ </Button>
159
+ </CardFooter>
160
+ </Card>
161
+ </form>
162
+ );
163
+ }
164
+