@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,579 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Badge } from "@/components/ui/badge"
5
+ import { cn } from "@/lib/utils"
6
+ import {
7
+ Shield,
8
+ Wrench,
9
+ Search,
10
+ Eye,
11
+ Ruler,
12
+ Zap,
13
+ Activity,
14
+ PackagePlus,
15
+ Sliders,
16
+ Archive,
17
+ Ban,
18
+ Calendar,
19
+ Clock,
20
+ AlertCircle,
21
+ AlertTriangle,
22
+ CheckCircle2,
23
+ XCircle,
24
+ MinusCircle,
25
+ ExternalLink,
26
+ FileEdit,
27
+ UserCheck,
28
+ RotateCcw,
29
+ CheckCheck,
30
+ HelpCircle,
31
+ Minus,
32
+ ArrowUp,
33
+ ArrowDown,
34
+ Sparkles,
35
+ } from "lucide-react"
36
+
37
+ /**
38
+ * Format string with underscore to capitalized readable words
39
+ */
40
+ function formatLabel(value?: string | null): string {
41
+ if (!value) return "Unknown"
42
+ return value
43
+ .replace(/_/g, " ")
44
+ .replace(/\b\w/g, (char) => char.toUpperCase())
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // 1. MaintenanceStatusBadge
49
+ // ---------------------------------------------------------------------------
50
+ interface MaintenanceStatusConfig {
51
+ label: string
52
+ className: string
53
+ icon: React.ComponentType<{ className?: string }>
54
+ }
55
+
56
+ const maintenanceStatusConfig: Record<string, MaintenanceStatusConfig> = {
57
+ draft: {
58
+ label: "Draft",
59
+ className: "bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
60
+ icon: FileEdit,
61
+ },
62
+ assigned: {
63
+ label: "Assigned",
64
+ className: "bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
65
+ icon: UserCheck,
66
+ },
67
+ in_progress: {
68
+ label: "In Progress",
69
+ className: "bg-indigo-100 text-indigo-800 border-indigo-300 dark:bg-indigo-950 dark:text-indigo-300 dark:border-indigo-800",
70
+ icon: Activity,
71
+ },
72
+ waiting_dependency: {
73
+ label: "Waiting Dependency",
74
+ className: "bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
75
+ icon: Clock,
76
+ },
77
+ work_complete: {
78
+ label: "Work Complete",
79
+ className: "bg-cyan-100 text-cyan-800 border-cyan-300 dark:bg-cyan-950 dark:text-cyan-300 dark:border-cyan-800",
80
+ icon: CheckCheck,
81
+ },
82
+ awaiting_review: {
83
+ label: "Awaiting Review",
84
+ className: "bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800",
85
+ icon: Eye,
86
+ },
87
+ returned_for_rework: {
88
+ label: "Returned for Rework",
89
+ className: "bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
90
+ icon: RotateCcw,
91
+ },
92
+ awaiting_release: {
93
+ label: "Awaiting Release",
94
+ className: "bg-violet-100 text-violet-800 border-violet-300 dark:bg-violet-950 dark:text-violet-300 dark:border-violet-800",
95
+ icon: Sparkles,
96
+ },
97
+ closed: {
98
+ label: "Closed",
99
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
100
+ icon: CheckCircle2,
101
+ },
102
+ cancelled: {
103
+ label: "Cancelled",
104
+ className: "bg-rose-100 text-rose-800 border-rose-300 line-through dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
105
+ icon: XCircle,
106
+ },
107
+ }
108
+
109
+ export interface MaintenanceStatusBadgeProps {
110
+ status?: string | null
111
+ className?: string
112
+ showIcon?: boolean
113
+ }
114
+
115
+ export function MaintenanceStatusBadge({
116
+ status,
117
+ className,
118
+ showIcon = false,
119
+ }: MaintenanceStatusBadgeProps) {
120
+ const normalized = status ? status.toLowerCase() : ""
121
+ const config = maintenanceStatusConfig[normalized] || {
122
+ label: formatLabel(status),
123
+ className: "bg-muted text-muted-foreground border-border",
124
+ icon: HelpCircle,
125
+ }
126
+ const Icon = config.icon
127
+
128
+ return (
129
+ <Badge
130
+ variant="outline"
131
+ className={cn("inline-flex items-center gap-1.5 font-medium text-xs", config.className, className)}
132
+ >
133
+ {showIcon && <Icon className="h-3 w-3 shrink-0" />}
134
+ <span>{config.label}</span>
135
+ </Badge>
136
+ )
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // 2. MaintenanceTypeBadge
141
+ // ---------------------------------------------------------------------------
142
+ interface MaintenanceTypeConfig {
143
+ label: string
144
+ className: string
145
+ icon: React.ComponentType<{ className?: string }>
146
+ }
147
+
148
+ const maintenanceTypeConfig: Record<string, MaintenanceTypeConfig> = {
149
+ preventive_maintenance: {
150
+ label: "Preventive Maintenance",
151
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
152
+ icon: Shield,
153
+ },
154
+ corrective_maintenance: {
155
+ label: "Corrective Maintenance",
156
+ className: "bg-orange-100 text-orange-800 border-orange-300 dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
157
+ icon: Wrench,
158
+ },
159
+ troubleshooting: {
160
+ label: "Troubleshooting",
161
+ className: "bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
162
+ icon: Search,
163
+ },
164
+ inspection: {
165
+ label: "Inspection",
166
+ className: "bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
167
+ icon: Eye,
168
+ },
169
+ calibration: {
170
+ label: "Calibration",
171
+ className: "bg-purple-100 text-purple-800 border-purple-300 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800",
172
+ icon: Ruler,
173
+ },
174
+ electrical_safety_testing: {
175
+ label: "Electrical Safety Testing",
176
+ className: "bg-yellow-100 text-yellow-800 border-yellow-300 dark:bg-yellow-950 dark:text-yellow-300 dark:border-yellow-800",
177
+ icon: Zap,
178
+ },
179
+ performance_testing: {
180
+ label: "Performance Testing",
181
+ className: "bg-teal-100 text-teal-800 border-teal-300 dark:bg-teal-950 dark:text-teal-300 dark:border-teal-800",
182
+ icon: Activity,
183
+ },
184
+ installation_commissioning: {
185
+ label: "Installation / Commissioning",
186
+ className: "bg-sky-100 text-sky-800 border-sky-300 dark:bg-sky-950 dark:text-sky-300 dark:border-sky-800",
187
+ icon: PackagePlus,
188
+ },
189
+ software_configuration: {
190
+ label: "Software Configuration",
191
+ className: "bg-indigo-100 text-indigo-800 border-indigo-300 dark:bg-indigo-950 dark:text-indigo-300 dark:border-indigo-800",
192
+ icon: Sliders,
193
+ },
194
+ decommissioning: {
195
+ label: "Decommissioning",
196
+ className: "bg-rose-100 text-rose-800 border-rose-300 dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
197
+ icon: Ban,
198
+ },
199
+ other: {
200
+ label: "Other Maintenance",
201
+ className: "bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
202
+ icon: Wrench,
203
+ },
204
+ }
205
+
206
+ export interface MaintenanceTypeBadgeProps {
207
+ type?: string | null
208
+ className?: string
209
+ showIcon?: boolean
210
+ }
211
+
212
+ export function MaintenanceTypeBadge({
213
+ type,
214
+ className,
215
+ showIcon = true,
216
+ }: MaintenanceTypeBadgeProps) {
217
+ const normalized = type ? type.toLowerCase() : ""
218
+ const config = maintenanceTypeConfig[normalized] || {
219
+ label: formatLabel(type),
220
+ className: "bg-muted text-muted-foreground border-border",
221
+ icon: Wrench,
222
+ }
223
+ const Icon = config.icon
224
+
225
+ return (
226
+ <Badge
227
+ variant="outline"
228
+ className={cn("inline-flex items-center gap-1.5 font-medium text-xs", config.className, className)}
229
+ >
230
+ {showIcon && <Icon className="h-3 w-3 shrink-0" />}
231
+ <span>{config.label}</span>
232
+ </Badge>
233
+ )
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // 3. DueStateBadge
238
+ // ---------------------------------------------------------------------------
239
+ interface DueStateConfig {
240
+ label: string
241
+ className: string
242
+ icon: React.ComponentType<{ className?: string }>
243
+ }
244
+
245
+ const dueStateConfig: Record<string, DueStateConfig> = {
246
+ scheduled: {
247
+ label: "Scheduled",
248
+ className: "bg-blue-100 text-blue-800 border-blue-300 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
249
+ icon: Calendar,
250
+ },
251
+ due_soon: {
252
+ label: "Due Soon",
253
+ className: "bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
254
+ icon: Clock,
255
+ },
256
+ due_today: {
257
+ label: "Due Today",
258
+ className: "bg-orange-100 text-orange-800 border-orange-300 font-semibold dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
259
+ icon: AlertCircle,
260
+ },
261
+ overdue: {
262
+ label: "Overdue",
263
+ className: "bg-red-100 text-red-800 border-red-400 animate-pulse font-bold dark:bg-red-950 dark:text-red-300 dark:border-red-800",
264
+ icon: AlertTriangle,
265
+ },
266
+ completed: {
267
+ label: "Completed",
268
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
269
+ icon: CheckCircle2,
270
+ },
271
+ deferred: {
272
+ label: "Deferred",
273
+ className: "bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
274
+ icon: Clock,
275
+ },
276
+ cancelled: {
277
+ label: "Cancelled",
278
+ className: "bg-gray-100 text-gray-500 border-gray-300 line-through dark:bg-gray-800 dark:text-gray-400 dark:border-gray-700",
279
+ icon: XCircle,
280
+ },
281
+ }
282
+
283
+ export interface DueStateBadgeProps {
284
+ state?: string | null
285
+ dueState?: string | null
286
+ className?: string
287
+ showIcon?: boolean
288
+ }
289
+
290
+ export function DueStateBadge({
291
+ state,
292
+ dueState,
293
+ className,
294
+ showIcon = true,
295
+ }: DueStateBadgeProps) {
296
+ const target = state || dueState
297
+ const normalized = target ? target.toLowerCase() : ""
298
+ const config = dueStateConfig[normalized] || {
299
+ label: formatLabel(target),
300
+ className: "bg-muted text-muted-foreground border-border",
301
+ icon: Calendar,
302
+ }
303
+ const Icon = config.icon
304
+
305
+ return (
306
+ <Badge
307
+ variant="outline"
308
+ className={cn("inline-flex items-center gap-1.5 font-medium text-xs", config.className, className)}
309
+ >
310
+ {showIcon && <Icon className="h-3 w-3 shrink-0" />}
311
+ <span>{config.label}</span>
312
+ </Badge>
313
+ )
314
+ }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // 4. PriorityBadge
318
+ // ---------------------------------------------------------------------------
319
+ interface PriorityConfig {
320
+ label: string
321
+ className: string
322
+ icon?: React.ComponentType<{ className?: string }>
323
+ }
324
+
325
+ const priorityConfig: Record<string, PriorityConfig> = {
326
+ p1_critical: {
327
+ label: "P1 Critical",
328
+ className: "bg-red-100 text-red-800 border-red-300 font-bold dark:bg-red-950 dark:text-red-300 dark:border-red-800",
329
+ icon: AlertTriangle,
330
+ },
331
+ critical: {
332
+ label: "Critical",
333
+ className: "bg-red-100 text-red-800 border-red-300 font-bold dark:bg-red-950 dark:text-red-300 dark:border-red-800",
334
+ icon: AlertTriangle,
335
+ },
336
+ p2_high: {
337
+ label: "P2 High",
338
+ className: "bg-orange-100 text-orange-800 border-orange-300 font-semibold dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
339
+ icon: ArrowUp,
340
+ },
341
+ high: {
342
+ label: "High",
343
+ className: "bg-orange-100 text-orange-800 border-orange-300 font-semibold dark:bg-orange-950 dark:text-orange-300 dark:border-orange-800",
344
+ icon: ArrowUp,
345
+ },
346
+ p3_normal: {
347
+ label: "P3 Normal",
348
+ className: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
349
+ icon: Minus,
350
+ },
351
+ medium: {
352
+ label: "Medium",
353
+ className: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
354
+ icon: Minus,
355
+ },
356
+ normal: {
357
+ label: "Normal",
358
+ className: "bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800",
359
+ icon: Minus,
360
+ },
361
+ p4_low: {
362
+ label: "P4 Low",
363
+ className: "bg-gray-50 text-gray-600 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700",
364
+ icon: ArrowDown,
365
+ },
366
+ low: {
367
+ label: "Low",
368
+ className: "bg-gray-50 text-gray-600 border-gray-200 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700",
369
+ icon: ArrowDown,
370
+ },
371
+ }
372
+
373
+ export interface PriorityBadgeProps {
374
+ priority?: string | null
375
+ level?: string | null
376
+ className?: string
377
+ showIcon?: boolean
378
+ }
379
+
380
+ export function PriorityBadge({
381
+ priority,
382
+ level,
383
+ className,
384
+ showIcon = false,
385
+ }: PriorityBadgeProps) {
386
+ const target = priority || level
387
+ const normalized = target ? target.toLowerCase() : ""
388
+ const config = priorityConfig[normalized] || {
389
+ label: formatLabel(target),
390
+ className: "bg-muted text-muted-foreground border-border",
391
+ }
392
+ const Icon = config.icon
393
+
394
+ return (
395
+ <Badge
396
+ variant="outline"
397
+ className={cn("inline-flex items-center gap-1 font-medium text-xs", config.className, className)}
398
+ >
399
+ {showIcon && Icon && <Icon className="h-3 w-3 shrink-0" />}
400
+ <span>{config.label}</span>
401
+ </Badge>
402
+ )
403
+ }
404
+
405
+ // ---------------------------------------------------------------------------
406
+ // 5. ChecklistResultBadge
407
+ // ---------------------------------------------------------------------------
408
+ interface ChecklistResultConfig {
409
+ label: string
410
+ className: string
411
+ icon: React.ComponentType<{ className?: string }>
412
+ }
413
+
414
+ const checklistResultConfig: Record<string, ChecklistResultConfig> = {
415
+ passed: {
416
+ label: "Passed",
417
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
418
+ icon: CheckCircle2,
419
+ },
420
+ failed: {
421
+ label: "Failed",
422
+ className: "bg-red-100 text-red-800 border-red-300 font-semibold dark:bg-red-950 dark:text-red-300 dark:border-red-800",
423
+ icon: XCircle,
424
+ },
425
+ not_applicable: {
426
+ label: "Not Applicable",
427
+ className: "bg-gray-100 text-gray-600 border-gray-300 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700",
428
+ icon: MinusCircle,
429
+ },
430
+ requires_follow_up: {
431
+ label: "Requires Follow-up",
432
+ className: "bg-amber-100 text-amber-800 border-amber-300 font-semibold dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
433
+ icon: AlertTriangle,
434
+ },
435
+ }
436
+
437
+ export interface ChecklistResultBadgeProps {
438
+ result?: string | null
439
+ status?: string | null
440
+ className?: string
441
+ showIcon?: boolean
442
+ }
443
+
444
+ export function ChecklistResultBadge({
445
+ result,
446
+ status,
447
+ className,
448
+ showIcon = true,
449
+ }: ChecklistResultBadgeProps) {
450
+ const target = result || status
451
+ const normalized = target ? target.toLowerCase() : ""
452
+ const config = checklistResultConfig[normalized] || {
453
+ label: formatLabel(target),
454
+ className: "bg-muted text-muted-foreground border-border",
455
+ icon: HelpCircle,
456
+ }
457
+ const Icon = config.icon
458
+
459
+ return (
460
+ <Badge
461
+ variant="outline"
462
+ className={cn("inline-flex items-center gap-1.5 font-medium text-xs", config.className, className)}
463
+ >
464
+ {showIcon && <Icon className="h-3 w-3 shrink-0" />}
465
+ <span>{config.label}</span>
466
+ </Badge>
467
+ )
468
+ }
469
+
470
+ // ---------------------------------------------------------------------------
471
+ // 6. FinalResultBadge
472
+ // ---------------------------------------------------------------------------
473
+ interface FinalResultConfig {
474
+ label: string
475
+ className: string
476
+ icon: React.ComponentType<{ className?: string }>
477
+ }
478
+
479
+ const finalResultConfig: Record<string, FinalResultConfig> = {
480
+ passed: {
481
+ label: "Passed",
482
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
483
+ icon: CheckCircle2,
484
+ },
485
+ passed_with_limitations: {
486
+ label: "Passed with Limitations",
487
+ className: "bg-amber-100 text-amber-800 border-amber-300 font-medium dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
488
+ icon: AlertCircle,
489
+ },
490
+ failed: {
491
+ label: "Failed",
492
+ className: "bg-red-100 text-red-800 border-red-300 font-semibold dark:bg-red-950 dark:text-red-300 dark:border-red-800",
493
+ icon: XCircle,
494
+ },
495
+ no_fault_found: {
496
+ label: "No Fault Found",
497
+ className: "bg-sky-100 text-sky-800 border-sky-300 dark:bg-sky-950 dark:text-sky-300 dark:border-sky-800",
498
+ icon: Search,
499
+ },
500
+ decommission_recommended: {
501
+ label: "Decommission Recommended",
502
+ className: "bg-rose-100 text-rose-800 border-rose-300 font-semibold dark:bg-rose-950 dark:text-rose-300 dark:border-rose-800",
503
+ icon: Ban,
504
+ },
505
+ requires_external_service: {
506
+ label: "Requires External Service",
507
+ className: "bg-indigo-100 text-indigo-800 border-indigo-300 dark:bg-indigo-950 dark:text-indigo-300 dark:border-indigo-800",
508
+ icon: ExternalLink,
509
+ },
510
+ }
511
+
512
+ export interface FinalResultBadgeProps {
513
+ result?: string | null
514
+ className?: string
515
+ showIcon?: boolean
516
+ }
517
+
518
+ export function FinalResultBadge({
519
+ result,
520
+ className,
521
+ showIcon = true,
522
+ }: FinalResultBadgeProps) {
523
+ const normalized = result ? result.toLowerCase() : ""
524
+ const config = finalResultConfig[normalized] || {
525
+ label: formatLabel(result),
526
+ className: "bg-muted text-muted-foreground border-border",
527
+ icon: HelpCircle,
528
+ }
529
+ const Icon = config.icon
530
+
531
+ return (
532
+ <Badge
533
+ variant="outline"
534
+ className={cn("inline-flex items-center gap-1.5 font-medium text-xs", config.className, className)}
535
+ >
536
+ {showIcon && <Icon className="h-3 w-3 shrink-0" />}
537
+ <span>{config.label}</span>
538
+ </Badge>
539
+ )
540
+ }
541
+
542
+ // ---------------------------------------------------------------------------
543
+ // 7. MaintenancePlanStatusBadge (Helper)
544
+ // ---------------------------------------------------------------------------
545
+ const planStatusConfig: Record<string, { label: string; className: string }> = {
546
+ active: {
547
+ label: "Active",
548
+ className: "bg-emerald-100 text-emerald-800 border-emerald-300 dark:bg-emerald-950 dark:text-emerald-300 dark:border-emerald-800",
549
+ },
550
+ paused: {
551
+ label: "Paused",
552
+ className: "bg-amber-100 text-amber-800 border-amber-300 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800",
553
+ },
554
+ archived: {
555
+ label: "Archived",
556
+ className: "bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700",
557
+ },
558
+ }
559
+
560
+ export function MaintenancePlanStatusBadge({
561
+ status,
562
+ className,
563
+ }: {
564
+ status?: string | null
565
+ className?: string
566
+ }) {
567
+ const normalized = status ? status.toLowerCase() : ""
568
+ const config = planStatusConfig[normalized] || {
569
+ label: formatLabel(status),
570
+ className: "bg-muted text-muted-foreground border-border",
571
+ }
572
+
573
+ return (
574
+ <Badge variant="outline" className={cn("font-medium text-xs", config.className, className)}>
575
+ {config.label}
576
+ </Badge>
577
+ )
578
+ }
579
+
@@ -0,0 +1,107 @@
1
+ "use client"
2
+
3
+ import Link from "next/link"
4
+ import { usePathname } from "next/navigation"
5
+ import {
6
+ Wrench,
7
+ ClipboardList,
8
+ Calendar,
9
+ CheckSquare,
10
+ Plus,
11
+ } from "lucide-react"
12
+ import { cn } from "@/lib/utils"
13
+ import { Button } from "@/components/ui/button"
14
+
15
+ interface MaintenanceNavProps {
16
+ className?: string
17
+ showNewTaskButton?: boolean
18
+ }
19
+
20
+ const TABS = [
21
+ {
22
+ href: "/maintenance",
23
+ label: "Overview",
24
+ icon: Wrench,
25
+ exact: true,
26
+ },
27
+ {
28
+ href: "/maintenance/tasks",
29
+ label: "Work Orders",
30
+ icon: ClipboardList,
31
+ exact: false,
32
+ },
33
+ {
34
+ href: "/maintenance/plans",
35
+ label: "PM Plans",
36
+ icon: Calendar,
37
+ exact: false,
38
+ },
39
+ {
40
+ href: "/maintenance/checklists",
41
+ label: "Checklists",
42
+ icon: CheckSquare,
43
+ exact: false,
44
+ },
45
+ ]
46
+
47
+ export function MaintenanceNav({
48
+ className,
49
+ showNewTaskButton = true,
50
+ }: MaintenanceNavProps) {
51
+ const pathname = usePathname()
52
+
53
+ const isTabActive = (href: string, exact: boolean) => {
54
+ if (exact) {
55
+ return pathname === href
56
+ }
57
+ return pathname === href || pathname.startsWith(href + "/")
58
+ }
59
+
60
+ return (
61
+ <div
62
+ className={cn(
63
+ "flex flex-col sm:flex-row sm:items-center justify-between gap-3 border-b border-border pb-3 mb-6",
64
+ className
65
+ )}
66
+ >
67
+ {/* Navigation Tabs */}
68
+ <nav
69
+ aria-label="Maintenance navigation"
70
+ className="flex items-center gap-1 overflow-x-auto no-scrollbar py-0.5"
71
+ >
72
+ {TABS.map((tab) => {
73
+ const active = isTabActive(tab.href, tab.exact)
74
+ const Icon = tab.icon
75
+
76
+ return (
77
+ <Link
78
+ key={tab.href}
79
+ href={tab.href}
80
+ className={cn(
81
+ "flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 shrink-0 select-none",
82
+ active
83
+ ? "bg-primary text-primary-foreground shadow-sm"
84
+ : "text-muted-foreground hover:text-foreground hover:bg-muted/60"
85
+ )}
86
+ >
87
+ <Icon className={cn("w-4 h-4", active ? "text-primary-foreground" : "text-muted-foreground")} />
88
+ <span>{tab.label}</span>
89
+ </Link>
90
+ )
91
+ })}
92
+ </nav>
93
+
94
+ {/* Action button */}
95
+ {showNewTaskButton && (
96
+ <div className="flex items-center gap-2 shrink-0">
97
+ <Link href="/maintenance/tasks/new">
98
+ <Button size="sm" className="gap-1.5 shadow-sm">
99
+ <Plus className="w-4 h-4" />
100
+ <span>New Task</span>
101
+ </Button>
102
+ </Link>
103
+ </div>
104
+ )}
105
+ </div>
106
+ )
107
+ }