@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.
- package/LICENSE +22 -0
- package/README.md +203 -2
- package/drizzle.config.ts +11 -0
- package/next.config.ts +51 -0
- package/package.json +99 -3
- package/postcss.config.mjs +7 -0
- package/public/hospital_demo_devices.csv +32 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon-maskable-512.png +0 -0
- package/public/manifest.webmanifest +31 -0
- package/public/sw.js +177 -0
- package/src/app/(auth)/layout.tsx +20 -0
- package/src/app/(auth)/login/page.tsx +135 -0
- package/src/app/(dashboard)/admin/audit-logs/page.tsx +178 -0
- package/src/app/(dashboard)/admin/data/page.tsx +37 -0
- package/src/app/(dashboard)/admin/departments/[deptId]/edit/page.tsx +206 -0
- package/src/app/(dashboard)/admin/departments/[deptId]/page.tsx +71 -0
- package/src/app/(dashboard)/admin/departments/new/page.tsx +6 -0
- package/src/app/(dashboard)/admin/departments/page.tsx +6 -0
- package/src/app/(dashboard)/admin/general/page.tsx +374 -0
- package/src/app/(dashboard)/admin/hospitals/[hospitalId]/edit/page.tsx +304 -0
- package/src/app/(dashboard)/admin/hospitals/[hospitalId]/page.tsx +94 -0
- package/src/app/(dashboard)/admin/hospitals/new/page.tsx +302 -0
- package/src/app/(dashboard)/admin/hospitals/page.tsx +6 -0
- package/src/app/(dashboard)/admin/layout.tsx +49 -0
- package/src/app/(dashboard)/admin/organizations/[orgId]/edit/page.tsx +172 -0
- package/src/app/(dashboard)/admin/organizations/[orgId]/page.tsx +82 -0
- package/src/app/(dashboard)/admin/organizations/new/page.tsx +150 -0
- package/src/app/(dashboard)/admin/organizations/page.tsx +6 -0
- package/src/app/(dashboard)/admin/page.tsx +258 -0
- package/src/app/(dashboard)/admin/roles/page.tsx +153 -0
- package/src/app/(dashboard)/admin/settings/page.tsx +69 -0
- package/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +153 -0
- package/src/app/(dashboard)/admin/users/[userId]/page.tsx +317 -0
- package/src/app/(dashboard)/admin/users/invite/page.tsx +3 -0
- package/src/app/(dashboard)/admin/users/new/page.tsx +5 -0
- package/src/app/(dashboard)/admin/users/page.tsx +23 -0
- package/src/app/(dashboard)/dashboard/page.tsx +462 -0
- package/src/app/(dashboard)/departments/[deptId]/page.tsx +39 -0
- package/src/app/(dashboard)/departments/new/page.tsx +330 -0
- package/src/app/(dashboard)/departments/page.tsx +52 -0
- package/src/app/(dashboard)/devices/[id]/edit/page.tsx +131 -0
- package/src/app/(dashboard)/devices/[id]/page.tsx +408 -0
- package/src/app/(dashboard)/devices/[id]/qr/page.tsx +54 -0
- package/src/app/(dashboard)/devices/new/page.tsx +382 -0
- package/src/app/(dashboard)/devices/page.tsx +80 -0
- package/src/app/(dashboard)/engineering-team/page.tsx +41 -0
- package/src/app/(dashboard)/layout.tsx +43 -0
- package/src/app/(dashboard)/maintenance/checklists/page.tsx +57 -0
- package/src/app/(dashboard)/maintenance/page.tsx +231 -0
- package/src/app/(dashboard)/maintenance/plans/page.tsx +142 -0
- package/src/app/(dashboard)/maintenance/tasks/[id]/page.tsx +997 -0
- package/src/app/(dashboard)/maintenance/tasks/page.tsx +119 -0
- package/src/app/(dashboard)/more/page.tsx +310 -0
- package/src/app/(dashboard)/my-department/page.tsx +151 -0
- package/src/app/(dashboard)/notifications/notification-list.tsx +103 -0
- package/src/app/(dashboard)/notifications/page.tsx +59 -0
- package/src/app/(dashboard)/profile/page.tsx +27 -0
- package/src/app/(dashboard)/reports/costs/page.tsx +127 -0
- package/src/app/(dashboard)/reports/history/page.tsx +105 -0
- package/src/app/(dashboard)/reports/inventory/page.tsx +116 -0
- package/src/app/(dashboard)/reports/maintenance/page.tsx +136 -0
- package/src/app/(dashboard)/reports/page.tsx +120 -0
- package/src/app/(dashboard)/reports/tickets/page.tsx +139 -0
- package/src/app/(dashboard)/reports/workload/page.tsx +135 -0
- package/src/app/(dashboard)/rounds/[id]/page.tsx +197 -0
- package/src/app/(dashboard)/rounds/history/page.tsx +82 -0
- package/src/app/(dashboard)/rounds/page.tsx +107 -0
- package/src/app/(dashboard)/scan/[code]/page.tsx +154 -0
- package/src/app/(dashboard)/scan/page.tsx +73 -0
- package/src/app/(dashboard)/team/page.tsx +42 -0
- package/src/app/(dashboard)/tickets/[id]/page.tsx +465 -0
- package/src/app/(dashboard)/tickets/create/page.tsx +722 -0
- package/src/app/(dashboard)/tickets/page.tsx +55 -0
- package/src/app/(dashboard)/tickets/triage/page.tsx +161 -0
- package/src/app/api/admin/data/export/route.ts +316 -0
- package/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/src/app/api/health/route.ts +46 -0
- package/src/app/api/notifications/sse/route.ts +91 -0
- package/src/app/api/notifications/subscribe/route.ts +54 -0
- package/src/app/api/notifications/vapid-key/route.ts +7 -0
- package/src/app/api/reports/export/route.ts +102 -0
- package/src/app/api/tickets/[id]/attachments/route.ts +129 -0
- package/src/app/api/tickets/[id]/live-sync/route.ts +52 -0
- package/src/app/error.tsx +54 -0
- package/src/app/globals.css +89 -0
- package/src/app/layout.tsx +59 -0
- package/src/app/not-found.tsx +32 -0
- package/src/app/page.tsx +5 -0
- package/src/components/admin/add-department-user-dialog.tsx +759 -0
- package/src/components/admin/add-engineer-dialog.tsx +773 -0
- package/src/components/admin/add-user-type-dialog.tsx +89 -0
- package/src/components/admin/audit-log-export.tsx +113 -0
- package/src/components/admin/audit-log-filters.tsx +255 -0
- package/src/components/admin/audit-log-table.tsx +383 -0
- package/src/components/admin/data-management/danger-zone.tsx +301 -0
- package/src/components/admin/data-management/data-management-tabs.tsx +61 -0
- package/src/components/admin/data-management/export-center.tsx +301 -0
- package/src/components/admin/data-management/import-wizard.tsx +558 -0
- package/src/components/admin/settings-forms/features-form.tsx +115 -0
- package/src/components/admin/settings-forms/health-panel.tsx +182 -0
- package/src/components/admin/settings-forms/localization-form.tsx +203 -0
- package/src/components/admin/settings-forms/retention-form.tsx +164 -0
- package/src/components/admin/settings-forms/security-form.tsx +186 -0
- package/src/components/admin/system-settings-tabs.tsx +88 -0
- package/src/components/admin/user-role-dialog.tsx +293 -0
- package/src/components/admin/user-status-dialog.tsx +166 -0
- package/src/components/admin/users-page-client.tsx +514 -0
- package/src/components/dashboard/action-buttons.tsx +93 -0
- package/src/components/dashboard/admin-alerts.tsx +86 -0
- package/src/components/dashboard/availability-alerts.tsx +65 -0
- package/src/components/dashboard/maintenance-due-list.tsx +87 -0
- package/src/components/dashboard/round-widget.tsx +110 -0
- package/src/components/dashboard/stat-card.tsx +38 -0
- package/src/components/dashboard/ticket-list-compact.tsx +86 -0
- package/src/components/departments/department-cards-view.tsx +244 -0
- package/src/components/departments/department-detail-view.tsx +467 -0
- package/src/components/departments/department-visuals.tsx +190 -0
- package/src/components/departments/my-department-view.tsx +333 -0
- package/src/components/devices/change-status-dialog.tsx +146 -0
- package/src/components/devices/device-action-buttons.tsx +125 -0
- package/src/components/devices/device-availability-banner.tsx +59 -0
- package/src/components/devices/device-badges.tsx +55 -0
- package/src/components/devices/device-documents-tab.tsx +93 -0
- package/src/components/devices/device-maintenance-tab.tsx +113 -0
- package/src/components/devices/device-mini-qr.tsx +109 -0
- package/src/components/devices/device-qr-card.tsx +187 -0
- package/src/components/devices/device-signatures-tab.tsx +607 -0
- package/src/components/devices/device-tickets-tab.tsx +139 -0
- package/src/components/devices/device-timelines.tsx +124 -0
- package/src/components/devices/qr-camera-scanner.tsx +167 -0
- package/src/components/devices/qr-code-display.tsx +139 -0
- package/src/components/devices/quick-add-category-dialog.tsx +135 -0
- package/src/components/devices/quick-add-manufacturer-dialog.tsx +212 -0
- package/src/components/devices/transfer-device-dialog.tsx +190 -0
- package/src/components/layout/header.tsx +80 -0
- package/src/components/layout/mobile-nav.tsx +91 -0
- package/src/components/layout/more-item-badge.tsx +15 -0
- package/src/components/layout/nav-badge.tsx +72 -0
- package/src/components/layout/sidebar.tsx +297 -0
- package/src/components/maintenance/maintenance-badges.tsx +579 -0
- package/src/components/maintenance/maintenance-nav.tsx +107 -0
- package/src/components/maintenance/parts-costs-manager.tsx +909 -0
- package/src/components/maintenance/signature-card.tsx +413 -0
- package/src/components/maintenance/task-action-bar.tsx +955 -0
- package/src/components/notifications/notification-bell.tsx +367 -0
- package/src/components/notifications/notification-item.tsx +93 -0
- package/src/components/notifications/notification-preferences.tsx +182 -0
- package/src/components/profile/user-profile-view.tsx +626 -0
- package/src/components/providers/nav-counts-provider.tsx +235 -0
- package/src/components/providers/session-provider.tsx +7 -0
- package/src/components/providers/theme-provider.tsx +124 -0
- package/src/components/pwa/install-prompt.tsx +82 -0
- package/src/components/pwa/sw-register.tsx +43 -0
- package/src/components/reports/export-button.tsx +62 -0
- package/src/components/reports/filter-chips.tsx +47 -0
- package/src/components/reports/summary-cards.tsx +40 -0
- package/src/components/reports/transparency-header.tsx +54 -0
- package/src/components/rounds/department-visit-sheet.tsx +199 -0
- package/src/components/rounds/mention-textarea.tsx +187 -0
- package/src/components/rounds/round-calendar.tsx +233 -0
- package/src/components/rounds/round-department-list.tsx +281 -0
- package/src/components/rounds/round-history-client.tsx +218 -0
- package/src/components/rounds/start-round-card.tsx +184 -0
- package/src/components/shared/confirm-dialog.tsx +67 -0
- package/src/components/shared/data-table.tsx +104 -0
- package/src/components/shared/page-header.tsx +20 -0
- package/src/components/shared/sign-out-button.tsx +52 -0
- package/src/components/shared/status-badge.tsx +42 -0
- package/src/components/shared/table-search.tsx +55 -0
- package/src/components/signatures/sign-action-dialog.tsx +253 -0
- package/src/components/signatures/signature-history.tsx +121 -0
- package/src/components/signatures/signature-status-panel.tsx +164 -0
- package/src/components/team/department-coverage.tsx +141 -0
- package/src/components/team/edit-engineer-dialog.tsx +242 -0
- package/src/components/team/engineer-card.tsx +180 -0
- package/src/components/team/engineer-details-dialog.tsx +432 -0
- package/src/components/team/schedule-calendar.tsx +204 -0
- package/src/components/team/staff-engineer-team-view.tsx +368 -0
- package/src/components/team/team-header-stats.tsx +88 -0
- package/src/components/team/team-page-client.tsx +216 -0
- package/src/components/theme/theme-selector-card.tsx +63 -0
- package/src/components/theme/theme-toggle.tsx +65 -0
- package/src/components/tickets/staff-status-label.tsx +58 -0
- package/src/components/tickets/staff-ticket-card.tsx +109 -0
- package/src/components/tickets/staff-ticket-progress-tracker.tsx +405 -0
- package/src/components/tickets/staff-tickets-view.tsx +182 -0
- package/src/components/tickets/ticket-action-buttons.tsx +873 -0
- package/src/components/tickets/ticket-auto-refresh.tsx +197 -0
- package/src/components/tickets/ticket-badges.tsx +43 -0
- package/src/components/tickets/ticket-card.tsx +197 -0
- package/src/components/tickets/ticket-cards-view.tsx +395 -0
- package/src/components/tickets/ticket-comment-section.tsx +684 -0
- package/src/components/tickets/ticket-dispatch-status.tsx +184 -0
- package/src/components/tickets/ticket-list-auto-refresh.tsx +55 -0
- package/src/components/tickets/ticket-reporter-profile.tsx +235 -0
- package/src/components/tickets/ticket-signatures-panel.tsx +32 -0
- package/src/components/tickets/ticket-timeline.tsx +131 -0
- package/src/components/tickets/ticket-triage-panel.tsx +124 -0
- package/src/components/tickets/tickets-nav.tsx +107 -0
- package/src/components/ui/avatar.tsx +43 -0
- package/src/components/ui/badge.tsx +34 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/card.tsx +77 -0
- package/src/components/ui/checkbox.tsx +31 -0
- package/src/components/ui/dialog.tsx +123 -0
- package/src/components/ui/dropdown-menu.tsx +126 -0
- package/src/components/ui/form.tsx +179 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +27 -0
- package/src/components/ui/popover.tsx +34 -0
- package/src/components/ui/progress.tsx +36 -0
- package/src/components/ui/scroll-area.tsx +47 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +131 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +121 -0
- package/src/components/ui/tabs.tsx +62 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +27 -0
- package/src/lib/actions/__tests__/devices.test.ts +176 -0
- package/src/lib/actions/__tests__/signatures.test.ts +303 -0
- package/src/lib/actions/__tests__/tickets.test.ts +243 -0
- package/src/lib/actions/audit-logs.ts +298 -0
- package/src/lib/actions/checklists.ts +429 -0
- package/src/lib/actions/dashboard.ts +358 -0
- package/src/lib/actions/data-management.ts +723 -0
- package/src/lib/actions/departments.ts +369 -0
- package/src/lib/actions/device-categories.ts +117 -0
- package/src/lib/actions/devices.ts +748 -0
- package/src/lib/actions/engineer-rounds.ts +973 -0
- package/src/lib/actions/engineers.ts +285 -0
- package/src/lib/actions/hospitals.ts +148 -0
- package/src/lib/actions/locations.ts +115 -0
- package/src/lib/actions/maintenance-plans.ts +316 -0
- package/src/lib/actions/maintenance-tasks.ts +415 -0
- package/src/lib/actions/manufacturers.ts +132 -0
- package/src/lib/actions/nav-counts.ts +144 -0
- package/src/lib/actions/notifications.ts +136 -0
- package/src/lib/actions/organizations.ts +99 -0
- package/src/lib/actions/qr.ts +147 -0
- package/src/lib/actions/report-history.ts +77 -0
- package/src/lib/actions/reports.ts +192 -0
- package/src/lib/actions/roles.ts +32 -0
- package/src/lib/actions/signature-auth.ts +377 -0
- package/src/lib/actions/signatures.ts +532 -0
- package/src/lib/actions/system-settings.ts +274 -0
- package/src/lib/actions/tickets.ts +1445 -0
- package/src/lib/actions/users.ts +740 -0
- package/src/lib/audit/index.ts +103 -0
- package/src/lib/auth/__tests__/rbac.test.ts +33 -0
- package/src/lib/auth/index.ts +338 -0
- package/src/lib/auth/password.ts +10 -0
- package/src/lib/auth/rbac.ts +103 -0
- package/src/lib/auth/types.ts +57 -0
- package/src/lib/constants/departments.ts +16 -0
- package/src/lib/constants/manufacturers.ts +32 -0
- package/src/lib/db/index.ts +23 -0
- package/src/lib/db/schema/audit-logs.ts +42 -0
- package/src/lib/db/schema/departments.ts +39 -0
- package/src/lib/db/schema/device-categories.ts +38 -0
- package/src/lib/db/schema/devices.ts +195 -0
- package/src/lib/db/schema/engineer-profiles.ts +63 -0
- package/src/lib/db/schema/engineer-rounds.ts +115 -0
- package/src/lib/db/schema/enums.ts +148 -0
- package/src/lib/db/schema/hospitals.ts +34 -0
- package/src/lib/db/schema/index.ts +24 -0
- package/src/lib/db/schema/locations.ts +46 -0
- package/src/lib/db/schema/maintenance-plans.ts +99 -0
- package/src/lib/db/schema/maintenance-records.ts +161 -0
- package/src/lib/db/schema/maintenance-tasks.ts +196 -0
- package/src/lib/db/schema/manufacturers.ts +60 -0
- package/src/lib/db/schema/notifications.ts +48 -0
- package/src/lib/db/schema/organizations.ts +24 -0
- package/src/lib/db/schema/report-history.ts +28 -0
- package/src/lib/db/schema/roles.ts +59 -0
- package/src/lib/db/schema/sessions.ts +46 -0
- package/src/lib/db/schema/signature-events.ts +48 -0
- package/src/lib/db/schema/signature-policies.ts +60 -0
- package/src/lib/db/schema/signatures.ts +87 -0
- package/src/lib/db/schema/tickets.ts +169 -0
- package/src/lib/db/schema/user-assignments.ts +85 -0
- package/src/lib/db/schema/users.ts +39 -0
- package/src/lib/db/seed.ts +131 -0
- package/src/lib/logger/__tests__/logger.test.ts +154 -0
- package/src/lib/logger/formatters.ts +159 -0
- package/src/lib/logger/index.ts +184 -0
- package/src/lib/logger/types.ts +48 -0
- package/src/lib/notifications/create.ts +71 -0
- package/src/lib/notifications/dispatcher.ts +490 -0
- package/src/lib/notifications/index.ts +7 -0
- package/src/lib/notifications/push-sender.ts +128 -0
- package/src/lib/notifications/push.ts +64 -0
- package/src/lib/notifications/triggers.ts +166 -0
- package/src/lib/utils/__tests__/crypto.test.ts +30 -0
- package/src/lib/utils/__tests__/export.test.ts +55 -0
- package/src/lib/utils/__tests__/nav-badge.test.ts +147 -0
- package/src/lib/utils/__tests__/staff-status.test.ts +50 -0
- package/src/lib/utils/attestations.ts +19 -0
- package/src/lib/utils/crypto.ts +18 -0
- package/src/lib/utils/csv-parser.ts +181 -0
- package/src/lib/utils/export.ts +45 -0
- package/src/lib/utils/nav-badge.ts +225 -0
- package/src/lib/utils/sound.ts +152 -0
- package/src/lib/utils/staff-status.ts +115 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validators/__tests__/auth.test.ts +127 -0
- package/src/lib/validators/__tests__/data-management.test.ts +182 -0
- package/src/lib/validators/__tests__/devices-profile.test.ts +129 -0
- package/src/lib/validators/__tests__/devices.test.ts +98 -0
- package/src/lib/validators/__tests__/engineer-rounds.test.ts +170 -0
- package/src/lib/validators/__tests__/maintenance.test.ts +178 -0
- package/src/lib/validators/__tests__/signatures.test.ts +154 -0
- package/src/lib/validators/__tests__/system-settings.test.ts +119 -0
- package/src/lib/validators/__tests__/tickets.test.ts +176 -0
- package/src/lib/validators/__tests__/users.test.ts +92 -0
- package/src/lib/validators/auth.ts +32 -0
- package/src/lib/validators/data-management.ts +67 -0
- package/src/lib/validators/departments.ts +14 -0
- package/src/lib/validators/devices.ts +94 -0
- package/src/lib/validators/engineer-rounds.ts +70 -0
- package/src/lib/validators/hospitals.ts +17 -0
- package/src/lib/validators/locations.ts +15 -0
- package/src/lib/validators/maintenance.ts +211 -0
- package/src/lib/validators/organizations.ts +11 -0
- package/src/lib/validators/reports.ts +31 -0
- package/src/lib/validators/signatures.ts +49 -0
- package/src/lib/validators/system-settings.ts +55 -0
- package/src/lib/validators/tickets.ts +125 -0
- package/src/lib/validators/users.ts +43 -0
- package/src/middleware.ts +45 -0
- package/tailwind.config.ts +76 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,997 @@
|
|
|
1
|
+
import { getMaintenanceTaskById } from "@/lib/actions/maintenance-tasks"
|
|
2
|
+
import { getTaskPartsAndCosts } from "@/lib/actions/checklists"
|
|
3
|
+
import { getRecordSignatures } from "@/lib/actions/signatures"
|
|
4
|
+
import { db } from "@/lib/db"
|
|
5
|
+
import { maintenanceRecords } from "@/lib/db/schema"
|
|
6
|
+
import { eq } from "drizzle-orm"
|
|
7
|
+
import { PageHeader } from "@/components/shared/page-header"
|
|
8
|
+
import {
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardDescription,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "@/components/ui/card"
|
|
15
|
+
import { Button } from "@/components/ui/button"
|
|
16
|
+
import { Badge } from "@/components/ui/badge"
|
|
17
|
+
import { Separator } from "@/components/ui/separator"
|
|
18
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
19
|
+
import {
|
|
20
|
+
Table,
|
|
21
|
+
TableBody,
|
|
22
|
+
TableCell,
|
|
23
|
+
TableHead,
|
|
24
|
+
TableHeader,
|
|
25
|
+
TableRow,
|
|
26
|
+
} from "@/components/ui/table"
|
|
27
|
+
import {
|
|
28
|
+
MaintenanceStatusBadge,
|
|
29
|
+
MaintenanceTypeBadge,
|
|
30
|
+
PriorityBadge,
|
|
31
|
+
ChecklistResultBadge,
|
|
32
|
+
FinalResultBadge,
|
|
33
|
+
} from "@/components/maintenance/maintenance-badges"
|
|
34
|
+
import { DeviceStatusBadge, CriticalityBadge } from "@/components/devices/device-badges"
|
|
35
|
+
import {
|
|
36
|
+
TicketStatusBadge,
|
|
37
|
+
TicketPriorityBadge,
|
|
38
|
+
TicketImpactBadge,
|
|
39
|
+
} from "@/components/tickets/ticket-badges"
|
|
40
|
+
import { PartsCostsManager } from "@/components/maintenance/parts-costs-manager"
|
|
41
|
+
import { SignatureTimeline } from "@/components/maintenance/signature-card"
|
|
42
|
+
import { TaskActionBar } from "@/components/maintenance/task-action-bar"
|
|
43
|
+
import {
|
|
44
|
+
ArrowLeft,
|
|
45
|
+
Calendar,
|
|
46
|
+
Clock,
|
|
47
|
+
User as UserIcon,
|
|
48
|
+
MapPin,
|
|
49
|
+
Monitor,
|
|
50
|
+
CheckSquare,
|
|
51
|
+
DollarSign,
|
|
52
|
+
ShieldCheck,
|
|
53
|
+
Info,
|
|
54
|
+
AlertTriangle,
|
|
55
|
+
FileText,
|
|
56
|
+
Activity,
|
|
57
|
+
Tag,
|
|
58
|
+
Building,
|
|
59
|
+
CheckCircle2,
|
|
60
|
+
XCircle,
|
|
61
|
+
Hash,
|
|
62
|
+
ExternalLink,
|
|
63
|
+
LifeBuoy,
|
|
64
|
+
} from "lucide-react"
|
|
65
|
+
import Link from "next/link"
|
|
66
|
+
|
|
67
|
+
export default async function MaintenanceTaskDetailPage({
|
|
68
|
+
params,
|
|
69
|
+
}: {
|
|
70
|
+
params: Promise<{ id: string }>
|
|
71
|
+
}) {
|
|
72
|
+
const { id } = await params
|
|
73
|
+
|
|
74
|
+
// 1. Fetch Task and Parts/Costs concurrently
|
|
75
|
+
const [taskRes, partsRes] = await Promise.all([
|
|
76
|
+
getMaintenanceTaskById(id),
|
|
77
|
+
getTaskPartsAndCosts(id),
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
if (!taskRes?.success || !taskRes.data) {
|
|
81
|
+
return (
|
|
82
|
+
<div className="flex flex-col items-center justify-center min-h-[400px] space-y-4 p-8 text-center">
|
|
83
|
+
<div className="rounded-full bg-muted p-4">
|
|
84
|
+
<AlertTriangle className="h-8 w-8 text-muted-foreground" />
|
|
85
|
+
</div>
|
|
86
|
+
<h2 className="text-xl font-bold">Maintenance Task Not Found</h2>
|
|
87
|
+
<p className="text-sm text-muted-foreground max-w-md">
|
|
88
|
+
{taskRes?.error || "The requested maintenance task could not be loaded or does not exist."}
|
|
89
|
+
</p>
|
|
90
|
+
<Button asChild variant="outline">
|
|
91
|
+
<Link href="/maintenance/tasks">
|
|
92
|
+
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
93
|
+
Back to Maintenance Tasks
|
|
94
|
+
</Link>
|
|
95
|
+
</Button>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const task = taskRes.data
|
|
101
|
+
const partsData = partsRes?.success && partsRes.data ? partsRes.data : { parts: [], costs: [], totalPartsCost: 0, totalOtherCosts: 0, grandTotal: 0 }
|
|
102
|
+
|
|
103
|
+
// 2. Fetch Signatures and Maintenance Record details if resultRecordId exists
|
|
104
|
+
let signatures: any[] = []
|
|
105
|
+
let recordVersions: any[] = []
|
|
106
|
+
let maintenanceRecord: any = null
|
|
107
|
+
|
|
108
|
+
if (task.resultRecordId) {
|
|
109
|
+
const [sigRes, recordData] = await Promise.all([
|
|
110
|
+
getRecordSignatures(task.resultRecordId),
|
|
111
|
+
db.query.maintenanceRecords.findFirst({
|
|
112
|
+
where: eq(maintenanceRecords.id, task.resultRecordId),
|
|
113
|
+
}),
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
if (sigRes?.success && sigRes.data) {
|
|
117
|
+
signatures = sigRes.data.signatures || []
|
|
118
|
+
recordVersions = sigRes.data.versions || []
|
|
119
|
+
}
|
|
120
|
+
maintenanceRecord = recordData || null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Active work states where parts and costs are editable
|
|
124
|
+
const isEditable = [
|
|
125
|
+
"assigned",
|
|
126
|
+
"in_progress",
|
|
127
|
+
"waiting_dependency",
|
|
128
|
+
"returned_for_rework",
|
|
129
|
+
"work_complete",
|
|
130
|
+
].includes(task.statusCode)
|
|
131
|
+
|
|
132
|
+
// Checklist items & results processing
|
|
133
|
+
const templateItems: any[] =
|
|
134
|
+
(task.checklistTemplateVersion?.itemsJsonb as any[]) || []
|
|
135
|
+
const checklistResultsMap = new Map<string, any>()
|
|
136
|
+
if (Array.isArray(task.checklistResults)) {
|
|
137
|
+
task.checklistResults.forEach((r: any) => {
|
|
138
|
+
checklistResultsMap.set(r.templateItemId, r)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Combine template items with executed results or fallback to checklist results
|
|
143
|
+
const checklistRows =
|
|
144
|
+
templateItems.length > 0
|
|
145
|
+
? templateItems.map((item: any, idx: number) => {
|
|
146
|
+
const result = checklistResultsMap.get(item.id)
|
|
147
|
+
return {
|
|
148
|
+
order: item.itemOrder ?? idx + 1,
|
|
149
|
+
id: item.id,
|
|
150
|
+
label: item.label,
|
|
151
|
+
description: item.description,
|
|
152
|
+
isRequired: item.isRequired,
|
|
153
|
+
inputType: item.inputType,
|
|
154
|
+
toleranceMin: item.toleranceMin,
|
|
155
|
+
toleranceMax: item.toleranceMax,
|
|
156
|
+
unit: item.unit,
|
|
157
|
+
resultCode: result?.resultCode || null,
|
|
158
|
+
measuredValue: result?.measuredValue || null,
|
|
159
|
+
resultUnit: result?.unit || item.unit || null,
|
|
160
|
+
notes: result?.notes || null,
|
|
161
|
+
completedAt: result?.completedAt || null,
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
: (task.checklistResults || []).map((r: any, idx: number) => ({
|
|
165
|
+
order: r.itemOrder ?? idx + 1,
|
|
166
|
+
id: r.templateItemId || `item-${idx}`,
|
|
167
|
+
label: r.itemLabelSnapshot || `Checklist Item #${idx + 1}`,
|
|
168
|
+
description: null,
|
|
169
|
+
isRequired: true,
|
|
170
|
+
inputType: r.measuredValue ? "measurement" : "pass_fail",
|
|
171
|
+
toleranceMin: null,
|
|
172
|
+
toleranceMax: null,
|
|
173
|
+
unit: r.unit,
|
|
174
|
+
resultCode: r.resultCode,
|
|
175
|
+
measuredValue: r.measuredValue,
|
|
176
|
+
resultUnit: r.unit,
|
|
177
|
+
notes: r.notes,
|
|
178
|
+
completedAt: r.completedAt,
|
|
179
|
+
}))
|
|
180
|
+
|
|
181
|
+
const checklistTotal = checklistRows.length
|
|
182
|
+
const checklistPassed = checklistRows.filter(
|
|
183
|
+
(r: any) => r.resultCode === "passed"
|
|
184
|
+
).length
|
|
185
|
+
const checklistFailed = checklistRows.filter(
|
|
186
|
+
(r: any) => r.resultCode === "failed" || r.resultCode === "requires_follow_up"
|
|
187
|
+
).length
|
|
188
|
+
|
|
189
|
+
// Counts for tabs
|
|
190
|
+
const partsAndCostsCount =
|
|
191
|
+
(partsData.parts?.length || 0) + (partsData.costs?.length || 0)
|
|
192
|
+
|
|
193
|
+
// Format helper for display strings
|
|
194
|
+
const formatCategory = (cat?: string | null) => {
|
|
195
|
+
if (!cat) return "Not specified"
|
|
196
|
+
return cat
|
|
197
|
+
.replace(/_/g, " ")
|
|
198
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div className="space-y-6">
|
|
203
|
+
{/* ── Top Back Link ──────────────────────────────────────────────────── */}
|
|
204
|
+
<div>
|
|
205
|
+
<Link
|
|
206
|
+
href="/maintenance/tasks"
|
|
207
|
+
className="inline-flex items-center text-xs sm:text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
208
|
+
>
|
|
209
|
+
<ArrowLeft className="mr-1.5 h-4 w-4" />
|
|
210
|
+
Back to Maintenance Tasks
|
|
211
|
+
</Link>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* ── 1. Header Section ──────────────────────────────────────────────── */}
|
|
215
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between border-b pb-6">
|
|
216
|
+
<div className="space-y-2">
|
|
217
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
218
|
+
<span className="font-mono text-xs sm:text-sm font-semibold text-muted-foreground">
|
|
219
|
+
{task.taskNumber}
|
|
220
|
+
</span>
|
|
221
|
+
<MaintenanceStatusBadge status={task.statusCode} showIcon />
|
|
222
|
+
<PriorityBadge priority={task.priorityCode} showIcon />
|
|
223
|
+
<MaintenanceTypeBadge type={task.maintenanceType} showIcon />
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<h1 className="text-2xl sm:text-3xl font-bold tracking-tight text-foreground">
|
|
227
|
+
{task.title}
|
|
228
|
+
</h1>
|
|
229
|
+
|
|
230
|
+
<div className="flex flex-wrap items-center gap-y-1 gap-x-4 text-xs text-muted-foreground">
|
|
231
|
+
<span className="flex items-center gap-1">
|
|
232
|
+
<Building className="h-3.5 w-3.5" />
|
|
233
|
+
{task.hospital?.name || "Hospital"}
|
|
234
|
+
{task.department?.name ? ` • ${task.department.name}` : ""}
|
|
235
|
+
</span>
|
|
236
|
+
|
|
237
|
+
{task.device && (
|
|
238
|
+
<span className="flex items-center gap-1">
|
|
239
|
+
<Monitor className="h-3.5 w-3.5" />
|
|
240
|
+
<Link
|
|
241
|
+
href={`/devices/${task.deviceId}`}
|
|
242
|
+
className="font-medium text-primary hover:underline"
|
|
243
|
+
>
|
|
244
|
+
{task.device.name}
|
|
245
|
+
</Link>
|
|
246
|
+
{task.device.assetNumber && (
|
|
247
|
+
<span className="text-muted-foreground/80">
|
|
248
|
+
({task.device.assetNumber})
|
|
249
|
+
</span>
|
|
250
|
+
)}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{task.dueDate && (
|
|
255
|
+
<span className="flex items-center gap-1">
|
|
256
|
+
<Calendar className="h-3.5 w-3.5" />
|
|
257
|
+
Due {new Date(task.dueDate).toLocaleDateString()}
|
|
258
|
+
</span>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
<span className="flex items-center gap-1">
|
|
262
|
+
<Clock className="h-3.5 w-3.5" />
|
|
263
|
+
Created {new Date(task.createdAt).toLocaleDateString()}
|
|
264
|
+
</span>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* ── 2. Action Buttons Bar (Contextual) ───────────────────────────── */}
|
|
269
|
+
<div className="shrink-0 pt-1">
|
|
270
|
+
<TaskActionBar
|
|
271
|
+
taskId={task.id}
|
|
272
|
+
taskNumber={task.taskNumber}
|
|
273
|
+
statusCode={task.statusCode}
|
|
274
|
+
deviceId={task.deviceId}
|
|
275
|
+
resultRecordId={task.resultRecordId}
|
|
276
|
+
/>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
{/* ── Quick Summary Cards Row ────────────────────────────────────────── */}
|
|
281
|
+
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
|
|
282
|
+
{/* Device Snapshot Item */}
|
|
283
|
+
<div className="rounded-lg border bg-card p-3 shadow-xs">
|
|
284
|
+
<span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
|
|
285
|
+
<Monitor className="h-3.5 w-3.5 text-primary" />
|
|
286
|
+
Target Device
|
|
287
|
+
</span>
|
|
288
|
+
<div className="mt-1 font-semibold text-sm truncate">
|
|
289
|
+
{task.device ? (
|
|
290
|
+
<Link
|
|
291
|
+
href={`/devices/${task.deviceId}`}
|
|
292
|
+
className="text-foreground hover:text-primary hover:underline truncate block"
|
|
293
|
+
>
|
|
294
|
+
{task.device.name}
|
|
295
|
+
</Link>
|
|
296
|
+
) : (
|
|
297
|
+
"—"
|
|
298
|
+
)}
|
|
299
|
+
</div>
|
|
300
|
+
<div className="text-xs text-muted-foreground truncate">
|
|
301
|
+
{task.device?.assetNumber ? `Asset: ${task.device.assetNumber}` : "No asset tag"}
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
|
|
305
|
+
{/* Location & Dept */}
|
|
306
|
+
<div className="rounded-lg border bg-card p-3 shadow-xs">
|
|
307
|
+
<span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
|
|
308
|
+
<MapPin className="h-3.5 w-3.5 text-primary" />
|
|
309
|
+
Location
|
|
310
|
+
</span>
|
|
311
|
+
<div className="mt-1 font-semibold text-sm truncate">
|
|
312
|
+
{task.department?.name || task.hospital?.name || "—"}
|
|
313
|
+
</div>
|
|
314
|
+
<div className="text-xs text-muted-foreground truncate">
|
|
315
|
+
{task.hospital?.name || "Hospital Facility"}
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
|
|
319
|
+
{/* Assignee */}
|
|
320
|
+
<div className="rounded-lg border bg-card p-3 shadow-xs">
|
|
321
|
+
<span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
|
|
322
|
+
<UserIcon className="h-3.5 w-3.5 text-primary" />
|
|
323
|
+
Assigned Engineer
|
|
324
|
+
</span>
|
|
325
|
+
<div className="mt-1 font-semibold text-sm truncate">
|
|
326
|
+
{task.assignedEngineer?.fullName || "Unassigned"}
|
|
327
|
+
</div>
|
|
328
|
+
<div className="text-xs text-muted-foreground truncate">
|
|
329
|
+
{task.assignedEngineer?.email || "Biomedical Team"}
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
{/* Schedule & Due */}
|
|
334
|
+
<div className="rounded-lg border bg-card p-3 shadow-xs">
|
|
335
|
+
<span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
|
|
336
|
+
<Calendar className="h-3.5 w-3.5 text-primary" />
|
|
337
|
+
Due Date
|
|
338
|
+
</span>
|
|
339
|
+
<div className="mt-1 font-semibold text-sm font-mono truncate">
|
|
340
|
+
{task.dueDate ? new Date(task.dueDate).toLocaleDateString() : "Flexible / None"}
|
|
341
|
+
</div>
|
|
342
|
+
<div className="text-xs text-muted-foreground truncate">
|
|
343
|
+
{task.completedAt
|
|
344
|
+
? `Completed ${new Date(task.completedAt).toLocaleDateString()}`
|
|
345
|
+
: task.startedAt
|
|
346
|
+
? `Started ${new Date(task.startedAt).toLocaleDateString()}`
|
|
347
|
+
: "Not started"}
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
|
|
352
|
+
{/* ── 3. Tabs Section ────────────────────────────────────────────────── */}
|
|
353
|
+
<Tabs defaultValue="overview" className="w-full space-y-6">
|
|
354
|
+
<TabsList className="grid w-full grid-cols-4 sm:w-auto sm:inline-flex">
|
|
355
|
+
<TabsTrigger value="overview" className="gap-1.5 text-xs sm:text-sm">
|
|
356
|
+
<Info className="h-4 w-4" />
|
|
357
|
+
<span>Overview</span>
|
|
358
|
+
</TabsTrigger>
|
|
359
|
+
|
|
360
|
+
<TabsTrigger value="checklist" className="gap-1.5 text-xs sm:text-sm">
|
|
361
|
+
<CheckSquare className="h-4 w-4" />
|
|
362
|
+
<span>Checklist</span>
|
|
363
|
+
{checklistTotal > 0 && (
|
|
364
|
+
<Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
|
|
365
|
+
{checklistTotal}
|
|
366
|
+
</Badge>
|
|
367
|
+
)}
|
|
368
|
+
</TabsTrigger>
|
|
369
|
+
|
|
370
|
+
<TabsTrigger value="parts-costs" className="gap-1.5 text-xs sm:text-sm">
|
|
371
|
+
<DollarSign className="h-4 w-4" />
|
|
372
|
+
<span>Parts & Costs</span>
|
|
373
|
+
{partsAndCostsCount > 0 && (
|
|
374
|
+
<Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
|
|
375
|
+
{partsAndCostsCount}
|
|
376
|
+
</Badge>
|
|
377
|
+
)}
|
|
378
|
+
</TabsTrigger>
|
|
379
|
+
|
|
380
|
+
<TabsTrigger value="signatures" className="gap-1.5 text-xs sm:text-sm">
|
|
381
|
+
<ShieldCheck className="h-4 w-4" />
|
|
382
|
+
<span>Signatures</span>
|
|
383
|
+
{signatures.length > 0 && (
|
|
384
|
+
<Badge variant="secondary" className="ml-1 text-[11px] px-1.5 py-0 h-4">
|
|
385
|
+
{signatures.length}
|
|
386
|
+
</Badge>
|
|
387
|
+
)}
|
|
388
|
+
</TabsTrigger>
|
|
389
|
+
</TabsList>
|
|
390
|
+
|
|
391
|
+
{/* ── TAB 1: OVERVIEW ──────────────────────────────────────────────── */}
|
|
392
|
+
<TabsContent value="overview" className="space-y-6">
|
|
393
|
+
<div className="grid gap-6 md:grid-cols-3">
|
|
394
|
+
{/* Left 2 Cols: Main Content */}
|
|
395
|
+
<div className="md:col-span-2 space-y-6">
|
|
396
|
+
{/* Waiting Dependency Alert Callout */}
|
|
397
|
+
{task.statusCode === "waiting_dependency" && (
|
|
398
|
+
<div className="rounded-lg border border-amber-300 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950/30">
|
|
399
|
+
<div className="flex items-start gap-3">
|
|
400
|
+
<Clock className="h-5 w-5 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
|
|
401
|
+
<div className="space-y-1 text-sm">
|
|
402
|
+
<h4 className="font-semibold text-amber-900 dark:text-amber-200">
|
|
403
|
+
Task Paused — Waiting on Dependency
|
|
404
|
+
</h4>
|
|
405
|
+
<p className="text-amber-800 dark:text-amber-300 text-xs sm:text-sm">
|
|
406
|
+
<span className="font-semibold capitalize">
|
|
407
|
+
{task.waitingDependencyType?.replace(/_/g, " ") || "External dependency"}:
|
|
408
|
+
</span>{" "}
|
|
409
|
+
{task.waitingReason || "No specific reason recorded."}
|
|
410
|
+
</p>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
)}
|
|
415
|
+
|
|
416
|
+
{/* Task Description & Technical Category */}
|
|
417
|
+
<Card>
|
|
418
|
+
<CardHeader className="pb-3">
|
|
419
|
+
<CardTitle className="text-base font-semibold">Problem & Task Description</CardTitle>
|
|
420
|
+
</CardHeader>
|
|
421
|
+
<CardContent className="space-y-4">
|
|
422
|
+
<p className="whitespace-pre-wrap text-sm text-foreground leading-relaxed">
|
|
423
|
+
{task.description || "No specific task description provided."}
|
|
424
|
+
</p>
|
|
425
|
+
|
|
426
|
+
<div className="flex flex-wrap gap-4 pt-4 border-t">
|
|
427
|
+
<div className="space-y-1">
|
|
428
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
|
|
429
|
+
Technical Category
|
|
430
|
+
</span>
|
|
431
|
+
<div className="text-sm font-medium">
|
|
432
|
+
{formatCategory(task.technicalProblemCategory)}
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div className="space-y-1">
|
|
437
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
|
|
438
|
+
Maintenance Type
|
|
439
|
+
</span>
|
|
440
|
+
<div>
|
|
441
|
+
<MaintenanceTypeBadge type={task.maintenanceType} showIcon />
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
<div className="space-y-1">
|
|
446
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium">
|
|
447
|
+
Task Priority
|
|
448
|
+
</span>
|
|
449
|
+
<div>
|
|
450
|
+
<PriorityBadge priority={task.priorityCode} showIcon />
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
</CardContent>
|
|
455
|
+
</Card>
|
|
456
|
+
|
|
457
|
+
{/* Maintenance Execution & Findings Card (if completed or record available) */}
|
|
458
|
+
{maintenanceRecord && (
|
|
459
|
+
<Card className="border-cyan-200/70 dark:border-cyan-900/50">
|
|
460
|
+
<CardHeader className="bg-cyan-50/40 dark:bg-cyan-950/20 pb-3">
|
|
461
|
+
<div className="flex items-center justify-between">
|
|
462
|
+
<div className="flex items-center gap-2">
|
|
463
|
+
<Activity className="h-4 w-4 text-cyan-700 dark:text-cyan-400" />
|
|
464
|
+
<CardTitle className="text-base font-semibold">
|
|
465
|
+
Executed Maintenance & Resolution
|
|
466
|
+
</CardTitle>
|
|
467
|
+
</div>
|
|
468
|
+
<span className="font-mono text-xs text-muted-foreground">
|
|
469
|
+
{maintenanceRecord.recordNumber}
|
|
470
|
+
</span>
|
|
471
|
+
</div>
|
|
472
|
+
<CardDescription className="text-xs">
|
|
473
|
+
Technical findings, diagnostic conclusions, and resolution summary
|
|
474
|
+
</CardDescription>
|
|
475
|
+
</CardHeader>
|
|
476
|
+
|
|
477
|
+
<CardContent className="space-y-4 pt-4">
|
|
478
|
+
{/* Work Performed */}
|
|
479
|
+
<div className="space-y-1">
|
|
480
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
481
|
+
Work Executed
|
|
482
|
+
</span>
|
|
483
|
+
<p className="text-sm whitespace-pre-wrap text-foreground bg-muted/30 p-3 rounded-md border">
|
|
484
|
+
{maintenanceRecord.workPerformed || "Standard maintenance procedure completed."}
|
|
485
|
+
</p>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
{/* Diagnosis & Root Cause Grid */}
|
|
489
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
490
|
+
{maintenanceRecord.diagnosis && (
|
|
491
|
+
<div className="space-y-1">
|
|
492
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
493
|
+
Diagnosis
|
|
494
|
+
</span>
|
|
495
|
+
<p className="text-sm text-foreground">{maintenanceRecord.diagnosis}</p>
|
|
496
|
+
</div>
|
|
497
|
+
)}
|
|
498
|
+
|
|
499
|
+
{maintenanceRecord.rootCause && (
|
|
500
|
+
<div className="space-y-1">
|
|
501
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
502
|
+
Root Cause
|
|
503
|
+
</span>
|
|
504
|
+
<p className="text-sm text-foreground">{maintenanceRecord.rootCause}</p>
|
|
505
|
+
</div>
|
|
506
|
+
)}
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
{/* Findings & Recommendations */}
|
|
510
|
+
{(maintenanceRecord.findings || maintenanceRecord.recommendations) && (
|
|
511
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2 border-t">
|
|
512
|
+
{maintenanceRecord.findings && (
|
|
513
|
+
<div className="space-y-1">
|
|
514
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
515
|
+
Key Findings
|
|
516
|
+
</span>
|
|
517
|
+
<p className="text-sm text-muted-foreground">{maintenanceRecord.findings}</p>
|
|
518
|
+
</div>
|
|
519
|
+
)}
|
|
520
|
+
|
|
521
|
+
{maintenanceRecord.recommendations && (
|
|
522
|
+
<div className="space-y-1">
|
|
523
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
524
|
+
Recommendations
|
|
525
|
+
</span>
|
|
526
|
+
<p className="text-sm text-muted-foreground">{maintenanceRecord.recommendations}</p>
|
|
527
|
+
</div>
|
|
528
|
+
)}
|
|
529
|
+
</div>
|
|
530
|
+
)}
|
|
531
|
+
|
|
532
|
+
{/* Final Result & Device Status Badges */}
|
|
533
|
+
<div className="flex flex-wrap items-center gap-4 pt-3 border-t">
|
|
534
|
+
<div className="space-y-1">
|
|
535
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
|
|
536
|
+
Final Test Result
|
|
537
|
+
</span>
|
|
538
|
+
<FinalResultBadge result={maintenanceRecord.finalResultCode} />
|
|
539
|
+
</div>
|
|
540
|
+
|
|
541
|
+
<div className="space-y-1">
|
|
542
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
|
|
543
|
+
Final Device Status
|
|
544
|
+
</span>
|
|
545
|
+
<DeviceStatusBadge status={maintenanceRecord.finalDeviceStatusCode} />
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
{maintenanceRecord.calibrationCertificateNumber && (
|
|
549
|
+
<div className="space-y-1">
|
|
550
|
+
<span className="text-xs uppercase tracking-wider text-muted-foreground font-medium block">
|
|
551
|
+
Calibration Certificate
|
|
552
|
+
</span>
|
|
553
|
+
<span className="font-mono text-xs font-medium text-foreground">
|
|
554
|
+
{maintenanceRecord.calibrationCertificateNumber}
|
|
555
|
+
</span>
|
|
556
|
+
</div>
|
|
557
|
+
)}
|
|
558
|
+
</div>
|
|
559
|
+
</CardContent>
|
|
560
|
+
</Card>
|
|
561
|
+
)}
|
|
562
|
+
|
|
563
|
+
{/* Linked Service Ticket Card */}
|
|
564
|
+
{task.serviceTicket ? (
|
|
565
|
+
<Card>
|
|
566
|
+
<CardHeader className="pb-3">
|
|
567
|
+
<div className="flex items-center justify-between">
|
|
568
|
+
<div className="flex items-center gap-2">
|
|
569
|
+
<LifeBuoy className="h-4 w-4 text-primary" />
|
|
570
|
+
<CardTitle className="text-base font-semibold">Originating Service Ticket</CardTitle>
|
|
571
|
+
</div>
|
|
572
|
+
<Button variant="ghost" size="sm" asChild className="h-7 text-xs">
|
|
573
|
+
<Link href={`/tickets/${task.serviceTicket.id}`} className="gap-1">
|
|
574
|
+
<span>View Ticket</span>
|
|
575
|
+
<ExternalLink className="h-3 w-3" />
|
|
576
|
+
</Link>
|
|
577
|
+
</Button>
|
|
578
|
+
</div>
|
|
579
|
+
</CardHeader>
|
|
580
|
+
<CardContent className="space-y-3">
|
|
581
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
582
|
+
<span className="font-mono text-xs font-semibold text-muted-foreground">
|
|
583
|
+
{task.serviceTicket.ticketNumber}
|
|
584
|
+
</span>
|
|
585
|
+
<TicketPriorityBadge priority={task.serviceTicket.priorityCode} />
|
|
586
|
+
<TicketStatusBadge status={task.serviceTicket.statusCode} />
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<h4 className="font-semibold text-sm text-foreground">
|
|
590
|
+
{task.serviceTicket.title}
|
|
591
|
+
</h4>
|
|
592
|
+
|
|
593
|
+
<p className="text-xs sm:text-sm text-muted-foreground line-clamp-2">
|
|
594
|
+
{task.serviceTicket.description}
|
|
595
|
+
</p>
|
|
596
|
+
|
|
597
|
+
<div className="flex flex-wrap gap-4 pt-2 border-t text-xs text-muted-foreground">
|
|
598
|
+
<div>
|
|
599
|
+
<span className="font-medium text-foreground">Category:</span>{" "}
|
|
600
|
+
{task.serviceTicket.reportedProblemCategory || "General"}
|
|
601
|
+
</div>
|
|
602
|
+
<div>
|
|
603
|
+
<span className="font-medium text-foreground">Impact:</span>{" "}
|
|
604
|
+
<TicketImpactBadge impact={task.serviceTicket.reportedImpact} />
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
</CardContent>
|
|
608
|
+
</Card>
|
|
609
|
+
) : null}
|
|
610
|
+
</div>
|
|
611
|
+
|
|
612
|
+
{/* Right 1 Col: Metadata & Context */}
|
|
613
|
+
<div className="space-y-6">
|
|
614
|
+
{/* Target Device Snapshot Card */}
|
|
615
|
+
<Card>
|
|
616
|
+
<CardHeader className="pb-3">
|
|
617
|
+
<div className="flex items-center justify-between">
|
|
618
|
+
<CardTitle className="text-sm font-semibold flex items-center gap-2">
|
|
619
|
+
<Monitor className="h-4 w-4 text-primary" />
|
|
620
|
+
Device Details
|
|
621
|
+
</CardTitle>
|
|
622
|
+
{task.device && (
|
|
623
|
+
<Button variant="ghost" size="sm" asChild className="h-7 text-xs">
|
|
624
|
+
<Link href={`/devices/${task.deviceId}`} className="gap-1">
|
|
625
|
+
<span>Open</span>
|
|
626
|
+
<ExternalLink className="h-3 w-3" />
|
|
627
|
+
</Link>
|
|
628
|
+
</Button>
|
|
629
|
+
)}
|
|
630
|
+
</div>
|
|
631
|
+
</CardHeader>
|
|
632
|
+
<CardContent className="space-y-3 text-xs">
|
|
633
|
+
{task.device ? (
|
|
634
|
+
<div className="space-y-2.5">
|
|
635
|
+
<div>
|
|
636
|
+
<span className="text-muted-foreground block text-[11px]">Device Name</span>
|
|
637
|
+
<Link
|
|
638
|
+
href={`/devices/${task.deviceId}`}
|
|
639
|
+
className="font-semibold text-sm text-primary hover:underline block"
|
|
640
|
+
>
|
|
641
|
+
{task.device.name}
|
|
642
|
+
</Link>
|
|
643
|
+
</div>
|
|
644
|
+
|
|
645
|
+
<div className="grid grid-cols-2 gap-2 pt-1 border-t">
|
|
646
|
+
<div>
|
|
647
|
+
<span className="text-muted-foreground block text-[11px]">Asset Tag</span>
|
|
648
|
+
<span className="font-mono font-medium text-foreground">
|
|
649
|
+
{task.device.assetNumber || "—"}
|
|
650
|
+
</span>
|
|
651
|
+
</div>
|
|
652
|
+
<div>
|
|
653
|
+
<span className="text-muted-foreground block text-[11px]">Serial #</span>
|
|
654
|
+
<span className="font-mono font-medium text-foreground">
|
|
655
|
+
{task.device.serialNumber || "—"}
|
|
656
|
+
</span>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
<div className="grid grid-cols-2 gap-2 pt-1 border-t">
|
|
661
|
+
<div>
|
|
662
|
+
<span className="text-muted-foreground block text-[11px]">Model</span>
|
|
663
|
+
<span className="font-medium text-foreground">
|
|
664
|
+
{task.device.modelNameFree || "—"}
|
|
665
|
+
</span>
|
|
666
|
+
</div>
|
|
667
|
+
<div>
|
|
668
|
+
<span className="text-muted-foreground block text-[11px]">Criticality</span>
|
|
669
|
+
<CriticalityBadge level={task.device.criticalityLevel} />
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
<div className="pt-1 border-t">
|
|
674
|
+
<span className="text-muted-foreground block text-[11px] mb-1">
|
|
675
|
+
Current Device Status
|
|
676
|
+
</span>
|
|
677
|
+
<DeviceStatusBadge status={task.device.currentStatusCode} />
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
) : (
|
|
681
|
+
<p className="text-muted-foreground italic">No medical device linked.</p>
|
|
682
|
+
)}
|
|
683
|
+
</CardContent>
|
|
684
|
+
</Card>
|
|
685
|
+
|
|
686
|
+
{/* Assignment & People Card */}
|
|
687
|
+
<Card>
|
|
688
|
+
<CardHeader className="pb-3">
|
|
689
|
+
<CardTitle className="text-sm font-semibold flex items-center gap-2">
|
|
690
|
+
<UserIcon className="h-4 w-4 text-primary" />
|
|
691
|
+
Assignment & Responsibility
|
|
692
|
+
</CardTitle>
|
|
693
|
+
</CardHeader>
|
|
694
|
+
<CardContent className="space-y-3 text-xs">
|
|
695
|
+
<div>
|
|
696
|
+
<span className="text-muted-foreground block text-[11px]">Assigned Engineer</span>
|
|
697
|
+
<span className="font-medium text-sm text-foreground block mt-0.5">
|
|
698
|
+
{task.assignedEngineer?.fullName || "Unassigned"}
|
|
699
|
+
</span>
|
|
700
|
+
{task.assignedEngineer?.email && (
|
|
701
|
+
<span className="text-muted-foreground text-[11px]">
|
|
702
|
+
{task.assignedEngineer.email}
|
|
703
|
+
</span>
|
|
704
|
+
)}
|
|
705
|
+
</div>
|
|
706
|
+
|
|
707
|
+
<div className="pt-2 border-t">
|
|
708
|
+
<span className="text-muted-foreground block text-[11px]">Created By</span>
|
|
709
|
+
<span className="font-medium text-foreground">
|
|
710
|
+
{task.createdByUser?.fullName || "System / Automated"}
|
|
711
|
+
</span>
|
|
712
|
+
</div>
|
|
713
|
+
</CardContent>
|
|
714
|
+
</Card>
|
|
715
|
+
|
|
716
|
+
{/* Execution Milestones & Timeline Card */}
|
|
717
|
+
<Card>
|
|
718
|
+
<CardHeader className="pb-3">
|
|
719
|
+
<CardTitle className="text-sm font-semibold flex items-center gap-2">
|
|
720
|
+
<Clock className="h-4 w-4 text-primary" />
|
|
721
|
+
Milestones & Dates
|
|
722
|
+
</CardTitle>
|
|
723
|
+
</CardHeader>
|
|
724
|
+
<CardContent className="space-y-2.5 text-xs">
|
|
725
|
+
<div className="flex justify-between items-center">
|
|
726
|
+
<span className="text-muted-foreground">Created:</span>
|
|
727
|
+
<span className="font-mono text-foreground">
|
|
728
|
+
{new Date(task.createdAt).toLocaleString("en-US", {
|
|
729
|
+
month: "short",
|
|
730
|
+
day: "numeric",
|
|
731
|
+
year: "numeric",
|
|
732
|
+
hour: "2-digit",
|
|
733
|
+
minute: "2-digit",
|
|
734
|
+
})}
|
|
735
|
+
</span>
|
|
736
|
+
</div>
|
|
737
|
+
|
|
738
|
+
{task.startedAt && (
|
|
739
|
+
<div className="flex justify-between items-center pt-1 border-t">
|
|
740
|
+
<span className="text-muted-foreground">Work Started:</span>
|
|
741
|
+
<span className="font-mono text-foreground">
|
|
742
|
+
{new Date(task.startedAt).toLocaleString("en-US", {
|
|
743
|
+
month: "short",
|
|
744
|
+
day: "numeric",
|
|
745
|
+
year: "numeric",
|
|
746
|
+
hour: "2-digit",
|
|
747
|
+
minute: "2-digit",
|
|
748
|
+
})}
|
|
749
|
+
</span>
|
|
750
|
+
</div>
|
|
751
|
+
)}
|
|
752
|
+
|
|
753
|
+
{task.completedAt && (
|
|
754
|
+
<div className="flex justify-between items-center pt-1 border-t">
|
|
755
|
+
<span className="text-muted-foreground">Work Completed:</span>
|
|
756
|
+
<span className="font-mono text-foreground">
|
|
757
|
+
{new Date(task.completedAt).toLocaleString("en-US", {
|
|
758
|
+
month: "short",
|
|
759
|
+
day: "numeric",
|
|
760
|
+
year: "numeric",
|
|
761
|
+
hour: "2-digit",
|
|
762
|
+
minute: "2-digit",
|
|
763
|
+
})}
|
|
764
|
+
</span>
|
|
765
|
+
</div>
|
|
766
|
+
)}
|
|
767
|
+
|
|
768
|
+
{task.dueDate && (
|
|
769
|
+
<div className="flex justify-between items-center pt-1 border-t">
|
|
770
|
+
<span className="text-muted-foreground font-medium">Due Date:</span>
|
|
771
|
+
<span className="font-mono font-semibold text-foreground">
|
|
772
|
+
{new Date(task.dueDate).toLocaleDateString()}
|
|
773
|
+
</span>
|
|
774
|
+
</div>
|
|
775
|
+
)}
|
|
776
|
+
|
|
777
|
+
<div className="flex justify-between items-center pt-1 border-t">
|
|
778
|
+
<span className="text-muted-foreground">Last Updated:</span>
|
|
779
|
+
<span className="font-mono text-muted-foreground">
|
|
780
|
+
{new Date(task.updatedAt).toLocaleString("en-US", {
|
|
781
|
+
month: "short",
|
|
782
|
+
day: "numeric",
|
|
783
|
+
hour: "2-digit",
|
|
784
|
+
minute: "2-digit",
|
|
785
|
+
})}
|
|
786
|
+
</span>
|
|
787
|
+
</div>
|
|
788
|
+
</CardContent>
|
|
789
|
+
</Card>
|
|
790
|
+
</div>
|
|
791
|
+
</div>
|
|
792
|
+
</TabsContent>
|
|
793
|
+
|
|
794
|
+
{/* ── TAB 2: CHECKLIST ────────────────────────────────────────────── */}
|
|
795
|
+
<TabsContent value="checklist" className="space-y-6">
|
|
796
|
+
<Card>
|
|
797
|
+
<CardHeader className="pb-3">
|
|
798
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
799
|
+
<div className="space-y-1">
|
|
800
|
+
<div className="flex items-center gap-2">
|
|
801
|
+
<CheckSquare className="h-5 w-5 text-primary" />
|
|
802
|
+
<CardTitle className="text-base font-semibold">
|
|
803
|
+
Maintenance Inspection & Procedure Checklist
|
|
804
|
+
</CardTitle>
|
|
805
|
+
</div>
|
|
806
|
+
<CardDescription className="text-xs">
|
|
807
|
+
{task.checklistTemplateVersion
|
|
808
|
+
? `Template Version v${task.checklistTemplateVersion.versionNumber} • Read-only review of measured values and criteria verification`
|
|
809
|
+
: "Standard maintenance checklist execution results"}
|
|
810
|
+
</CardDescription>
|
|
811
|
+
</div>
|
|
812
|
+
|
|
813
|
+
{checklistTotal > 0 && (
|
|
814
|
+
<div className="flex items-center gap-2">
|
|
815
|
+
<Badge variant="outline" className="text-xs bg-emerald-50 text-emerald-700 border-emerald-300">
|
|
816
|
+
{checklistPassed} Passed
|
|
817
|
+
</Badge>
|
|
818
|
+
{checklistFailed > 0 && (
|
|
819
|
+
<Badge variant="outline" className="text-xs bg-red-50 text-red-700 border-red-300">
|
|
820
|
+
{checklistFailed} Issues
|
|
821
|
+
</Badge>
|
|
822
|
+
)}
|
|
823
|
+
</div>
|
|
824
|
+
)}
|
|
825
|
+
</div>
|
|
826
|
+
</CardHeader>
|
|
827
|
+
|
|
828
|
+
<CardContent className="p-0">
|
|
829
|
+
{checklistRows.length === 0 ? (
|
|
830
|
+
<div className="p-12 text-center space-y-3">
|
|
831
|
+
<CheckSquare className="mx-auto h-10 w-10 text-muted-foreground/40" />
|
|
832
|
+
<div className="space-y-1">
|
|
833
|
+
<p className="text-sm font-semibold text-foreground">No Checklist Attached</p>
|
|
834
|
+
<p className="text-xs text-muted-foreground max-w-sm mx-auto">
|
|
835
|
+
This maintenance task was created without a standardized checklist template. Work details are recorded in the task resolution.
|
|
836
|
+
</p>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
) : (
|
|
840
|
+
<div className="overflow-x-auto">
|
|
841
|
+
<Table>
|
|
842
|
+
<TableHeader>
|
|
843
|
+
<TableRow className="bg-muted/50">
|
|
844
|
+
<TableHead className="w-[60px] text-center font-semibold">#</TableHead>
|
|
845
|
+
<TableHead className="min-w-[200px] font-semibold">Checklist Item / Requirement</TableHead>
|
|
846
|
+
<TableHead className="w-[140px] font-semibold">Type & Range</TableHead>
|
|
847
|
+
<TableHead className="w-[130px] font-semibold">Result</TableHead>
|
|
848
|
+
<TableHead className="w-[130px] font-semibold">Measured Value</TableHead>
|
|
849
|
+
<TableHead className="min-w-[180px] font-semibold">Findings / Notes</TableHead>
|
|
850
|
+
</TableRow>
|
|
851
|
+
</TableHeader>
|
|
852
|
+
<TableBody>
|
|
853
|
+
{checklistRows.map((row: any) => {
|
|
854
|
+
const hasTolerance =
|
|
855
|
+
row.toleranceMin !== null &&
|
|
856
|
+
row.toleranceMin !== undefined &&
|
|
857
|
+
row.toleranceMax !== null &&
|
|
858
|
+
row.toleranceMax !== undefined
|
|
859
|
+
|
|
860
|
+
return (
|
|
861
|
+
<TableRow key={row.id} className="hover:bg-muted/30">
|
|
862
|
+
<TableCell className="text-center font-mono text-xs text-muted-foreground font-semibold">
|
|
863
|
+
{row.order}
|
|
864
|
+
</TableCell>
|
|
865
|
+
|
|
866
|
+
<TableCell>
|
|
867
|
+
<div className="font-medium text-xs sm:text-sm text-foreground flex items-center gap-1.5">
|
|
868
|
+
<span>{row.label}</span>
|
|
869
|
+
{row.isRequired && (
|
|
870
|
+
<Badge variant="secondary" className="text-[10px] px-1 py-0 h-3.5 bg-muted">
|
|
871
|
+
Required
|
|
872
|
+
</Badge>
|
|
873
|
+
)}
|
|
874
|
+
</div>
|
|
875
|
+
{row.description && (
|
|
876
|
+
<p className="text-xs text-muted-foreground mt-0.5">{row.description}</p>
|
|
877
|
+
)}
|
|
878
|
+
</TableCell>
|
|
879
|
+
|
|
880
|
+
<TableCell className="text-xs text-muted-foreground font-mono">
|
|
881
|
+
<span className="capitalize">{row.inputType.replace(/_/g, " ")}</span>
|
|
882
|
+
{hasTolerance && (
|
|
883
|
+
<span className="block text-[11px] text-muted-foreground/80">
|
|
884
|
+
[{row.toleranceMin} - {row.toleranceMax} {row.unit || ""}]
|
|
885
|
+
</span>
|
|
886
|
+
)}
|
|
887
|
+
</TableCell>
|
|
888
|
+
|
|
889
|
+
<TableCell>
|
|
890
|
+
{row.resultCode ? (
|
|
891
|
+
<ChecklistResultBadge result={row.resultCode} showIcon />
|
|
892
|
+
) : (
|
|
893
|
+
<Badge variant="outline" className="text-xs text-muted-foreground">
|
|
894
|
+
Pending
|
|
895
|
+
</Badge>
|
|
896
|
+
)}
|
|
897
|
+
</TableCell>
|
|
898
|
+
|
|
899
|
+
<TableCell className="font-mono text-xs font-semibold text-foreground">
|
|
900
|
+
{row.measuredValue ? (
|
|
901
|
+
<span>
|
|
902
|
+
{row.measuredValue} {row.resultUnit || row.unit || ""}
|
|
903
|
+
</span>
|
|
904
|
+
) : (
|
|
905
|
+
<span className="text-muted-foreground font-normal">—</span>
|
|
906
|
+
)}
|
|
907
|
+
</TableCell>
|
|
908
|
+
|
|
909
|
+
<TableCell className="text-xs text-muted-foreground">
|
|
910
|
+
{row.notes ? (
|
|
911
|
+
<span className="whitespace-pre-wrap">{row.notes}</span>
|
|
912
|
+
) : (
|
|
913
|
+
<span className="text-muted-foreground/60">—</span>
|
|
914
|
+
)}
|
|
915
|
+
</TableCell>
|
|
916
|
+
</TableRow>
|
|
917
|
+
)
|
|
918
|
+
})}
|
|
919
|
+
</TableBody>
|
|
920
|
+
</Table>
|
|
921
|
+
</div>
|
|
922
|
+
)}
|
|
923
|
+
</CardContent>
|
|
924
|
+
</Card>
|
|
925
|
+
</TabsContent>
|
|
926
|
+
|
|
927
|
+
{/* ── TAB 3: PARTS & COSTS ────────────────────────────────────────── */}
|
|
928
|
+
<TabsContent value="parts-costs" className="space-y-6">
|
|
929
|
+
<PartsCostsManager
|
|
930
|
+
taskId={task.id}
|
|
931
|
+
parts={partsData.parts}
|
|
932
|
+
costs={partsData.costs}
|
|
933
|
+
editable={isEditable}
|
|
934
|
+
/>
|
|
935
|
+
</TabsContent>
|
|
936
|
+
|
|
937
|
+
{/* ── TAB 4: SIGNATURES ───────────────────────────────────────────── */}
|
|
938
|
+
<TabsContent value="signatures" className="space-y-6">
|
|
939
|
+
<Card>
|
|
940
|
+
<CardHeader className="pb-3">
|
|
941
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
|
|
942
|
+
<div className="space-y-1">
|
|
943
|
+
<div className="flex items-center gap-2">
|
|
944
|
+
<ShieldCheck className="h-5 w-5 text-primary" />
|
|
945
|
+
<CardTitle className="text-base font-semibold">
|
|
946
|
+
Electronic Signatures & Audit Trail
|
|
947
|
+
</CardTitle>
|
|
948
|
+
</div>
|
|
949
|
+
<CardDescription className="text-xs">
|
|
950
|
+
Cryptographic signature attestation history for work execution, peer review, and release authorization
|
|
951
|
+
</CardDescription>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
{signatures.length > 0 && (
|
|
955
|
+
<Badge variant="outline" className="text-xs bg-emerald-50 text-emerald-800 border-emerald-300">
|
|
956
|
+
<CheckCircle2 className="w-3 h-3 mr-1" />
|
|
957
|
+
{signatures.length} Verified {signatures.length === 1 ? "Signature" : "Signatures"}
|
|
958
|
+
</Badge>
|
|
959
|
+
)}
|
|
960
|
+
</div>
|
|
961
|
+
</CardHeader>
|
|
962
|
+
|
|
963
|
+
<CardContent className="space-y-6 pt-2">
|
|
964
|
+
{/* Record Version Hash Information */}
|
|
965
|
+
{recordVersions.length > 0 && (
|
|
966
|
+
<div className="rounded-lg border bg-muted/30 p-3 space-y-2">
|
|
967
|
+
<div className="flex items-center justify-between">
|
|
968
|
+
<span className="text-xs font-semibold text-foreground flex items-center gap-1.5">
|
|
969
|
+
<Hash className="h-3.5 w-3.5 text-primary" />
|
|
970
|
+
Record Version v{recordVersions[0].versionNumber} Canonical Digest
|
|
971
|
+
</span>
|
|
972
|
+
<span className="text-[11px] text-muted-foreground">
|
|
973
|
+
Created {new Date(recordVersions[0].createdAt).toLocaleString()}
|
|
974
|
+
</span>
|
|
975
|
+
</div>
|
|
976
|
+
<div className="font-mono text-[11px] text-muted-foreground truncate bg-background p-2 rounded border">
|
|
977
|
+
SHA-256: {recordVersions[0].contentHashSha256}
|
|
978
|
+
</div>
|
|
979
|
+
</div>
|
|
980
|
+
)}
|
|
981
|
+
|
|
982
|
+
{/* Signature Timeline Component */}
|
|
983
|
+
<SignatureTimeline
|
|
984
|
+
signatures={signatures}
|
|
985
|
+
emptyMessage={
|
|
986
|
+
task.statusCode === "work_complete"
|
|
987
|
+
? "Work is complete. Click 'Sign & Submit' in the header to execute performer signature."
|
|
988
|
+
: "No electronic signatures recorded yet. Signatures appear upon completion, review, and release."
|
|
989
|
+
}
|
|
990
|
+
/>
|
|
991
|
+
</CardContent>
|
|
992
|
+
</Card>
|
|
993
|
+
</TabsContent>
|
|
994
|
+
</Tabs>
|
|
995
|
+
</div>
|
|
996
|
+
)
|
|
997
|
+
}
|