@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,281 @@
1
+ 'use client';
2
+
3
+ import { useState, useTransition } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent } from '@/components/ui/card';
6
+ import { Progress } from '@/components/ui/progress';
7
+ import { DepartmentVisitSheet } from './department-visit-sheet';
8
+ import { completeRound } from '@/lib/actions/engineer-rounds';
9
+ import { toast } from 'sonner';
10
+ import { useRouter } from 'next/navigation';
11
+ import {
12
+ CheckCircle2, Circle, Clock, AlertTriangle, PartyPopper,
13
+ FileText, Loader2, SkipForward,
14
+ } from 'lucide-react';
15
+ import { cn } from '@/lib/utils';
16
+ import { formatDistanceToNow } from 'date-fns';
17
+
18
+ interface Visit {
19
+ id: string;
20
+ departmentId: string;
21
+ departmentName: string;
22
+ sequence: number;
23
+ status: string;
24
+ startedAt: string | null;
25
+ completedAt: string | null;
26
+ ticketCount: number;
27
+ notes: {
28
+ id: string;
29
+ noteText: string;
30
+ deviceMentions: any[];
31
+ author?: { id: string; fullName: string };
32
+ }[];
33
+ department: { id: string; name: string; code: string };
34
+ }
35
+
36
+ interface RoundData {
37
+ id: string;
38
+ roundDate: string;
39
+ shiftType: string;
40
+ status: string;
41
+ startedAt: string | null;
42
+ completedAt: string | null;
43
+ totalDepartments: number;
44
+ completedDepartments: number;
45
+ totalIssuesReported: number;
46
+ totalNotes: number;
47
+ hospitalId: string;
48
+ hospital: { id: string; name: string };
49
+ engineer: { id: string; fullName: string };
50
+ departmentVisits: Visit[];
51
+ }
52
+
53
+ interface RoundDepartmentListProps {
54
+ round: RoundData;
55
+ }
56
+
57
+ export function RoundDepartmentList({ round: initialRound }: RoundDepartmentListProps) {
58
+ const router = useRouter();
59
+ const [selectedVisit, setSelectedVisit] = useState<Visit | null>(null);
60
+ const [sheetOpen, setSheetOpen] = useState(false);
61
+ const [isPending, startTransition] = useTransition();
62
+
63
+ const round = initialRound;
64
+ const visits = round.departmentVisits;
65
+ const completedCount = visits.filter(v => v.status === 'completed').length;
66
+ const progress = visits.length > 0 ? (completedCount / visits.length) * 100 : 0;
67
+ const allCompleted = completedCount === visits.length;
68
+ const isCompleted = round.status === 'completed';
69
+
70
+ const handleVisitClick = (visit: Visit) => {
71
+ setSelectedVisit(visit);
72
+ setSheetOpen(true);
73
+ };
74
+
75
+ const handleCompleteRound = () => {
76
+ startTransition(async () => {
77
+ const result = await completeRound({ roundId: round.id });
78
+ if (result.success) {
79
+ toast.success('Round completed successfully!');
80
+ router.refresh();
81
+ } else {
82
+ toast.error(result.error || 'Failed to complete round');
83
+ }
84
+ });
85
+ };
86
+
87
+ const formatTime = (ts: string | null) => {
88
+ if (!ts) return null;
89
+ return new Date(ts).toLocaleTimeString('en-US', {
90
+ hour: '2-digit',
91
+ minute: '2-digit',
92
+ hour12: true,
93
+ });
94
+ };
95
+
96
+ return (
97
+ <div className="space-y-4">
98
+ {/* Progress header */}
99
+ <Card>
100
+ <CardContent className="pt-6">
101
+ <div className="flex items-center justify-between mb-2">
102
+ <span className="text-sm font-medium">
103
+ {completedCount} / {visits.length} Departments
104
+ </span>
105
+ <span className="text-sm text-muted-foreground">
106
+ {Math.round(progress)}%
107
+ </span>
108
+ </div>
109
+ <Progress value={progress} className="h-2" />
110
+ {round.startedAt && (
111
+ <p className="text-xs text-muted-foreground mt-2">
112
+ Started {formatTime(round.startedAt)} · {round.shiftType} shift
113
+ </p>
114
+ )}
115
+ </CardContent>
116
+ </Card>
117
+
118
+ {/* Department List (To-Do Style) */}
119
+ <div className="space-y-2">
120
+ {visits.map((visit) => {
121
+ const isVisitCompleted = visit.status === 'completed';
122
+ const isSkipped = visit.status === 'skipped';
123
+
124
+ return (
125
+ <button
126
+ key={visit.id}
127
+ onClick={() => handleVisitClick(visit)}
128
+ className={cn(
129
+ 'w-full flex items-start gap-3 p-4 rounded-lg border transition-all text-left',
130
+ isVisitCompleted
131
+ ? 'bg-green-50 border-green-200 dark:bg-green-950/20 dark:border-green-900/40'
132
+ : isSkipped
133
+ ? 'bg-muted/30 border-muted opacity-60'
134
+ : 'bg-card border-border hover:border-primary/40 hover:shadow-sm active:scale-[0.99]',
135
+ )}
136
+ disabled={isCompleted}
137
+ >
138
+ {/* Status Icon */}
139
+ <div className="mt-0.5 shrink-0">
140
+ {isVisitCompleted ? (
141
+ <CheckCircle2 className="h-5 w-5 text-green-500" />
142
+ ) : isSkipped ? (
143
+ <SkipForward className="h-5 w-5 text-muted-foreground" />
144
+ ) : (
145
+ <Circle className="h-5 w-5 text-muted-foreground/40" />
146
+ )}
147
+ </div>
148
+
149
+ {/* Content */}
150
+ <div className="flex-1 min-w-0">
151
+ <div className="flex items-center justify-between gap-2">
152
+ <span className={cn(
153
+ 'font-medium',
154
+ isVisitCompleted && 'text-green-700 dark:text-green-400',
155
+ isSkipped && 'line-through text-muted-foreground',
156
+ )}>
157
+ {visit.departmentName}
158
+ </span>
159
+ {isVisitCompleted && visit.completedAt && (
160
+ <span className="text-xs text-muted-foreground shrink-0">
161
+ {formatTime(visit.completedAt)}
162
+ </span>
163
+ )}
164
+ </div>
165
+
166
+ {/* Visit details */}
167
+ {isVisitCompleted && (
168
+ <div className="flex flex-wrap items-center gap-2 mt-1">
169
+ {visit.notes.length > 0 && (
170
+ <span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
171
+ <FileText className="h-3 w-3" />
172
+ {visit.notes.length} note{visit.notes.length !== 1 ? 's' : ''}
173
+ </span>
174
+ )}
175
+ {visit.ticketCount > 0 && (
176
+ <span className="inline-flex items-center gap-1 text-xs text-yellow-600 dark:text-yellow-400">
177
+ <AlertTriangle className="h-3 w-3" />
178
+ {visit.ticketCount} issue{visit.ticketCount !== 1 ? 's' : ''}
179
+ </span>
180
+ )}
181
+ {visit.notes.length === 0 && visit.ticketCount === 0 && (
182
+ <span className="text-xs text-green-600/70 dark:text-green-500/70">
183
+ No issues
184
+ </span>
185
+ )}
186
+ </div>
187
+ )}
188
+
189
+ {/* First note preview */}
190
+ {visit.notes.length > 0 && (
191
+ <p className="text-xs text-muted-foreground mt-1 line-clamp-1">
192
+ &quot;{visit.notes[0].noteText}&quot;
193
+ </p>
194
+ )}
195
+ </div>
196
+ </button>
197
+ );
198
+ })}
199
+ </div>
200
+
201
+ {/* Complete Round Button */}
202
+ {!isCompleted && (
203
+ <div className="pt-2">
204
+ {allCompleted ? (
205
+ <Card className="border-green-200 bg-green-50 dark:border-green-900/40 dark:bg-green-950/20">
206
+ <CardContent className="pt-6 text-center space-y-3">
207
+ <PartyPopper className="h-8 w-8 mx-auto text-green-500" />
208
+ <div>
209
+ <h3 className="font-semibold text-green-700 dark:text-green-400">
210
+ All departments visited!
211
+ </h3>
212
+ <p className="text-sm text-muted-foreground mt-1">
213
+ {round.totalIssuesReported} issue{round.totalIssuesReported !== 1 ? 's' : ''} reported · {round.totalNotes} note{round.totalNotes !== 1 ? 's' : ''}
214
+ </p>
215
+ </div>
216
+ <Button
217
+ size="lg"
218
+ className="w-full gap-2"
219
+ onClick={handleCompleteRound}
220
+ disabled={isPending}
221
+ >
222
+ {isPending ? (
223
+ <Loader2 className="h-5 w-5 animate-spin" />
224
+ ) : (
225
+ <CheckCircle2 className="h-5 w-5" />
226
+ )}
227
+ Complete Round
228
+ </Button>
229
+ </CardContent>
230
+ </Card>
231
+ ) : (
232
+ <Button
233
+ variant="outline"
234
+ size="lg"
235
+ className="w-full gap-2"
236
+ onClick={handleCompleteRound}
237
+ disabled={isPending}
238
+ >
239
+ {isPending ? (
240
+ <Loader2 className="h-5 w-5 animate-spin" />
241
+ ) : (
242
+ <CheckCircle2 className="h-5 w-5" />
243
+ )}
244
+ Complete Round ({completedCount}/{visits.length} done)
245
+ </Button>
246
+ )}
247
+ </div>
248
+ )}
249
+
250
+ {/* Completed Summary */}
251
+ {isCompleted && (
252
+ <Card className="border-green-200 bg-green-50 dark:border-green-900/40 dark:bg-green-950/20">
253
+ <CardContent className="pt-6 text-center">
254
+ <CheckCircle2 className="h-8 w-8 mx-auto text-green-500 mb-2" />
255
+ <h3 className="font-semibold text-green-700 dark:text-green-400">
256
+ Round Completed
257
+ </h3>
258
+ <p className="text-sm text-muted-foreground mt-1">
259
+ {completedCount}/{visits.length} departments · {round.totalIssuesReported} issues · {round.totalNotes} notes
260
+ </p>
261
+ {round.startedAt && round.completedAt && (
262
+ <p className="text-xs text-muted-foreground mt-1">
263
+ {formatTime(round.startedAt)} — {formatTime(round.completedAt)}
264
+ </p>
265
+ )}
266
+ </CardContent>
267
+ </Card>
268
+ )}
269
+
270
+ {/* Department Visit Sheet */}
271
+ <DepartmentVisitSheet
272
+ open={sheetOpen}
273
+ onOpenChange={setSheetOpen}
274
+ visit={selectedVisit}
275
+ roundId={round.id}
276
+ hospitalId={round.hospitalId}
277
+ onCompleted={() => router.refresh()}
278
+ />
279
+ </div>
280
+ );
281
+ }
@@ -0,0 +1,218 @@
1
+ 'use client';
2
+
3
+ import { useState, useTransition } from 'react';
4
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5
+ import { RoundCalendar } from './round-calendar';
6
+ import { getRoundCalendarData } from '@/lib/actions/engineer-rounds';
7
+ import {
8
+ CheckCircle2, XCircle, Clock, UserX, Loader2, AlertTriangle, FileText,
9
+ } from 'lucide-react';
10
+ import { cn } from '@/lib/utils';
11
+ import Link from 'next/link';
12
+
13
+ interface RoundHistoryClientProps {
14
+ initialHistory: any[];
15
+ initialCalendar: any;
16
+ initialCompliance: any;
17
+ isManager: boolean;
18
+ currentUserId: string;
19
+ pagination: any;
20
+ }
21
+
22
+ export function RoundHistoryClient({
23
+ initialHistory,
24
+ initialCalendar,
25
+ initialCompliance,
26
+ isManager,
27
+ currentUserId,
28
+ pagination,
29
+ }: RoundHistoryClientProps) {
30
+ const [calendarData, setCalendarData] = useState(initialCalendar);
31
+ const [isPending, startTransition] = useTransition();
32
+
33
+ const handleMonthChange = (month: number, year: number) => {
34
+ startTransition(async () => {
35
+ const result = await getRoundCalendarData({
36
+ engineerId: currentUserId,
37
+ month,
38
+ year,
39
+ });
40
+ if (result.success) {
41
+ setCalendarData(result.data);
42
+ }
43
+ });
44
+ };
45
+
46
+ const statusIcons: Record<string, any> = {
47
+ completed: <CheckCircle2 className="h-4 w-4 text-green-500" />,
48
+ in_progress: <Clock className="h-4 w-4 text-blue-500 animate-pulse" />,
49
+ missed: <XCircle className="h-4 w-4 text-red-500" />,
50
+ scheduled: <Clock className="h-4 w-4 text-muted-foreground" />,
51
+ cancelled: <XCircle className="h-4 w-4 text-muted-foreground" />,
52
+ };
53
+
54
+ const formatTime = (ts: string | null) => {
55
+ if (!ts) return '';
56
+ return new Date(ts).toLocaleTimeString('en-US', {
57
+ hour: '2-digit', minute: '2-digit', hour12: true,
58
+ });
59
+ };
60
+
61
+ return (
62
+ <div className="space-y-6">
63
+ {/* Calendar */}
64
+ {calendarData && (
65
+ <Card>
66
+ <CardHeader>
67
+ <CardTitle className="text-base">Round Activity</CardTitle>
68
+ </CardHeader>
69
+ <CardContent className="relative">
70
+ {isPending && (
71
+ <div className="absolute inset-0 bg-background/50 flex items-center justify-center z-10 rounded-lg">
72
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
73
+ </div>
74
+ )}
75
+ <RoundCalendar
76
+ month={calendarData.month}
77
+ year={calendarData.year}
78
+ calendarDays={calendarData.calendarDays}
79
+ summary={calendarData.summary}
80
+ onMonthChange={handleMonthChange}
81
+ />
82
+ </CardContent>
83
+ </Card>
84
+ )}
85
+
86
+ {/* Manager: Team Compliance */}
87
+ {isManager && initialCompliance && (
88
+ <Card>
89
+ <CardHeader>
90
+ <CardTitle className="text-base">Team Compliance Today</CardTitle>
91
+ </CardHeader>
92
+ <CardContent>
93
+ {/* Summary Stats */}
94
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-4">
95
+ <div className="rounded-lg bg-green-50 dark:bg-green-950/20 p-3 text-center">
96
+ <p className="text-2xl font-bold text-green-600 dark:text-green-400">
97
+ {initialCompliance.summary.completed}
98
+ </p>
99
+ <p className="text-xs text-muted-foreground">Completed</p>
100
+ </div>
101
+ <div className="rounded-lg bg-blue-50 dark:bg-blue-950/20 p-3 text-center">
102
+ <p className="text-2xl font-bold text-blue-600 dark:text-blue-400">
103
+ {initialCompliance.summary.inProgress}
104
+ </p>
105
+ <p className="text-xs text-muted-foreground">In Progress</p>
106
+ </div>
107
+ <div className="rounded-lg bg-red-50 dark:bg-red-950/20 p-3 text-center">
108
+ <p className="text-2xl font-bold text-red-600 dark:text-red-400">
109
+ {initialCompliance.summary.missed}
110
+ </p>
111
+ <p className="text-xs text-muted-foreground">Missed</p>
112
+ </div>
113
+ <div className="rounded-lg bg-muted p-3 text-center">
114
+ <p className="text-2xl font-bold">
115
+ {initialCompliance.summary.complianceRate}%
116
+ </p>
117
+ <p className="text-xs text-muted-foreground">Compliance</p>
118
+ </div>
119
+ </div>
120
+
121
+ {/* Engineer list */}
122
+ <div className="space-y-1.5">
123
+ {initialCompliance.team.map((member: any) => (
124
+ <div
125
+ key={member.engineerId}
126
+ className="flex items-center gap-3 py-2 px-3 rounded-md hover:bg-muted/50"
127
+ >
128
+ {member.status === 'completed' ? (
129
+ <CheckCircle2 className="h-4 w-4 text-green-500 shrink-0" />
130
+ ) : member.status === 'in_progress' ? (
131
+ <Clock className="h-4 w-4 text-blue-500 shrink-0" />
132
+ ) : member.status === 'missed' ? (
133
+ <XCircle className="h-4 w-4 text-red-500 shrink-0" />
134
+ ) : member.status === 'off_duty' ? (
135
+ <UserX className="h-4 w-4 text-muted-foreground shrink-0" />
136
+ ) : (
137
+ <Clock className="h-4 w-4 text-muted-foreground shrink-0" />
138
+ )}
139
+ <div className="flex-1 min-w-0">
140
+ <p className="text-sm font-medium truncate">{member.engineerName}</p>
141
+ <p className="text-xs text-muted-foreground">{member.hospitalName}</p>
142
+ </div>
143
+ <span className={cn(
144
+ 'text-xs font-medium capitalize',
145
+ member.status === 'completed' && 'text-green-600 dark:text-green-400',
146
+ member.status === 'in_progress' && 'text-blue-600 dark:text-blue-400',
147
+ member.status === 'missed' && 'text-red-600 dark:text-red-400',
148
+ member.status === 'off_duty' && 'text-muted-foreground',
149
+ )}>
150
+ {member.status?.replace(/_/g, ' ')}
151
+ </span>
152
+ </div>
153
+ ))}
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+ )}
158
+
159
+ {/* History List */}
160
+ <Card>
161
+ <CardHeader>
162
+ <CardTitle className="text-base">Recent Rounds</CardTitle>
163
+ </CardHeader>
164
+ <CardContent>
165
+ {initialHistory.length === 0 ? (
166
+ <p className="text-sm text-muted-foreground text-center py-4">
167
+ No rounds found
168
+ </p>
169
+ ) : (
170
+ <div className="space-y-2">
171
+ {initialHistory.map((round: any) => (
172
+ <Link
173
+ key={round.id}
174
+ href={`/rounds/${round.id}`}
175
+ className="flex items-start gap-3 py-3 px-3 rounded-lg hover:bg-muted/50 transition-colors"
176
+ >
177
+ {statusIcons[round.status] || statusIcons.scheduled}
178
+ <div className="flex-1 min-w-0">
179
+ <div className="flex items-center justify-between gap-2">
180
+ <span className="text-sm font-medium">
181
+ {round.engineer?.fullName || 'Unknown'}
182
+ </span>
183
+ <span className="text-xs text-muted-foreground capitalize">
184
+ {round.shiftType}
185
+ </span>
186
+ </div>
187
+ <p className="text-xs text-muted-foreground">
188
+ {new Date(round.roundDate + 'T00:00:00').toLocaleDateString('en-US', {
189
+ weekday: 'short', month: 'short', day: 'numeric',
190
+ })}
191
+ {' · '}
192
+ {round.hospital?.name}
193
+ </p>
194
+ <div className="flex items-center gap-3 mt-1 text-xs text-muted-foreground">
195
+ <span>{round.completedDepartments}/{round.totalDepartments} depts</span>
196
+ {round.totalIssuesReported > 0 && (
197
+ <span className="flex items-center gap-1 text-yellow-600 dark:text-yellow-400">
198
+ <AlertTriangle className="h-3 w-3" />
199
+ {round.totalIssuesReported} issues
200
+ </span>
201
+ )}
202
+ {round.totalNotes > 0 && (
203
+ <span className="flex items-center gap-1">
204
+ <FileText className="h-3 w-3" />
205
+ {round.totalNotes} notes
206
+ </span>
207
+ )}
208
+ </div>
209
+ </div>
210
+ </Link>
211
+ ))}
212
+ </div>
213
+ )}
214
+ </CardContent>
215
+ </Card>
216
+ </div>
217
+ );
218
+ }
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import { useState, useTransition } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Label } from '@/components/ui/label';
8
+ import {
9
+ Dialog, DialogContent, DialogHeader, DialogTitle,
10
+ DialogDescription, DialogFooter,
11
+ } from '@/components/ui/dialog';
12
+ import { startRound } from '@/lib/actions/engineer-rounds';
13
+ import { toast } from 'sonner';
14
+ import { useRouter } from 'next/navigation';
15
+ import { ShieldCheck, Loader2, Building2, Calendar, Clock, MapPin } from 'lucide-react';
16
+
17
+ interface StartRoundCardProps {
18
+ hospitalId: string;
19
+ hospitalName?: string;
20
+ departments: { id: string; name: string; code: string }[];
21
+ shiftType: string;
22
+ }
23
+
24
+ export function StartRoundCard({
25
+ hospitalId,
26
+ hospitalName,
27
+ departments,
28
+ shiftType,
29
+ }: StartRoundCardProps) {
30
+ const router = useRouter();
31
+ const [showSignDialog, setShowSignDialog] = useState(false);
32
+ const [password, setPassword] = useState('');
33
+ const [isPending, startTransition] = useTransition();
34
+
35
+ const today = new Date();
36
+ const dateStr = today.toLocaleDateString('en-US', {
37
+ weekday: 'long',
38
+ year: 'numeric',
39
+ month: 'long',
40
+ day: 'numeric',
41
+ });
42
+
43
+ const handleStartRound = () => {
44
+ if (!password.trim()) {
45
+ toast.error('Please enter your password');
46
+ return;
47
+ }
48
+
49
+ startTransition(async () => {
50
+ const result = await startRound({
51
+ hospitalId,
52
+ shiftType,
53
+ password,
54
+ });
55
+
56
+ if (result.success) {
57
+ toast.success('Round started! Good luck on your round.');
58
+ setShowSignDialog(false);
59
+ setPassword('');
60
+ router.refresh();
61
+ } else {
62
+ toast.error(result.error || 'Failed to start round');
63
+ }
64
+ });
65
+ };
66
+
67
+ return (
68
+ <>
69
+ <Card className="border-primary/20 shadow-sm">
70
+ <CardHeader className="pb-3">
71
+ <CardTitle className="text-xl">Start Daily Round</CardTitle>
72
+ </CardHeader>
73
+ <CardContent className="space-y-4">
74
+ <div className="grid gap-3 text-sm">
75
+ <div className="flex items-center gap-2 text-muted-foreground">
76
+ <Calendar className="h-4 w-4 shrink-0" />
77
+ <span>{dateStr}</span>
78
+ </div>
79
+ <div className="flex items-center gap-2 text-muted-foreground">
80
+ <Building2 className="h-4 w-4 shrink-0" />
81
+ <span>{hospitalName || 'Your Hospital'}</span>
82
+ </div>
83
+ <div className="flex items-center gap-2 text-muted-foreground">
84
+ <Clock className="h-4 w-4 shrink-0" />
85
+ <span className="capitalize">{shiftType} Shift</span>
86
+ </div>
87
+ <div className="flex items-center gap-2 text-muted-foreground">
88
+ <MapPin className="h-4 w-4 shrink-0" />
89
+ <span>{departments.length} department{departments.length !== 1 ? 's' : ''} to visit</span>
90
+ </div>
91
+ </div>
92
+
93
+ {/* Department Preview */}
94
+ <div className="flex flex-wrap gap-1.5 pt-1">
95
+ {departments.map(dept => (
96
+ <span
97
+ key={dept.id}
98
+ className="inline-flex items-center text-xs bg-muted px-2 py-1 rounded-md"
99
+ >
100
+ {dept.name}
101
+ </span>
102
+ ))}
103
+ </div>
104
+
105
+ <Button
106
+ size="lg"
107
+ className="w-full gap-2 text-base h-12"
108
+ onClick={() => setShowSignDialog(true)}
109
+ >
110
+ <ShieldCheck className="h-5 w-5" />
111
+ Sign &amp; Start Round
112
+ </Button>
113
+ </CardContent>
114
+ </Card>
115
+
116
+ {/* E-Signature Dialog */}
117
+ <Dialog open={showSignDialog} onOpenChange={setShowSignDialog}>
118
+ <DialogContent className="sm:max-w-md">
119
+ <DialogHeader>
120
+ <DialogTitle className="flex items-center gap-2">
121
+ <ShieldCheck className="h-5 w-5 text-primary" />
122
+ Electronic Signature
123
+ </DialogTitle>
124
+ <DialogDescription>
125
+ Confirm your identity to start the daily engineering round.
126
+ This action will be recorded with your electronic signature.
127
+ </DialogDescription>
128
+ </DialogHeader>
129
+
130
+ <div className="space-y-4 py-2">
131
+ <div className="rounded-md bg-muted/50 p-3 text-sm space-y-1">
132
+ <p><span className="font-medium">Action:</span> Start Daily Engineering Round</p>
133
+ <p><span className="font-medium">Date:</span> {dateStr}</p>
134
+ <p><span className="font-medium">Shift:</span> <span className="capitalize">{shiftType}</span></p>
135
+ <p><span className="font-medium">Departments:</span> {departments.length}</p>
136
+ </div>
137
+
138
+ <div>
139
+ <Label htmlFor="sig-password">Enter your password to confirm</Label>
140
+ <Input
141
+ id="sig-password"
142
+ type="password"
143
+ placeholder="Your password"
144
+ value={password}
145
+ onChange={(e) => setPassword(e.target.value)}
146
+ onKeyDown={(e) => {
147
+ if (e.key === 'Enter') handleStartRound();
148
+ }}
149
+ disabled={isPending}
150
+ className="mt-1.5"
151
+ autoFocus
152
+ />
153
+ </div>
154
+ </div>
155
+
156
+ <DialogFooter>
157
+ <Button
158
+ variant="outline"
159
+ onClick={() => {
160
+ setShowSignDialog(false);
161
+ setPassword('');
162
+ }}
163
+ disabled={isPending}
164
+ >
165
+ Cancel
166
+ </Button>
167
+ <Button
168
+ onClick={handleStartRound}
169
+ disabled={isPending || !password.trim()}
170
+ className="gap-2"
171
+ >
172
+ {isPending ? (
173
+ <Loader2 className="h-4 w-4 animate-spin" />
174
+ ) : (
175
+ <ShieldCheck className="h-4 w-4" />
176
+ )}
177
+ Confirm &amp; Start
178
+ </Button>
179
+ </DialogFooter>
180
+ </DialogContent>
181
+ </Dialog>
182
+ </>
183
+ );
184
+ }