@omersx/bemms 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,465 @@
|
|
|
1
|
+
import { auth } from "@/lib/auth"
|
|
2
|
+
import { hasRole } from "@/lib/auth/rbac"
|
|
3
|
+
import { getTicketById, getTicketComments, getTicketTimeline, getTicketSignatures } from "@/lib/actions/tickets"
|
|
4
|
+
import { getUsers } from "@/lib/actions/users"
|
|
5
|
+
import { db } from "@/lib/db"
|
|
6
|
+
import { engineerProfiles } from "@/lib/db/schema"
|
|
7
|
+
import { eq } from "drizzle-orm"
|
|
8
|
+
import { PageHeader } from "@/components/shared/page-header"
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
10
|
+
import { Button } from "@/components/ui/button"
|
|
11
|
+
import { Badge } from "@/components/ui/badge"
|
|
12
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
13
|
+
import { TicketStatusBadge, TicketPriorityBadge, TicketImpactBadge } from "@/components/tickets/ticket-badges"
|
|
14
|
+
import { StaffStatusLabel } from "@/components/tickets/staff-status-label"
|
|
15
|
+
import { StaffTicketProgressTracker } from "@/components/tickets/staff-ticket-progress-tracker"
|
|
16
|
+
import { DeviceStatusBadge } from "@/components/devices/device-badges"
|
|
17
|
+
import { TicketTimeline } from "@/components/tickets/ticket-timeline"
|
|
18
|
+
import { TicketCommentSection } from "@/components/tickets/ticket-comment-section"
|
|
19
|
+
import { TicketTriagePanel } from "@/components/tickets/ticket-triage-panel"
|
|
20
|
+
import { TicketActionButtons, ResumeWorkButton } from "@/components/tickets/ticket-action-buttons"
|
|
21
|
+
import { TicketSignaturesPanel } from "@/components/tickets/ticket-signatures-panel"
|
|
22
|
+
import { TicketReporterProfile } from "@/components/tickets/ticket-reporter-profile"
|
|
23
|
+
import { WaitingForEngineerBanner, AcceptedByEngineerBanner } from "@/components/tickets/ticket-dispatch-status"
|
|
24
|
+
import { TicketAutoRefresh } from "@/components/tickets/ticket-auto-refresh"
|
|
25
|
+
import { MapPin, Phone, User as UserIcon, Monitor, Clock, AlertTriangle, CheckCircle, ShieldCheck, AlertOctagon, Package, Info, Mail, MessageCircle } from "lucide-react"
|
|
26
|
+
import Link from "next/link"
|
|
27
|
+
import { use } from "react"
|
|
28
|
+
|
|
29
|
+
export const dynamic = 'force-dynamic'
|
|
30
|
+
|
|
31
|
+
export default async function TicketDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
|
32
|
+
const { id } = await params
|
|
33
|
+
return <TicketDetailContent ticketId={id} />
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function TicketDetailContent({ ticketId }: { ticketId: string }) {
|
|
37
|
+
const [ticketRes, commentsRes, timelineRes, usersRes, signaturesRes] = await Promise.all([
|
|
38
|
+
getTicketById(ticketId),
|
|
39
|
+
getTicketComments(ticketId),
|
|
40
|
+
getTicketTimeline(ticketId),
|
|
41
|
+
getUsers(),
|
|
42
|
+
getTicketSignatures(ticketId),
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
const ticket = ticketRes?.success ? ticketRes.data : null
|
|
46
|
+
const comments = (commentsRes?.success && Array.isArray(commentsRes.data)) ? commentsRes.data : []
|
|
47
|
+
const timeline = (timelineRes?.success && Array.isArray(timelineRes.data)) ? timelineRes.data : []
|
|
48
|
+
const engineers = (usersRes?.success && Array.isArray(usersRes.data)) ? usersRes.data : []
|
|
49
|
+
const signatures = (signaturesRes?.success && Array.isArray(signaturesRes.data?.signatures)) ? signaturesRes.data.signatures : []
|
|
50
|
+
|
|
51
|
+
if (!ticket) return <div className="p-8 text-center text-muted-foreground">Ticket not found</div>
|
|
52
|
+
|
|
53
|
+
// Role checking
|
|
54
|
+
const session = await auth()
|
|
55
|
+
const currentUser = session?.user as { roles?: string[] } | undefined
|
|
56
|
+
const userRoles = currentUser?.roles || []
|
|
57
|
+
const isBiomed = hasRole(userRoles, 'BIOMED_ENG', 'BIOMED_TECH', 'BIOMED_MGR', 'SYS_ADMIN', 'ORG_ADMIN', 'HOSP_ADMIN')
|
|
58
|
+
const isStaffOnly = hasRole(userRoles, 'STAFF', 'DEPT_MGR') && !isBiomed
|
|
59
|
+
|
|
60
|
+
let engineerProfile: any = null
|
|
61
|
+
if (ticket.assignedEngineerUserId) {
|
|
62
|
+
engineerProfile = await db.query.engineerProfiles.findFirst({
|
|
63
|
+
where: eq(engineerProfiles.userId, ticket.assignedEngineerUserId),
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const visibleTimeline = isStaffOnly
|
|
68
|
+
? timeline.filter((e: any) => e.visibility !== 'internal')
|
|
69
|
+
: timeline
|
|
70
|
+
|
|
71
|
+
const reportedDate = ticket.reportedAt
|
|
72
|
+
? new Date(ticket.reportedAt).toISOString().replace('T', ' ').substring(0, 16)
|
|
73
|
+
: ''
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-6">
|
|
77
|
+
<TicketAutoRefresh
|
|
78
|
+
ticketId={ticketId}
|
|
79
|
+
initialUpdatedAt={ticket.updatedAt}
|
|
80
|
+
initialStatusCode={ticket.statusCode}
|
|
81
|
+
initialCommentCount={comments.length}
|
|
82
|
+
initialAssignedEngineerUserId={ticket.assignedEngineerUserId}
|
|
83
|
+
/>
|
|
84
|
+
<PageHeader
|
|
85
|
+
title={ticket.title}
|
|
86
|
+
description={`Ticket ${ticket.ticketNumber}${reportedDate ? ` — Reported ${reportedDate} UTC` : ''}`}
|
|
87
|
+
>
|
|
88
|
+
<div className="flex gap-2 items-center flex-wrap">
|
|
89
|
+
<TicketPriorityBadge priority={ticket.priorityCode} />
|
|
90
|
+
{isStaffOnly ? (
|
|
91
|
+
<StaffStatusLabel statusCode={ticket.statusCode} />
|
|
92
|
+
) : (
|
|
93
|
+
<TicketStatusBadge status={ticket.statusCode} />
|
|
94
|
+
)}
|
|
95
|
+
{!isStaffOnly && (
|
|
96
|
+
<TicketActionButtons
|
|
97
|
+
ticketId={ticketId}
|
|
98
|
+
statusCode={ticket.statusCode}
|
|
99
|
+
hasDevice={!!ticket.deviceId}
|
|
100
|
+
ticketNumber={ticket.ticketNumber}
|
|
101
|
+
currentDeviceStatus={ticket.device?.currentStatusCode}
|
|
102
|
+
/>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</PageHeader>
|
|
106
|
+
|
|
107
|
+
{/* Staff Visual Progress Stepper & Guidance */}
|
|
108
|
+
{isStaffOnly && (
|
|
109
|
+
<StaffTicketProgressTracker
|
|
110
|
+
ticket={ticket}
|
|
111
|
+
engineerContact={engineerProfile}
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{/* Dispatch Status: Waiting for Engineer / Accepted */}
|
|
116
|
+
{ticket.statusCode === 'new' && !ticket.assignedEngineerUserId && (
|
|
117
|
+
<WaitingForEngineerBanner
|
|
118
|
+
reportedAt={ticket.reportedAt}
|
|
119
|
+
reportedImpact={ticket.reportedImpact}
|
|
120
|
+
>
|
|
121
|
+
{!isStaffOnly && (
|
|
122
|
+
<TicketActionButtons
|
|
123
|
+
ticketId={ticketId}
|
|
124
|
+
statusCode={ticket.statusCode}
|
|
125
|
+
hasDevice={!!ticket.deviceId}
|
|
126
|
+
ticketNumber={ticket.ticketNumber}
|
|
127
|
+
currentDeviceStatus={ticket.device?.currentStatusCode}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
</WaitingForEngineerBanner>
|
|
131
|
+
)}
|
|
132
|
+
{ticket.acceptedAt && ticket.assignedEngineer && isStaffOnly && (
|
|
133
|
+
<AcceptedByEngineerBanner
|
|
134
|
+
engineerName={(ticket.assignedEngineer as any).fullName || 'Biomedical Engineer'}
|
|
135
|
+
acceptedAt={ticket.acceptedAt}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
|
|
139
|
+
<div className="grid gap-6 md:grid-cols-3">
|
|
140
|
+
{/* Left Column: Details */}
|
|
141
|
+
<div className="md:col-span-2 space-y-6">
|
|
142
|
+
<Card>
|
|
143
|
+
<CardHeader><CardTitle>Problem Description</CardTitle></CardHeader>
|
|
144
|
+
<CardContent className="space-y-4">
|
|
145
|
+
<p className="whitespace-pre-wrap">{ticket.description}</p>
|
|
146
|
+
|
|
147
|
+
<div className="flex flex-wrap gap-4 pt-4 border-t border-border">
|
|
148
|
+
<div className="space-y-1">
|
|
149
|
+
<span className="text-xs text-muted-foreground uppercase">Reported Impact</span>
|
|
150
|
+
<div><TicketImpactBadge impact={ticket.reportedImpact} /></div>
|
|
151
|
+
</div>
|
|
152
|
+
<div className="space-y-1">
|
|
153
|
+
<span className="text-xs text-muted-foreground uppercase">Category</span>
|
|
154
|
+
<div className="text-sm font-medium">{ticket.reportedProblemCategory || "Not specified"}</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</CardContent>
|
|
158
|
+
</Card>
|
|
159
|
+
|
|
160
|
+
{/* Waiting for Spare Parts Card */}
|
|
161
|
+
{['in_progress', 'waiting_parts_vendor', 'in_triage', 'acknowledged'].includes(ticket.statusCode) &&
|
|
162
|
+
(ticket.statusCode === 'waiting_parts_vendor' || ticket.device?.currentStatusCode === 'waiting_for_parts' || ticket.waitingReason) && (
|
|
163
|
+
<Card className="border-amber-300 dark:border-amber-800 bg-amber-50/40 dark:bg-amber-950/20 shadow-sm overflow-hidden">
|
|
164
|
+
<CardHeader className="pb-3 border-b border-amber-200/80 dark:border-amber-800/60 bg-amber-100/40 dark:bg-amber-900/20">
|
|
165
|
+
<div className="flex items-center justify-between flex-wrap gap-2">
|
|
166
|
+
<div className="flex items-center gap-2.5">
|
|
167
|
+
<div className="p-1.5 bg-amber-500/10 dark:bg-amber-400/10 rounded-md border border-amber-300/60 dark:border-amber-700/60">
|
|
168
|
+
<Package className="w-5 h-5 text-amber-600 dark:text-amber-400 shrink-0" />
|
|
169
|
+
</div>
|
|
170
|
+
<div>
|
|
171
|
+
<CardTitle className="text-base text-amber-950 dark:text-amber-100">
|
|
172
|
+
Equipment On Hold — Waiting for Spare Parts
|
|
173
|
+
</CardTitle>
|
|
174
|
+
<p className="text-xs text-amber-800/80 dark:text-amber-300/80">
|
|
175
|
+
Active maintenance paused pending replacement component delivery
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex items-center gap-2">
|
|
180
|
+
<Badge className="bg-amber-500 hover:bg-amber-600 text-white gap-1 border-0 shadow-sm">
|
|
181
|
+
<Clock className="w-3 h-3" />
|
|
182
|
+
Parts On Order
|
|
183
|
+
</Badge>
|
|
184
|
+
<ResumeWorkButton ticketId={ticketId} />
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</CardHeader>
|
|
188
|
+
<CardContent className="pt-4 space-y-4">
|
|
189
|
+
<div className="grid sm:grid-cols-2 gap-3 text-sm">
|
|
190
|
+
<div className="p-3 bg-white/80 dark:bg-zinc-900/70 rounded-md border border-amber-200/70 dark:border-amber-800/50 space-y-1">
|
|
191
|
+
<span className="text-xs font-semibold uppercase text-amber-800 dark:text-amber-300 tracking-wider">
|
|
192
|
+
Required Part / Component
|
|
193
|
+
</span>
|
|
194
|
+
<p className="font-semibold text-zinc-900 dark:text-zinc-100">
|
|
195
|
+
{ticket.waitingReason || "Spare parts ordered"}
|
|
196
|
+
</p>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
{ticket.waitingDependencyType ? (
|
|
200
|
+
<div className="p-3 bg-white/80 dark:bg-zinc-900/70 rounded-md border border-amber-200/70 dark:border-amber-800/50 space-y-1">
|
|
201
|
+
<span className="text-xs font-semibold uppercase text-amber-800 dark:text-amber-300 tracking-wider">
|
|
202
|
+
Supplier / PO Reference
|
|
203
|
+
</span>
|
|
204
|
+
<p className="font-medium text-zinc-900 dark:text-zinc-100">
|
|
205
|
+
{ticket.waitingDependencyType}
|
|
206
|
+
</p>
|
|
207
|
+
</div>
|
|
208
|
+
) : (
|
|
209
|
+
<div className="p-3 bg-white/80 dark:bg-zinc-900/70 rounded-md border border-amber-200/70 dark:border-amber-800/50 space-y-1">
|
|
210
|
+
<span className="text-xs font-semibold uppercase text-amber-800 dark:text-amber-300 tracking-wider">
|
|
211
|
+
Equipment Status
|
|
212
|
+
</span>
|
|
213
|
+
<div>
|
|
214
|
+
<DeviceStatusBadge status={ticket.device?.currentStatusCode || 'waiting_for_parts'} />
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div className="flex items-start gap-2.5 rounded-md bg-amber-100/60 dark:bg-amber-900/30 p-3 text-xs text-amber-900 dark:text-amber-200 border border-amber-200/80 dark:border-amber-800/50">
|
|
221
|
+
<Info className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
|
|
222
|
+
<p className="leading-relaxed">
|
|
223
|
+
The linked medical device is marked <strong>Waiting for Spare Parts</strong> across hospital department dashboards. Clinical staff have been notified that this unit is unavailable. When parts arrive, click <strong>Parts Received (Resume)</strong> to restart work.
|
|
224
|
+
</p>
|
|
225
|
+
</div>
|
|
226
|
+
</CardContent>
|
|
227
|
+
</Card>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{/* Resolution / Disposition Details Card */}
|
|
231
|
+
{ticket.resolutionSummary && ['resolved', 'closed'].includes(ticket.statusCode) && (
|
|
232
|
+
(() => {
|
|
233
|
+
const status = ticket.finalDeviceStatusCode || ''
|
|
234
|
+
const isOutOfService = ['out_of_service', 'decommissioned'].includes(status)
|
|
235
|
+
const isLimited = status === 'operational_with_limitations'
|
|
236
|
+
const isAwaitingRelease = status === 'awaiting_release'
|
|
237
|
+
const isStandby = status === 'standby'
|
|
238
|
+
const isConflict = ['under_repair', 'waiting_for_parts', 'under_maintenance'].includes(status)
|
|
239
|
+
|
|
240
|
+
let cardStyle = "border-emerald-200 dark:border-emerald-950 bg-emerald-50/20 dark:bg-emerald-950/10"
|
|
241
|
+
let headerBorder = "border-emerald-100 dark:border-emerald-900/40"
|
|
242
|
+
let titleColor = "text-emerald-950 dark:text-emerald-200"
|
|
243
|
+
let icon = <CheckCircle className="w-5 h-5 text-emerald-600 shrink-0" />
|
|
244
|
+
let title = "Resolution Summary — Fully Operational"
|
|
245
|
+
|
|
246
|
+
if (isOutOfService) {
|
|
247
|
+
cardStyle = "border-rose-300 dark:border-rose-900 bg-rose-50/30 dark:bg-rose-950/20"
|
|
248
|
+
headerBorder = "border-rose-200 dark:border-rose-900/50"
|
|
249
|
+
titleColor = "text-rose-950 dark:text-rose-200"
|
|
250
|
+
icon = <AlertOctagon className="w-5 h-5 text-rose-600 shrink-0" />
|
|
251
|
+
title = status === 'decommissioned'
|
|
252
|
+
? "Equipment Disposition — Decommissioned (Retired)"
|
|
253
|
+
: "Equipment Disposition — Out of Service (Unrepairable)"
|
|
254
|
+
} else if (isLimited) {
|
|
255
|
+
cardStyle = "border-amber-300 dark:border-amber-900 bg-amber-50/30 dark:bg-amber-950/20"
|
|
256
|
+
headerBorder = "border-amber-200 dark:border-amber-900/50"
|
|
257
|
+
titleColor = "text-amber-950 dark:text-amber-200"
|
|
258
|
+
icon = <AlertTriangle className="w-5 h-5 text-amber-600 shrink-0" />
|
|
259
|
+
title = "Resolution Summary — Operational with Limitations"
|
|
260
|
+
} else if (isAwaitingRelease) {
|
|
261
|
+
cardStyle = "border-purple-300 dark:border-purple-900 bg-purple-50/30 dark:bg-purple-950/20"
|
|
262
|
+
headerBorder = "border-purple-200 dark:border-purple-900/50"
|
|
263
|
+
titleColor = "text-purple-950 dark:text-purple-200"
|
|
264
|
+
icon = <Clock className="w-5 h-5 text-purple-600 shrink-0" />
|
|
265
|
+
title = "Resolution Summary — Awaiting QA / Regulatory Release"
|
|
266
|
+
} else if (isStandby) {
|
|
267
|
+
cardStyle = "border-blue-300 dark:border-blue-900 bg-blue-50/25 dark:bg-blue-950/20"
|
|
268
|
+
headerBorder = "border-blue-200 dark:border-blue-900/50"
|
|
269
|
+
titleColor = "text-blue-950 dark:text-blue-200"
|
|
270
|
+
icon = <ShieldCheck className="w-5 h-5 text-blue-600 shrink-0" />
|
|
271
|
+
title = "Resolution Summary — Restored to Standby Reserve"
|
|
272
|
+
} else if (isConflict) {
|
|
273
|
+
cardStyle = "border-amber-400 dark:border-amber-800 bg-amber-50/50 dark:bg-amber-950/30"
|
|
274
|
+
headerBorder = "border-amber-300 dark:border-amber-800/60"
|
|
275
|
+
titleColor = "text-amber-950 dark:text-amber-200"
|
|
276
|
+
icon = <AlertTriangle className="w-5 h-5 text-amber-600 shrink-0" />
|
|
277
|
+
title = "Resolution Status Conflict — Device Still Under Repair"
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<Card className={cardStyle}>
|
|
282
|
+
<CardHeader className={`pb-3 border-b ${headerBorder}`}>
|
|
283
|
+
<div className="flex items-center justify-between flex-wrap gap-2">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
{icon}
|
|
286
|
+
<CardTitle className={`text-base ${titleColor}`}>
|
|
287
|
+
{title}
|
|
288
|
+
</CardTitle>
|
|
289
|
+
</div>
|
|
290
|
+
{ticket.finalDeviceStatusCode && (
|
|
291
|
+
<div className="flex items-center gap-1.5 text-xs">
|
|
292
|
+
<span className="text-muted-foreground">Final Device Status:</span>
|
|
293
|
+
<DeviceStatusBadge status={ticket.finalDeviceStatusCode} />
|
|
294
|
+
</div>
|
|
295
|
+
)}
|
|
296
|
+
</div>
|
|
297
|
+
</CardHeader>
|
|
298
|
+
<CardContent className="pt-4 space-y-3">
|
|
299
|
+
{isConflict && (
|
|
300
|
+
<div className="rounded bg-amber-100/80 dark:bg-amber-900/40 p-2.5 text-xs text-amber-900 dark:text-amber-200 border border-amber-300 dark:border-amber-800">
|
|
301
|
+
⚠️ <strong>Workflow Notice:</strong> This ticket was marked resolved, but the device is currently <em>Under Repair</em>. If repair work is still in progress, use the <strong>Reopen / Resume Work</strong> button above to resume tracking.
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
{isOutOfService && (
|
|
305
|
+
<div className="rounded bg-rose-100/80 dark:bg-rose-950/60 p-2.5 text-xs text-rose-900 dark:text-rose-200 border border-rose-300 dark:border-rose-800">
|
|
306
|
+
🚫 <strong>Non-Clinical Disposition:</strong> This medical device is flagged out of clinical service and barred from patient use.
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
{isAwaitingRelease && (
|
|
310
|
+
<div className="rounded bg-purple-100/80 dark:bg-purple-950/60 p-2.5 text-xs text-purple-900 dark:text-purple-200 border border-purple-300 dark:border-purple-800">
|
|
311
|
+
⏳ <strong>QA Release Verification Required:</strong> Repair work is completed, but this unit must be inspected and released by Biomedical Quality Assurance before bedside deployment.
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
{isStandby && (
|
|
315
|
+
<div className="rounded bg-blue-100/80 dark:bg-blue-950/60 p-2.5 text-xs text-blue-900 dark:text-blue-200 border border-blue-300 dark:border-blue-800">
|
|
316
|
+
💤 <strong>Equipment Pool / Reserve:</strong> Equipment is verified operational and stored as backup inventory.
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
{isLimited && ticket.device?.statusLimitationsNote && (
|
|
320
|
+
<div className="rounded bg-amber-100/90 dark:bg-amber-950/60 p-2.5 text-xs text-amber-950 dark:text-amber-200 border border-amber-300 dark:border-amber-800 space-y-1">
|
|
321
|
+
<span className="font-semibold block uppercase tracking-wider text-[11px] text-amber-800 dark:text-amber-300">
|
|
322
|
+
Active Clinical Limitations / Restrictions:
|
|
323
|
+
</span>
|
|
324
|
+
<p className="font-medium">{ticket.device.statusLimitationsNote}</p>
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
<p className="text-sm whitespace-pre-wrap leading-relaxed">{ticket.resolutionSummary}</p>
|
|
328
|
+
{ticket.resolvedAt && (
|
|
329
|
+
<p className="text-xs text-muted-foreground pt-2 border-t border-border/50">
|
|
330
|
+
Resolved {new Date(ticket.resolvedAt).toISOString().replace('T', ' ').substring(0, 16)} UTC
|
|
331
|
+
{(ticket as any).resolvedByUser?.fullName ? ` by ${(ticket as any).resolvedByUser.fullName}` : ''}
|
|
332
|
+
</p>
|
|
333
|
+
)}
|
|
334
|
+
</CardContent>
|
|
335
|
+
</Card>
|
|
336
|
+
)
|
|
337
|
+
})()
|
|
338
|
+
)}
|
|
339
|
+
|
|
340
|
+
{/* Electronic Signatures Panel */}
|
|
341
|
+
{!isStaffOnly && <TicketSignaturesPanel signatures={signatures} />}
|
|
342
|
+
|
|
343
|
+
{isBiomed && ['new', 'acknowledged', 'in_triage', 'waiting_requester'].includes(ticket.statusCode) && (
|
|
344
|
+
<TicketTriagePanel
|
|
345
|
+
ticketId={ticketId}
|
|
346
|
+
currentPriority={ticket.priorityCode}
|
|
347
|
+
engineers={engineers}
|
|
348
|
+
/>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
<Card className="overflow-hidden border shadow-sm">
|
|
352
|
+
<CardHeader className="pb-3 border-b">
|
|
353
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
354
|
+
<MessageCircle className="w-4 h-4 text-primary" />
|
|
355
|
+
Chat with Biomedical Engineering
|
|
356
|
+
</CardTitle>
|
|
357
|
+
</CardHeader>
|
|
358
|
+
<CardContent className="p-0">
|
|
359
|
+
<TicketCommentSection
|
|
360
|
+
ticketId={ticketId}
|
|
361
|
+
comments={comments as any}
|
|
362
|
+
canAddInternal={isBiomed}
|
|
363
|
+
currentUserId={session?.user?.id}
|
|
364
|
+
assignedEngineerUserId={ticket.assignedEngineerUserId}
|
|
365
|
+
assignedEngineerName={(ticket as any).assignedEngineer?.fullName}
|
|
366
|
+
/>
|
|
367
|
+
</CardContent>
|
|
368
|
+
</Card>
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
{/* Right Column: Meta & Timeline */}
|
|
372
|
+
<div className="space-y-6">
|
|
373
|
+
<Card>
|
|
374
|
+
<CardHeader><CardTitle>Context</CardTitle></CardHeader>
|
|
375
|
+
<CardContent className="space-y-4 text-sm">
|
|
376
|
+
<div className="space-y-2">
|
|
377
|
+
<h4 className="font-semibold flex items-center gap-2"><Monitor className="w-4 h-4" /> Device</h4>
|
|
378
|
+
{ticket.device ? (
|
|
379
|
+
<div className="pl-6 space-y-1">
|
|
380
|
+
<Link href={`/devices/${ticket.deviceId}`} className="text-primary hover:underline font-medium block">
|
|
381
|
+
{ticket.device.name}
|
|
382
|
+
</Link>
|
|
383
|
+
<p className="text-muted-foreground">Asset: {ticket.device.assetNumber}</p>
|
|
384
|
+
</div>
|
|
385
|
+
) : (
|
|
386
|
+
<p className="pl-6 text-muted-foreground">No device linked.</p>
|
|
387
|
+
)}
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
<div className="space-y-2 pt-2 border-t">
|
|
391
|
+
<h4 className="font-semibold flex items-center gap-2"><MapPin className="w-4 h-4" /> Location</h4>
|
|
392
|
+
<div className="pl-6 space-y-1">
|
|
393
|
+
<p>{(ticket as any).hospital?.name}</p>
|
|
394
|
+
<p>{(ticket as any).department?.name}</p>
|
|
395
|
+
<p className="text-muted-foreground">{ticket.reportedLocationCorrection || (ticket as any).location?.name || "No specific room"}</p>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<div className="space-y-2 pt-2 border-t">
|
|
400
|
+
<h4 className="font-semibold flex items-center gap-2">
|
|
401
|
+
<UserIcon className="w-4 h-4 text-primary" />
|
|
402
|
+
Reported By
|
|
403
|
+
</h4>
|
|
404
|
+
<TicketReporterProfile
|
|
405
|
+
reporter={{
|
|
406
|
+
id: ticket.reportedByUserId || undefined,
|
|
407
|
+
fullName: (ticket as any).reportedByUser?.fullName || ticket.requesterContactName,
|
|
408
|
+
email: (ticket as any).reportedByUser?.email,
|
|
409
|
+
phone: ticket.requesterContactPhone || (ticket as any).reportedByUser?.phone,
|
|
410
|
+
jobTitle: (ticket as any).reportedByUser?.jobTitle,
|
|
411
|
+
employeeIdentifier: (ticket as any).reportedByUser?.employeeIdentifier,
|
|
412
|
+
departmentName: (ticket as any).department?.name,
|
|
413
|
+
hospitalName: (ticket as any).hospital?.name,
|
|
414
|
+
reportedLocationCorrection: ticket.reportedLocationCorrection,
|
|
415
|
+
requesterContactName: ticket.requesterContactName,
|
|
416
|
+
requesterContactPhone: ticket.requesterContactPhone,
|
|
417
|
+
}}
|
|
418
|
+
isBiomedOrAdmin={isBiomed}
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<div className="space-y-2 pt-2 border-t">
|
|
423
|
+
<h4 className="font-semibold flex items-center gap-2"><Clock className="w-4 h-4" /> Assignment</h4>
|
|
424
|
+
<div className="pl-6 space-y-1">
|
|
425
|
+
{ticket.assignedEngineerUserId ? (
|
|
426
|
+
<p>{(ticket as any).assignedEngineer?.fullName}</p>
|
|
427
|
+
) : (
|
|
428
|
+
<p className="text-muted-foreground italic">Unassigned</p>
|
|
429
|
+
)}
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
</CardContent>
|
|
433
|
+
</Card>
|
|
434
|
+
|
|
435
|
+
{/* Assigned Engineer - prominent for staff */}
|
|
436
|
+
{isStaffOnly && ticket.assignedEngineerUserId && (
|
|
437
|
+
<Card className="border-blue-200 dark:border-blue-900 bg-blue-50/50 dark:bg-blue-950/20">
|
|
438
|
+
<CardContent className="p-4">
|
|
439
|
+
<h3 className="text-xs font-semibold text-blue-700 dark:text-blue-300 uppercase tracking-wider mb-2">
|
|
440
|
+
Assigned Engineer
|
|
441
|
+
</h3>
|
|
442
|
+
<div className="flex items-center gap-3">
|
|
443
|
+
<div className="w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center text-blue-700 dark:text-blue-300 font-bold text-sm">
|
|
444
|
+
{(ticket as any).assignedEngineer?.fullName?.substring(0, 2)?.toUpperCase() || '??'}
|
|
445
|
+
</div>
|
|
446
|
+
<div>
|
|
447
|
+
<p className="font-semibold text-sm">{(ticket as any).assignedEngineer?.fullName}</p>
|
|
448
|
+
<p className="text-xs text-muted-foreground">Biomedical Engineer</p>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
</CardContent>
|
|
452
|
+
</Card>
|
|
453
|
+
)}
|
|
454
|
+
|
|
455
|
+
<Card>
|
|
456
|
+
<CardHeader><CardTitle>Timeline</CardTitle></CardHeader>
|
|
457
|
+
<CardContent>
|
|
458
|
+
<TicketTimeline entries={visibleTimeline as any} />
|
|
459
|
+
</CardContent>
|
|
460
|
+
</Card>
|
|
461
|
+
</div>
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
)
|
|
465
|
+
}
|