@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,55 @@
1
+ import { cn } from "@/lib/utils"
2
+ import { Badge } from "@/components/ui/badge"
3
+
4
+ const statusConfig: Record<string, { label: string; variant: string; className: string }> = {
5
+ operational: { label: "Operational", variant: "default", className: "bg-green-100 text-green-800 border-green-200 dark:bg-green-950 dark:text-green-300 dark:border-green-800" },
6
+ operational_with_limitations: { label: "Limited Use", variant: "default", className: "bg-amber-100 text-amber-800 border-amber-200 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800" },
7
+ under_maintenance: { label: "Under Maintenance", variant: "default", className: "bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800" },
8
+ under_repair: { label: "Under Repair", variant: "default", className: "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800" },
9
+ waiting_for_parts: { label: "Waiting for Parts", variant: "default", className: "bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-300 dark:border-yellow-800" },
10
+ awaiting_release: { label: "Awaiting Release", variant: "default", className: "bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800" },
11
+ out_of_service: { label: "Out of Service", variant: "default", className: "bg-red-100 text-red-800 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-800" },
12
+ standby: { label: "Standby", variant: "default", className: "bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700" },
13
+ decommissioned: { label: "Decommissioned", variant: "default", className: "bg-slate-200 text-slate-600 border-slate-300 line-through dark:bg-slate-800 dark:text-slate-400 dark:border-slate-700" },
14
+ }
15
+
16
+ const criticalityConfig: Record<string, { label: string; className: string }> = {
17
+ critical: { label: "Critical", className: "bg-red-100 text-red-800 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-800" },
18
+ high: { label: "High", className: "bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800" },
19
+ medium: { label: "Medium", className: "bg-yellow-100 text-yellow-800 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-300 dark:border-yellow-800" },
20
+ low: { label: "Low", className: "bg-green-100 text-green-800 border-green-200 dark:bg-green-950 dark:text-green-300 dark:border-green-800" },
21
+ }
22
+
23
+ const riskConfig: Record<string, { label: string; className: string }> = {
24
+ class_i: { label: "Class I", className: "bg-green-50 text-green-700 border-green-200 dark:bg-green-950 dark:text-green-300 dark:border-green-800" },
25
+ class_iia: { label: "Class IIa", className: "bg-yellow-50 text-yellow-700 border-yellow-200 dark:bg-yellow-950 dark:text-yellow-300 dark:border-yellow-800" },
26
+ class_iib: { label: "Class IIb", className: "bg-orange-50 text-orange-700 border-orange-200 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800" },
27
+ class_iii: { label: "Class III", className: "bg-red-50 text-red-700 border-red-200 dark:bg-red-950 dark:text-red-300 dark:border-red-800" },
28
+ }
29
+
30
+ export function DeviceStatusBadge({ status }: { status?: string | null }) {
31
+ const config = statusConfig[status || ''] || { label: status || 'Unknown', variant: 'secondary', className: 'bg-muted text-muted-foreground' }
32
+ return (
33
+ <Badge variant="outline" className={cn("font-medium", config.className)}>
34
+ {config.label}
35
+ </Badge>
36
+ )
37
+ }
38
+
39
+ export function CriticalityBadge({ level }: { level?: string | null }) {
40
+ const config = criticalityConfig[level || ''] || { label: level || 'N/A', className: 'bg-muted text-muted-foreground' }
41
+ return (
42
+ <Badge variant="outline" className={cn("font-medium text-xs", config.className)}>
43
+ {config.label}
44
+ </Badge>
45
+ )
46
+ }
47
+
48
+ export function RiskClassBadge({ classification }: { classification?: string | null }) {
49
+ const config = riskConfig[classification || ''] || { label: classification || 'N/A', className: 'bg-muted text-muted-foreground' }
50
+ return (
51
+ <Badge variant="outline" className={cn("font-medium text-xs", config.className)}>
52
+ {config.label}
53
+ </Badge>
54
+ )
55
+ }
@@ -0,0 +1,93 @@
1
+ "use client"
2
+
3
+ import { FileText, Download } from "lucide-react"
4
+ import { Card } from "@/components/ui/card"
5
+ import { Badge } from "@/components/ui/badge"
6
+ import { cn } from "@/lib/utils"
7
+ import { Button } from "@/components/ui/button"
8
+
9
+ interface DeviceDocumentsTabProps {
10
+ documents: any[];
11
+ deviceId: string;
12
+ }
13
+
14
+ const formatSize = (bytes: number) => {
15
+ if (bytes === 0) return '0 Bytes'
16
+ const k = 1024
17
+ const sizes = ['Bytes', 'KB', 'MB', 'GB']
18
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
19
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
20
+ }
21
+
22
+ const categoryLabels: Record<string, string> = {
23
+ user_manual: "User Manual",
24
+ service_manual: "Service Manual",
25
+ certificate: "Certificate",
26
+ calibration_report: "Calibration Report",
27
+ photo: "Photo",
28
+ other: "Other"
29
+ }
30
+
31
+ export function DeviceDocumentsTab({ documents, deviceId }: DeviceDocumentsTabProps) {
32
+ return (
33
+ <div className="space-y-4">
34
+ <div className="flex justify-between items-center">
35
+ <h3 className="text-lg font-semibold">Device Documents</h3>
36
+ <Button variant="outline" size="sm" disabled>
37
+ Upload Document
38
+ </Button>
39
+ </div>
40
+
41
+ {(!documents || documents.length === 0) ? (
42
+ <div className="text-center py-12 border rounded-lg bg-muted/20">
43
+ <FileText className="w-12 h-12 text-muted-foreground mx-auto mb-4 opacity-50" />
44
+ <h4 className="text-lg font-medium text-foreground">No documents found</h4>
45
+ <p className="text-muted-foreground text-sm mt-1">Upload manuals, certificates, and other device documentation here.</p>
46
+ </div>
47
+ ) : (
48
+ <div className="border rounded-md">
49
+ <div className="grid grid-cols-12 gap-4 p-4 border-b bg-muted/50 font-medium text-sm">
50
+ <div className="col-span-4">File Name</div>
51
+ <div className="col-span-2">Category</div>
52
+ <div className="col-span-2">Size</div>
53
+ <div className="col-span-2">Visibility</div>
54
+ <div className="col-span-2 text-right">Actions</div>
55
+ </div>
56
+ <div className="divide-y">
57
+ {documents.map((doc) => (
58
+ <div key={doc.id} className="grid grid-cols-12 gap-4 p-4 items-center text-sm hover:bg-muted/20 transition-colors">
59
+ <div className="col-span-4 font-medium flex items-center gap-2 truncate">
60
+ <FileText className="w-4 h-4 text-muted-foreground flex-shrink-0" />
61
+ <span className="truncate">{doc.fileName}</span>
62
+ </div>
63
+ <div className="col-span-2">
64
+ <Badge variant="secondary" className="font-normal">
65
+ {categoryLabels[doc.category] || doc.category}
66
+ </Badge>
67
+ </div>
68
+ <div className="col-span-2 text-muted-foreground">
69
+ {formatSize(doc.fileSize)}
70
+ </div>
71
+ <div className="col-span-2">
72
+ <Badge variant="outline" className={cn(
73
+ "font-normal capitalize",
74
+ doc.visibility === 'biomedical_only' ? 'text-purple-700 bg-purple-50 border-purple-200' : 'text-green-700 bg-green-50 border-green-200'
75
+ )}>
76
+ {doc.visibility.replace('_', ' ')}
77
+ </Badge>
78
+ </div>
79
+ <div className="col-span-2 text-right">
80
+ <Button variant="ghost" size="icon" title="Download">
81
+ <Download className="w-4 h-4" />
82
+ <span className="sr-only">Download</span>
83
+ </Button>
84
+ </div>
85
+ </div>
86
+ ))}
87
+ </div>
88
+ </div>
89
+ )}
90
+ </div>
91
+ )
92
+ }
93
+
@@ -0,0 +1,113 @@
1
+ "use client"
2
+
3
+ import Link from "next/link"
4
+ import { Wrench } from "lucide-react"
5
+ import { Card } from "@/components/ui/card"
6
+ import { Badge } from "@/components/ui/badge"
7
+ import { cn } from "@/lib/utils"
8
+
9
+ interface DeviceMaintenanceTabProps {
10
+ maintenanceTasks: any[];
11
+ deviceId: string;
12
+ }
13
+
14
+ const statusColors: Record<string, string> = {
15
+ draft: "bg-gray-100 text-gray-800 border-gray-200",
16
+ scheduled: "bg-blue-100 text-blue-800 border-blue-200",
17
+ assigned: "bg-indigo-100 text-indigo-800 border-indigo-200",
18
+ in_progress: "bg-yellow-100 text-yellow-800 border-yellow-200",
19
+ work_complete: "bg-emerald-100 text-emerald-800 border-emerald-200",
20
+ awaiting_review: "bg-purple-100 text-purple-800 border-purple-200",
21
+ awaiting_release: "bg-orange-100 text-orange-800 border-orange-200",
22
+ returned_for_rework: "bg-rose-100 text-rose-800 border-rose-200",
23
+ closed: "bg-green-100 text-green-800 border-green-200",
24
+ cancelled: "bg-gray-100 text-gray-800 border-gray-200",
25
+ }
26
+
27
+ export function DeviceMaintenanceTab({ maintenanceTasks = [], deviceId }: DeviceMaintenanceTabProps) {
28
+ const total = maintenanceTasks.length
29
+ const completed = maintenanceTasks.filter(t => (t.statusCode || t.status) === "closed").length
30
+ const pending = total - completed
31
+ const overdue = maintenanceTasks.filter(t => t.dueDate && new Date(t.dueDate) < new Date() && (t.statusCode || t.status) !== "closed").length
32
+
33
+ return (
34
+ <div className="space-y-6">
35
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
36
+ <Card className="p-4 flex flex-col items-center justify-center text-center">
37
+ <span className="text-2xl font-bold">{total}</span>
38
+ <span className="text-sm text-muted-foreground">Total Tasks</span>
39
+ </Card>
40
+ <Card className="p-4 flex flex-col items-center justify-center text-center">
41
+ <span className="text-2xl font-bold text-emerald-600">{completed}</span>
42
+ <span className="text-sm text-muted-foreground">Completed</span>
43
+ </Card>
44
+ <Card className="p-4 flex flex-col items-center justify-center text-center">
45
+ <span className="text-2xl font-bold text-blue-600">{pending}</span>
46
+ <span className="text-sm text-muted-foreground">Pending</span>
47
+ </Card>
48
+ <Card className="p-4 flex flex-col items-center justify-center text-center">
49
+ <span className="text-2xl font-bold text-rose-600">{overdue}</span>
50
+ <span className="text-sm text-muted-foreground">Overdue</span>
51
+ </Card>
52
+ </div>
53
+
54
+ <div className="space-y-4">
55
+ <h3 className="text-lg font-semibold">Maintenance History</h3>
56
+
57
+ {maintenanceTasks.length === 0 ? (
58
+ <div className="text-center py-12 border rounded-lg bg-muted/20">
59
+ <Wrench className="w-12 h-12 text-muted-foreground mx-auto mb-4 opacity-50" />
60
+ <h4 className="text-lg font-medium text-foreground">No maintenance records</h4>
61
+ <p className="text-muted-foreground text-sm mt-1">This device has no recorded maintenance history.</p>
62
+ </div>
63
+ ) : (
64
+ <div className="grid gap-3">
65
+ {maintenanceTasks.map(task => {
66
+ const statusKey = task.statusCode || task.status || "draft"
67
+ const typeLabel = (task.maintenanceType || task.type || "corrective").replace(/_/g, " ")
68
+ const assigneeName = task.assignedEngineer?.fullName || task.assignedEngineerName || "Unassigned"
69
+ const completedDate = task.completedAt || task.completedDate
70
+ const startDate = task.startedAt || task.startDate
71
+ const dueDate = task.dueDate
72
+
73
+ return (
74
+ <Link key={task.id} href={`/maintenance/tasks/${task.id}`}>
75
+ <Card className="p-4 hover:bg-muted/50 transition-colors flex flex-col sm:flex-row sm:items-center justify-between gap-4">
76
+ <div className="space-y-1">
77
+ <div className="flex items-center gap-2">
78
+ <span className="font-mono text-sm text-muted-foreground">#{task.taskNumber}</span>
79
+ <span className="font-medium">{task.title}</span>
80
+ </div>
81
+ <div className="text-sm text-muted-foreground">
82
+ Assigned to: {assigneeName}
83
+ </div>
84
+ </div>
85
+
86
+ <div className="flex flex-wrap sm:flex-nowrap items-center gap-2">
87
+ <Badge variant="outline" className="capitalize">{typeLabel}</Badge>
88
+ <Badge variant="outline" className={cn("capitalize", statusColors[statusKey] || statusColors.draft)}>
89
+ {statusKey.replace(/_/g, " ")}
90
+ </Badge>
91
+ <div className="text-xs text-muted-foreground sm:text-right sm:w-28">
92
+ {completedDate ? (
93
+ <span>Done: {new Date(completedDate).toLocaleDateString()}</span>
94
+ ) : startDate ? (
95
+ <span>Started: {new Date(startDate).toLocaleDateString()}</span>
96
+ ) : dueDate ? (
97
+ <span>Due: {new Date(dueDate).toLocaleDateString()}</span>
98
+ ) : (
99
+ <span>—</span>
100
+ )}
101
+ </div>
102
+ </div>
103
+ </Card>
104
+ </Link>
105
+ )
106
+ })}
107
+ </div>
108
+ )}
109
+ </div>
110
+ </div>
111
+ )
112
+ }
113
+
@@ -0,0 +1,109 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { QRCodeCanvas } from "qrcode.react"
5
+ import { QrCode, ExternalLink, Copy, Check } from "lucide-react"
6
+ import Link from "next/link"
7
+ import { toast } from "sonner"
8
+
9
+ interface DeviceMiniQrProps {
10
+ deviceId: string
11
+ opaqueReference: string
12
+ assetNumber?: string
13
+ deviceName?: string
14
+ }
15
+
16
+ export function DeviceMiniQr({
17
+ deviceId,
18
+ opaqueReference,
19
+ assetNumber,
20
+ deviceName,
21
+ }: DeviceMiniQrProps) {
22
+ const [origin, setOrigin] = React.useState("")
23
+ const [copied, setCopied] = React.useState(false)
24
+
25
+ React.useEffect(() => {
26
+ if (typeof window !== "undefined") {
27
+ setOrigin(window.location.origin)
28
+ }
29
+ }, [])
30
+
31
+ const scanUrl = `${origin || ""}/scan/${opaqueReference}`
32
+
33
+ const handleCopy = async (e: React.MouseEvent) => {
34
+ e.preventDefault()
35
+ e.stopPropagation()
36
+ try {
37
+ await navigator.clipboard.writeText(scanUrl)
38
+ setCopied(true)
39
+ toast.success("Scan link copied to clipboard")
40
+ setTimeout(() => setCopied(false), 2000)
41
+ } catch {
42
+ toast.error("Failed to copy link")
43
+ }
44
+ }
45
+
46
+ return (
47
+ <div className="flex items-center gap-3.5 p-3 rounded-lg border bg-muted/40 hover:bg-muted/60 transition-colors">
48
+ {/* Mini QR Canvas with clickable link to print page */}
49
+ <Link
50
+ href={`/devices/${deviceId}/qr`}
51
+ className="bg-white p-1.5 rounded-md border shadow-2xs shrink-0 hover:ring-2 hover:ring-primary/40 transition-all cursor-pointer group"
52
+ title="Click to view & print full label"
53
+ >
54
+ <QRCodeCanvas
55
+ value={scanUrl}
56
+ size={72}
57
+ level="M"
58
+ includeMargin={false}
59
+ />
60
+ </Link>
61
+
62
+ {/* Info & quick actions */}
63
+ <div className="flex-1 min-w-0 space-y-1">
64
+ <div className="flex items-center justify-between gap-1">
65
+ <div className="flex items-center gap-1.5 text-xs font-semibold text-foreground">
66
+ <QrCode className="w-3.5 h-3.5 text-primary" />
67
+ <span>Device QR Label</span>
68
+ </div>
69
+ <span className="font-mono text-[11px] text-muted-foreground bg-background px-1.5 py-0.5 rounded border">
70
+ {opaqueReference}
71
+ </span>
72
+ </div>
73
+
74
+ <p className="text-[11px] text-muted-foreground leading-tight">
75
+ Scan to access quick helpdesk &amp; status
76
+ </p>
77
+
78
+ <div className="pt-1 flex items-center gap-3 text-xs">
79
+ <Link
80
+ href={`/devices/${deviceId}/qr`}
81
+ className="text-primary hover:underline font-medium inline-flex items-center gap-1 text-[11px]"
82
+ >
83
+ <span>View &amp; Print</span>
84
+ <ExternalLink className="w-3 h-3" />
85
+ </Link>
86
+
87
+ <button
88
+ type="button"
89
+ onClick={handleCopy}
90
+ className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-[11px] transition-colors"
91
+ >
92
+ {copied ? (
93
+ <>
94
+ <Check className="w-3 h-3 text-emerald-600" />
95
+ <span className="text-emerald-600 font-medium">Copied</span>
96
+ </>
97
+ ) : (
98
+ <>
99
+ <Copy className="w-3 h-3" />
100
+ <span>Copy Link</span>
101
+ </>
102
+ )}
103
+ </button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ )
108
+ }
109
+
@@ -0,0 +1,187 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { QrCodeDisplay } from "./qr-code-display"
5
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
6
+ import { Button } from "@/components/ui/button"
7
+ import { Printer, RefreshCw, QrCode } from "lucide-react"
8
+ import { generateNewQrLabel, recordQrPrint } from "@/lib/actions/qr"
9
+ import { useRouter } from "next/navigation"
10
+ import { toast } from "sonner"
11
+
12
+ export interface DeviceQrCardProps {
13
+ device: {
14
+ id: string;
15
+ name: string;
16
+ internalCode: string;
17
+ assetNumber: string;
18
+ hospital?: { name?: string } | null;
19
+ department?: { name?: string } | null;
20
+ };
21
+ qrLabel: {
22
+ id: string;
23
+ opaqueReference: string;
24
+ labelStatus: string;
25
+ printCount?: number | null;
26
+ } | null;
27
+ }
28
+
29
+ export function DeviceQrCard({ device, qrLabel }: DeviceQrCardProps) {
30
+ const router = useRouter()
31
+ const [isGenerating, setIsGenerating] = React.useState(false)
32
+
33
+ // Construct absolute URL for the QR code target
34
+ const [origin, setOrigin] = React.useState("")
35
+ React.useEffect(() => {
36
+ if (typeof window !== "undefined") {
37
+ setOrigin(window.location.origin)
38
+ }
39
+ }, [])
40
+
41
+ const scanTargetUrl = qrLabel
42
+ ? `${origin || ""}/scan/${qrLabel.opaqueReference}`
43
+ : ""
44
+
45
+ const handlePrint = async () => {
46
+ if (qrLabel?.id) {
47
+ await recordQrPrint(qrLabel.id)
48
+ }
49
+ window.print()
50
+ }
51
+
52
+ const handleRegenerate = async () => {
53
+ if (!confirm("Are you sure you want to regenerate this QR label? The previous QR code will be revoked.")) {
54
+ return
55
+ }
56
+
57
+ setIsGenerating(true)
58
+ try {
59
+ const res = await generateNewQrLabel(device.id, "User requested label replacement")
60
+ if (res.success) {
61
+ toast.success("New QR label generated successfully")
62
+ router.refresh()
63
+ } else {
64
+ toast.error((res as any).error || "Failed to generate QR label")
65
+ }
66
+ } catch {
67
+ toast.error("An unexpected error occurred")
68
+ } finally {
69
+ setIsGenerating(false)
70
+ }
71
+ }
72
+
73
+ return (
74
+ <div className="grid gap-6 md:grid-cols-2 print:grid-cols-1">
75
+ {/* 1. Main QR Code & Actions Card */}
76
+ <Card className="print:hidden">
77
+ <CardHeader>
78
+ <CardTitle className="text-base flex items-center gap-2">
79
+ <QrCode className="w-5 h-5 text-primary" />
80
+ Active QR Code
81
+ </CardTitle>
82
+ </CardHeader>
83
+ <CardContent className="flex flex-col items-center gap-5">
84
+ {qrLabel ? (
85
+ <>
86
+ <QrCodeDisplay
87
+ value={scanTargetUrl || qrLabel.opaqueReference}
88
+ size={220}
89
+ assetNumber={device.assetNumber}
90
+ deviceName={device.name}
91
+ />
92
+
93
+ <div className="flex flex-wrap gap-2 justify-center w-full pt-2 border-t">
94
+ <Button variant="default" size="sm" onClick={handlePrint} className="gap-1.5">
95
+ <Printer className="w-4 h-4" /> Print Label
96
+ </Button>
97
+ <Button
98
+ variant="outline"
99
+ size="sm"
100
+ onClick={handleRegenerate}
101
+ disabled={isGenerating}
102
+ className="gap-1.5"
103
+ >
104
+ <RefreshCw className={`w-4 h-4 ${isGenerating ? "animate-spin" : ""}`} />
105
+ Regenerate
106
+ </Button>
107
+ </div>
108
+
109
+ <div className="text-xs text-muted-foreground space-y-1 text-center">
110
+ <p>
111
+ Opaque Reference: <span className="font-mono font-medium text-foreground">{qrLabel.opaqueReference}</span>
112
+ </p>
113
+ <p>Status: <span className="capitalize font-medium text-emerald-600 dark:text-emerald-400">{qrLabel.labelStatus}</span></p>
114
+ <p>Print Count: {qrLabel.printCount || 0}</p>
115
+ </div>
116
+ </>
117
+ ) : (
118
+ <div className="text-center py-8">
119
+ <p className="text-muted-foreground mb-4">No active QR label for this device.</p>
120
+ <Button onClick={handleRegenerate} disabled={isGenerating}>
121
+ <RefreshCw className="w-4 h-4 mr-2" /> Generate QR Label
122
+ </Button>
123
+ </div>
124
+ )}
125
+ </CardContent>
126
+ </Card>
127
+
128
+ {/* 2. Physical Thermal Label Preview (50mm x 30mm format) */}
129
+ <Card>
130
+ <CardHeader className="print:hidden">
131
+ <CardTitle className="text-base">Thermal Label Preview (50 × 30mm)</CardTitle>
132
+ </CardHeader>
133
+ <CardContent className="flex flex-col items-center justify-center p-6">
134
+ <div
135
+ id="printable-qr-label"
136
+ className="w-[280px] border-2 border-slate-900 rounded-lg p-3.5 bg-white text-slate-900 shadow-md flex flex-col justify-between aspect-[5/3]"
137
+ >
138
+ <div className="border-b pb-1">
139
+ <p className="text-[10px] font-semibold uppercase tracking-wider text-slate-500 truncate">
140
+ {device.hospital?.name || "Biomedical Engineering"}
141
+ </p>
142
+ <p className="text-sm font-bold truncate leading-tight mt-0.5">{device.name}</p>
143
+ </div>
144
+
145
+ <div className="flex items-center justify-between gap-2 my-1">
146
+ <div className="space-y-0.5">
147
+ <p className="text-[11px] font-mono font-bold tracking-tight">
148
+ {device.assetNumber}
149
+ </p>
150
+ <p className="text-[9px] text-slate-600 font-mono">
151
+ {device.internalCode}
152
+ </p>
153
+ <p className="text-[8px] text-slate-500 truncate max-w-[120px]">
154
+ {device.department?.name || "General"}
155
+ </p>
156
+ </div>
157
+
158
+ {qrLabel ? (
159
+ <div className="p-1 bg-white shrink-0">
160
+ <QrCodeDisplay
161
+ value={scanTargetUrl || qrLabel.opaqueReference}
162
+ size={64}
163
+ showDownloadOptions={false}
164
+ />
165
+ </div>
166
+ ) : (
167
+ <div className="w-16 h-16 bg-slate-100 flex items-center justify-center rounded text-[10px] text-slate-400">
168
+ QR
169
+ </div>
170
+ )}
171
+ </div>
172
+
173
+ <div className="border-t pt-1 flex items-center justify-between text-[8px] text-slate-500">
174
+ <span>Scan to view status or report</span>
175
+ <span className="font-mono">{qrLabel?.opaqueReference.slice(0, 8)}</span>
176
+ </div>
177
+ </div>
178
+
179
+ <p className="text-xs text-muted-foreground mt-4 text-center print:hidden">
180
+ Optimized for 50mm × 30mm thermal label printers (Brother, Zebra, Dymo).
181
+ </p>
182
+ </CardContent>
183
+ </Card>
184
+ </div>
185
+ )
186
+ }
187
+