@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,180 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Card } from '@/components/ui/card';
|
|
4
|
+
import { Badge } from '@/components/ui/badge';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
import {
|
|
7
|
+
Phone,
|
|
8
|
+
PhoneCall,
|
|
9
|
+
Wrench,
|
|
10
|
+
CheckCircle2,
|
|
11
|
+
Moon,
|
|
12
|
+
ChevronRight,
|
|
13
|
+
Building2,
|
|
14
|
+
} from 'lucide-react';
|
|
15
|
+
import { EngineerTeamMember } from '@/lib/actions/engineers';
|
|
16
|
+
import { EngineerDutyStatus } from '@/lib/db/schema/engineer-profiles';
|
|
17
|
+
|
|
18
|
+
interface EngineerCardProps {
|
|
19
|
+
engineer: EngineerTeamMember;
|
|
20
|
+
onClick?: (engineer: EngineerTeamMember) => void;
|
|
21
|
+
onEdit?: (engineer: EngineerTeamMember) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function EngineerCard({ engineer, onClick, onEdit }: EngineerCardProps) {
|
|
25
|
+
const getStatusBadge = (status: EngineerDutyStatus) => {
|
|
26
|
+
switch (status) {
|
|
27
|
+
case 'on_duty':
|
|
28
|
+
return (
|
|
29
|
+
<Badge className="bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/30 gap-1 font-medium whitespace-nowrap text-[11px] h-5 px-1.5 shrink-0">
|
|
30
|
+
<span className="relative flex h-1.5 w-1.5">
|
|
31
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
32
|
+
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-emerald-500"></span>
|
|
33
|
+
</span>
|
|
34
|
+
On Duty
|
|
35
|
+
</Badge>
|
|
36
|
+
);
|
|
37
|
+
case 'on_call':
|
|
38
|
+
return (
|
|
39
|
+
<Badge className="bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/30 gap-1 font-medium whitespace-nowrap text-[11px] h-5 px-1.5 shrink-0">
|
|
40
|
+
<PhoneCall className="w-2.5 h-2.5 text-amber-500 animate-pulse" />
|
|
41
|
+
On Call
|
|
42
|
+
</Badge>
|
|
43
|
+
);
|
|
44
|
+
case 'in_maintenance':
|
|
45
|
+
return (
|
|
46
|
+
<Badge className="bg-blue-500/15 text-blue-700 dark:text-blue-400 border-blue-500/30 gap-1 font-medium whitespace-nowrap text-[11px] h-5 px-1.5 shrink-0">
|
|
47
|
+
<Wrench className="w-2.5 h-2.5 text-blue-500" />
|
|
48
|
+
Maintenance
|
|
49
|
+
</Badge>
|
|
50
|
+
);
|
|
51
|
+
case 'off_duty':
|
|
52
|
+
return (
|
|
53
|
+
<Badge variant="outline" className="text-muted-foreground gap-1 font-medium whitespace-nowrap text-[11px] h-5 px-1.5 shrink-0">
|
|
54
|
+
<Moon className="w-2.5 h-2.5" />
|
|
55
|
+
Off Duty
|
|
56
|
+
</Badge>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const cleanName = (engineer.fullName || '').replace(/^Eng\.\s*/i, '');
|
|
62
|
+
const initials =
|
|
63
|
+
cleanName
|
|
64
|
+
.split(/\s+/)
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.map((n) => n[0])
|
|
67
|
+
.join('')
|
|
68
|
+
.slice(0, 2)
|
|
69
|
+
.toUpperCase() || 'BE';
|
|
70
|
+
|
|
71
|
+
const statusColor =
|
|
72
|
+
engineer.dutyStatus === 'on_duty'
|
|
73
|
+
? 'bg-emerald-500'
|
|
74
|
+
: engineer.dutyStatus === 'on_call'
|
|
75
|
+
? 'bg-amber-500'
|
|
76
|
+
: engineer.dutyStatus === 'in_maintenance'
|
|
77
|
+
? 'bg-blue-500'
|
|
78
|
+
: 'bg-muted-foreground/30';
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Card
|
|
82
|
+
onClick={() => onClick?.(engineer)}
|
|
83
|
+
className="group relative flex flex-col justify-between border-border/70 hover:border-primary/50 hover:shadow-md transition-all cursor-pointer overflow-hidden bg-card active:scale-[0.99] select-none p-3.5 gap-3"
|
|
84
|
+
>
|
|
85
|
+
{/* Top Accent Strip */}
|
|
86
|
+
<div className={`absolute top-0 left-0 right-0 h-1 ${statusColor}`} />
|
|
87
|
+
|
|
88
|
+
{/* Main Header Row */}
|
|
89
|
+
<div className="flex items-start justify-between gap-2.5">
|
|
90
|
+
<div className="flex items-center gap-2.5 min-w-0 flex-1">
|
|
91
|
+
{/* Avatar with status indicator */}
|
|
92
|
+
<div className="relative shrink-0">
|
|
93
|
+
<div className="w-10 h-10 rounded-full bg-primary/10 text-primary font-bold text-xs flex items-center justify-center border border-primary/20">
|
|
94
|
+
{initials}
|
|
95
|
+
</div>
|
|
96
|
+
<span
|
|
97
|
+
className={`absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full ring-2 ring-background ${statusColor}`}
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Name & Title */}
|
|
102
|
+
<div className="min-w-0 flex-1">
|
|
103
|
+
<h3
|
|
104
|
+
className="font-semibold text-sm leading-tight text-foreground truncate group-hover:text-primary transition-colors"
|
|
105
|
+
title={engineer.fullName}
|
|
106
|
+
>
|
|
107
|
+
{engineer.fullName}
|
|
108
|
+
</h3>
|
|
109
|
+
<p className="text-[11px] text-muted-foreground truncate mt-0.5">
|
|
110
|
+
{engineer.jobTitle}
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Status Badge */}
|
|
116
|
+
{getStatusBadge(engineer.dutyStatus)}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
{/* Middle: Specialization or Department chip */}
|
|
120
|
+
<div className="flex items-center gap-1.5 flex-wrap min-w-0 text-[11px]">
|
|
121
|
+
{engineer.specialization ? (
|
|
122
|
+
<span className="inline-flex items-center text-muted-foreground bg-muted/50 px-2 py-0.5 rounded border border-border/40 truncate max-w-full">
|
|
123
|
+
{engineer.specialization}
|
|
124
|
+
</span>
|
|
125
|
+
) : engineer.coveredDepartments.length > 0 ? (
|
|
126
|
+
<span className="inline-flex items-center gap-1 text-muted-foreground bg-muted/50 px-2 py-0.5 rounded border border-border/40 truncate max-w-full">
|
|
127
|
+
<Building2 className="w-3 h-3 text-primary shrink-0" />
|
|
128
|
+
<span className="truncate">{engineer.coveredDepartments[0].name}</span>
|
|
129
|
+
{engineer.coveredDepartments.length > 1 && (
|
|
130
|
+
<span className="text-foreground/70 font-medium">+{engineer.coveredDepartments.length - 1}</span>
|
|
131
|
+
)}
|
|
132
|
+
</span>
|
|
133
|
+
) : (
|
|
134
|
+
<span className="text-muted-foreground italic text-[11px]">Biomedical Team Pool</span>
|
|
135
|
+
)}
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
{/* Bottom Summary & Quick Action */}
|
|
139
|
+
<div className="flex items-center justify-between text-[11px] text-muted-foreground pt-2 border-t border-border/50">
|
|
140
|
+
<div className="flex items-center gap-2.5">
|
|
141
|
+
<span className="flex items-center gap-1" title="Assigned Devices">
|
|
142
|
+
<CheckCircle2 className="w-3 h-3 text-emerald-600" />
|
|
143
|
+
<strong className="text-foreground">{engineer.assignedDevicesCount}</strong> dev
|
|
144
|
+
</span>
|
|
145
|
+
<span className="flex items-center gap-1" title="Active Tasks & Tickets">
|
|
146
|
+
<Wrench className="w-3 h-3 text-blue-600" />
|
|
147
|
+
<strong className="text-foreground">{engineer.activeTicketsCount + engineer.activeMaintenanceCount}</strong> tasks
|
|
148
|
+
</span>
|
|
149
|
+
{engineer.extension && (
|
|
150
|
+
<span className="hidden sm:inline-flex items-center gap-0.5 font-mono text-[10px] bg-muted/60 px-1 py-0.2 rounded text-foreground font-medium">
|
|
151
|
+
Ext:{engineer.extension}
|
|
152
|
+
</span>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
157
|
+
{engineer.mobileNumber && (
|
|
158
|
+
<Button
|
|
159
|
+
asChild
|
|
160
|
+
variant="ghost"
|
|
161
|
+
size="icon"
|
|
162
|
+
className="h-6 w-6 text-muted-foreground hover:text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-950/40"
|
|
163
|
+
onClick={(e) => e.stopPropagation()}
|
|
164
|
+
title={`Call ${engineer.mobileNumber}`}
|
|
165
|
+
>
|
|
166
|
+
<a href={`tel:${engineer.mobileNumber}`}>
|
|
167
|
+
<Phone className="w-3 h-3" />
|
|
168
|
+
</a>
|
|
169
|
+
</Button>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
<span className="text-[11px] text-primary font-medium flex items-center gap-0.5 group-hover:translate-x-0.5 transition-transform">
|
|
173
|
+
Details
|
|
174
|
+
<ChevronRight className="w-3 h-3" />
|
|
175
|
+
</span>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
</Card>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogHeader,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
} from '@/components/ui/dialog';
|
|
12
|
+
import { Badge } from '@/components/ui/badge';
|
|
13
|
+
import { Button } from '@/components/ui/button';
|
|
14
|
+
import {
|
|
15
|
+
DropdownMenu,
|
|
16
|
+
DropdownMenuContent,
|
|
17
|
+
DropdownMenuItem,
|
|
18
|
+
DropdownMenuTrigger,
|
|
19
|
+
} from '@/components/ui/dropdown-menu';
|
|
20
|
+
import {
|
|
21
|
+
Phone,
|
|
22
|
+
Mail,
|
|
23
|
+
PhoneCall,
|
|
24
|
+
Clock,
|
|
25
|
+
Calendar,
|
|
26
|
+
Building2,
|
|
27
|
+
Wrench,
|
|
28
|
+
CheckCircle2,
|
|
29
|
+
Moon,
|
|
30
|
+
ChevronDown,
|
|
31
|
+
Edit,
|
|
32
|
+
ExternalLink,
|
|
33
|
+
Shield,
|
|
34
|
+
Radio,
|
|
35
|
+
FileText,
|
|
36
|
+
} from 'lucide-react';
|
|
37
|
+
import { EngineerTeamMember, updateEngineerDutyStatus } from '@/lib/actions/engineers';
|
|
38
|
+
import { EngineerDutyStatus } from '@/lib/db/schema/engineer-profiles';
|
|
39
|
+
import { toast } from 'sonner';
|
|
40
|
+
import Link from 'next/link';
|
|
41
|
+
|
|
42
|
+
interface EngineerDetailsDialogProps {
|
|
43
|
+
engineer: EngineerTeamMember | null;
|
|
44
|
+
open: boolean;
|
|
45
|
+
onOpenChange: (open: boolean) => void;
|
|
46
|
+
onEdit?: (engineer: EngineerTeamMember) => void;
|
|
47
|
+
onStatusUpdated?: () => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function EngineerDetailsDialog({
|
|
51
|
+
engineer,
|
|
52
|
+
open,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
onEdit,
|
|
55
|
+
onStatusUpdated,
|
|
56
|
+
}: EngineerDetailsDialogProps) {
|
|
57
|
+
const [currentStatus, setCurrentStatus] = useState<EngineerDutyStatus>(engineer?.dutyStatus || 'off_duty');
|
|
58
|
+
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (engineer?.dutyStatus) {
|
|
62
|
+
setCurrentStatus(engineer.dutyStatus);
|
|
63
|
+
}
|
|
64
|
+
}, [engineer?.dutyStatus]);
|
|
65
|
+
|
|
66
|
+
if (!engineer) return null;
|
|
67
|
+
|
|
68
|
+
const handleStatusChange = async (newStatus: EngineerDutyStatus) => {
|
|
69
|
+
if (!engineer) return;
|
|
70
|
+
setIsUpdatingStatus(true);
|
|
71
|
+
try {
|
|
72
|
+
const res = await updateEngineerDutyStatus(engineer.id, newStatus);
|
|
73
|
+
if (res.success) {
|
|
74
|
+
setCurrentStatus(newStatus);
|
|
75
|
+
toast.success(`Duty status updated to ${formatStatus(newStatus)}`);
|
|
76
|
+
onStatusUpdated?.();
|
|
77
|
+
} else {
|
|
78
|
+
toast.error(res.error || 'Failed to update duty status');
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
toast.error('An unexpected error occurred');
|
|
82
|
+
} finally {
|
|
83
|
+
setIsUpdatingStatus(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const formatStatus = (status: EngineerDutyStatus) => {
|
|
88
|
+
switch (status) {
|
|
89
|
+
case 'on_duty':
|
|
90
|
+
return 'On Duty';
|
|
91
|
+
case 'on_call':
|
|
92
|
+
return 'On Call';
|
|
93
|
+
case 'in_maintenance':
|
|
94
|
+
return 'In Maintenance';
|
|
95
|
+
case 'off_duty':
|
|
96
|
+
return 'Off Duty';
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getStatusBadge = (status: EngineerDutyStatus) => {
|
|
101
|
+
switch (status) {
|
|
102
|
+
case 'on_duty':
|
|
103
|
+
return (
|
|
104
|
+
<Badge className="bg-emerald-500/15 text-emerald-700 dark:text-emerald-400 border-emerald-500/30 gap-1.5 font-medium whitespace-nowrap text-xs h-6 px-2.5">
|
|
105
|
+
<span className="relative flex h-2 w-2">
|
|
106
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
107
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
|
108
|
+
</span>
|
|
109
|
+
On Duty (In Hospital)
|
|
110
|
+
</Badge>
|
|
111
|
+
);
|
|
112
|
+
case 'on_call':
|
|
113
|
+
return (
|
|
114
|
+
<Badge className="bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-500/30 gap-1.5 font-medium whitespace-nowrap text-xs h-6 px-2.5">
|
|
115
|
+
<PhoneCall className="w-3.5 h-3.5 text-amber-500 animate-pulse" />
|
|
116
|
+
On Call (Emergency)
|
|
117
|
+
</Badge>
|
|
118
|
+
);
|
|
119
|
+
case 'in_maintenance':
|
|
120
|
+
return (
|
|
121
|
+
<Badge className="bg-blue-500/15 text-blue-700 dark:text-blue-400 border-blue-500/30 gap-1.5 font-medium whitespace-nowrap text-xs h-6 px-2.5">
|
|
122
|
+
<Wrench className="w-3.5 h-3.5 text-blue-500" />
|
|
123
|
+
In Active Maintenance
|
|
124
|
+
</Badge>
|
|
125
|
+
);
|
|
126
|
+
case 'off_duty':
|
|
127
|
+
return (
|
|
128
|
+
<Badge variant="outline" className="text-muted-foreground gap-1.5 font-medium whitespace-nowrap text-xs h-6 px-2.5">
|
|
129
|
+
<Moon className="w-3.5 h-3.5" />
|
|
130
|
+
Off Duty / Rest
|
|
131
|
+
</Badge>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const cleanName = (engineer.fullName || '').replace(/^Eng\.\s*/i, '');
|
|
137
|
+
const initials =
|
|
138
|
+
cleanName
|
|
139
|
+
.split(/\s+/)
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.map((n) => n[0])
|
|
142
|
+
.join('')
|
|
143
|
+
.slice(0, 2)
|
|
144
|
+
.toUpperCase() || 'BE';
|
|
145
|
+
|
|
146
|
+
const ALL_DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
150
|
+
<DialogContent className="max-w-xl max-h-[90vh] overflow-y-auto p-0 gap-0">
|
|
151
|
+
{/* Top Status Accent Bar */}
|
|
152
|
+
<div
|
|
153
|
+
className={`h-2 w-full ${
|
|
154
|
+
currentStatus === 'on_duty'
|
|
155
|
+
? 'bg-emerald-500'
|
|
156
|
+
: currentStatus === 'on_call'
|
|
157
|
+
? 'bg-amber-500'
|
|
158
|
+
: currentStatus === 'in_maintenance'
|
|
159
|
+
? 'bg-blue-500'
|
|
160
|
+
: 'bg-muted-foreground/30'
|
|
161
|
+
}`}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
{/* Dialog Header */}
|
|
165
|
+
<div className="p-6 pb-4 border-b bg-card">
|
|
166
|
+
<div className="flex items-start justify-between gap-4">
|
|
167
|
+
<div className="flex items-start gap-3.5 min-w-0">
|
|
168
|
+
<div className="w-14 h-14 rounded-full bg-primary/10 text-primary font-bold text-lg flex items-center justify-center border-2 border-primary/20 shrink-0 shadow-sm">
|
|
169
|
+
{initials}
|
|
170
|
+
</div>
|
|
171
|
+
<div className="min-w-0">
|
|
172
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
173
|
+
<h2 className="text-lg font-bold text-foreground leading-tight">
|
|
174
|
+
{engineer.fullName}
|
|
175
|
+
</h2>
|
|
176
|
+
</div>
|
|
177
|
+
<p className="text-xs font-medium text-primary mt-0.5">{engineer.jobTitle}</p>
|
|
178
|
+
{engineer.specialization && (
|
|
179
|
+
<Badge variant="secondary" className="text-[11px] font-normal mt-1.5 px-2 py-0.5">
|
|
180
|
+
{engineer.specialization}
|
|
181
|
+
</Badge>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
{/* Duty Status Selector & Edit */}
|
|
187
|
+
<div className="flex flex-col items-end gap-1.5 shrink-0">
|
|
188
|
+
<DropdownMenu>
|
|
189
|
+
<DropdownMenuTrigger asChild>
|
|
190
|
+
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs gap-1 hover:bg-muted/80 cursor-pointer border" disabled={isUpdatingStatus}>
|
|
191
|
+
{getStatusBadge(currentStatus)}
|
|
192
|
+
<ChevronDown className="h-3 w-3 text-muted-foreground ml-1" />
|
|
193
|
+
</Button>
|
|
194
|
+
</DropdownMenuTrigger>
|
|
195
|
+
<DropdownMenuContent align="end" className="w-52 text-xs">
|
|
196
|
+
<DropdownMenuItem onClick={() => handleStatusChange('on_duty')} className="gap-2 cursor-pointer">
|
|
197
|
+
<span className="w-2 h-2 rounded-full bg-emerald-500" />
|
|
198
|
+
On Duty (In Hospital)
|
|
199
|
+
</DropdownMenuItem>
|
|
200
|
+
<DropdownMenuItem onClick={() => handleStatusChange('on_call')} className="gap-2 cursor-pointer">
|
|
201
|
+
<span className="w-2 h-2 rounded-full bg-amber-500" />
|
|
202
|
+
On Call (Emergency)
|
|
203
|
+
</DropdownMenuItem>
|
|
204
|
+
<DropdownMenuItem onClick={() => handleStatusChange('in_maintenance')} className="gap-2 cursor-pointer">
|
|
205
|
+
<span className="w-2 h-2 rounded-full bg-blue-500" />
|
|
206
|
+
In Active Maintenance
|
|
207
|
+
</DropdownMenuItem>
|
|
208
|
+
<DropdownMenuItem onClick={() => handleStatusChange('off_duty')} className="gap-2 cursor-pointer">
|
|
209
|
+
<span className="w-2 h-2 rounded-full bg-slate-400" />
|
|
210
|
+
Off Duty
|
|
211
|
+
</DropdownMenuItem>
|
|
212
|
+
</DropdownMenuContent>
|
|
213
|
+
</DropdownMenu>
|
|
214
|
+
|
|
215
|
+
{onEdit && (
|
|
216
|
+
<Button
|
|
217
|
+
variant="outline"
|
|
218
|
+
size="sm"
|
|
219
|
+
className="h-7 text-xs gap-1.5 text-muted-foreground hover:text-foreground"
|
|
220
|
+
onClick={() => {
|
|
221
|
+
onOpenChange(false);
|
|
222
|
+
onEdit(engineer);
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
225
|
+
<Edit className="h-3.5 w-3.5" />
|
|
226
|
+
Edit Schedule
|
|
227
|
+
</Button>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
{/* Content Body */}
|
|
234
|
+
<div className="p-6 space-y-5 text-xs">
|
|
235
|
+
{/* 1. Contact Information Row */}
|
|
236
|
+
<div>
|
|
237
|
+
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2.5">
|
|
238
|
+
Contact & Direct Channels
|
|
239
|
+
</h4>
|
|
240
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2.5">
|
|
241
|
+
{/* Mobile */}
|
|
242
|
+
<div className="p-3 rounded-lg bg-muted/30 border border-border/50 flex flex-col justify-between">
|
|
243
|
+
<span className="text-[11px] text-muted-foreground flex items-center gap-1.5 mb-1">
|
|
244
|
+
<Phone className="w-3.5 h-3.5 text-primary" />
|
|
245
|
+
Direct Mobile
|
|
246
|
+
</span>
|
|
247
|
+
{engineer.mobileNumber ? (
|
|
248
|
+
<div className="flex items-center justify-between gap-1 mt-1">
|
|
249
|
+
<span className="font-semibold text-foreground text-xs">{engineer.mobileNumber}</span>
|
|
250
|
+
<Button asChild size="sm" variant="ghost" className="h-6 px-1.5 text-xs text-primary">
|
|
251
|
+
<a href={`tel:${engineer.mobileNumber}`}>Call</a>
|
|
252
|
+
</Button>
|
|
253
|
+
</div>
|
|
254
|
+
) : (
|
|
255
|
+
<span className="text-muted-foreground italic text-xs">Not provided</span>
|
|
256
|
+
)}
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
{/* Extension */}
|
|
260
|
+
<div className="p-3 rounded-lg bg-muted/30 border border-border/50 flex flex-col justify-between">
|
|
261
|
+
<span className="text-[11px] text-muted-foreground flex items-center gap-1.5 mb-1">
|
|
262
|
+
<PhoneCall className="w-3.5 h-3.5 text-primary" />
|
|
263
|
+
Hospital Extension
|
|
264
|
+
</span>
|
|
265
|
+
<div className="mt-1">
|
|
266
|
+
{engineer.extension ? (
|
|
267
|
+
<span className="font-bold text-foreground text-sm font-mono">{engineer.extension}</span>
|
|
268
|
+
) : (
|
|
269
|
+
<span className="text-muted-foreground italic text-xs">No extension</span>
|
|
270
|
+
)}
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
|
|
274
|
+
{/* Email */}
|
|
275
|
+
<div className="p-3 rounded-lg bg-muted/30 border border-border/50 flex flex-col justify-between">
|
|
276
|
+
<span className="text-[11px] text-muted-foreground flex items-center gap-1.5 mb-1">
|
|
277
|
+
<Mail className="w-3.5 h-3.5 text-primary" />
|
|
278
|
+
Email Address
|
|
279
|
+
</span>
|
|
280
|
+
<div className="flex items-center justify-between gap-1 mt-1">
|
|
281
|
+
<span className="font-medium text-foreground text-xs truncate" title={engineer.email}>
|
|
282
|
+
{engineer.email}
|
|
283
|
+
</span>
|
|
284
|
+
<Button asChild size="sm" variant="ghost" className="h-6 px-1.5 text-xs text-primary">
|
|
285
|
+
<a href={`mailto:${engineer.email}`}>Email</a>
|
|
286
|
+
</Button>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* 2. Workload & Responsibilities Stats */}
|
|
293
|
+
<div>
|
|
294
|
+
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2.5">
|
|
295
|
+
Assigned Workload & Responsibility
|
|
296
|
+
</h4>
|
|
297
|
+
<div className="grid grid-cols-3 gap-2.5 text-center">
|
|
298
|
+
<div className="p-3 rounded-lg bg-emerald-500/10 border border-emerald-500/20">
|
|
299
|
+
<p className="text-xl font-bold text-emerald-700 dark:text-emerald-400">
|
|
300
|
+
{engineer.assignedDevicesCount}
|
|
301
|
+
</p>
|
|
302
|
+
<p className="text-[11px] text-muted-foreground mt-0.5 flex items-center justify-center gap-1">
|
|
303
|
+
<CheckCircle2 className="w-3 h-3 text-emerald-600" />
|
|
304
|
+
Assigned Devices
|
|
305
|
+
</p>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div className="p-3 rounded-lg bg-blue-500/10 border border-blue-500/20">
|
|
309
|
+
<p className="text-xl font-bold text-blue-700 dark:text-blue-400">
|
|
310
|
+
{engineer.activeTicketsCount}
|
|
311
|
+
</p>
|
|
312
|
+
<p className="text-[11px] text-muted-foreground mt-0.5 flex items-center justify-center gap-1">
|
|
313
|
+
<Wrench className="w-3 h-3 text-blue-600" />
|
|
314
|
+
Active Tickets
|
|
315
|
+
</p>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div className="p-3 rounded-lg bg-amber-500/10 border border-amber-500/20">
|
|
319
|
+
<p className="text-xl font-bold text-amber-700 dark:text-amber-400">
|
|
320
|
+
{engineer.activeMaintenanceCount}
|
|
321
|
+
</p>
|
|
322
|
+
<p className="text-[11px] text-muted-foreground mt-0.5 flex items-center justify-center gap-1">
|
|
323
|
+
<Clock className="w-3 h-3 text-amber-600" />
|
|
324
|
+
PM Tasks
|
|
325
|
+
</p>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{/* 3. Shift Schedule & Working Days */}
|
|
331
|
+
<div>
|
|
332
|
+
<div className="flex items-center justify-between mb-2">
|
|
333
|
+
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
334
|
+
Working Days & Shift Schedule
|
|
335
|
+
</h4>
|
|
336
|
+
<Badge variant="outline" className="text-[11px] gap-1 font-mono">
|
|
337
|
+
<Clock className="w-3 h-3 text-primary" />
|
|
338
|
+
{engineer.shiftHours || '08:00 AM - 04:00 PM'}
|
|
339
|
+
</Badge>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div className="grid grid-cols-7 gap-1.5 p-3 rounded-lg bg-muted/30 border border-border/50">
|
|
343
|
+
{ALL_DAYS.map((day) => {
|
|
344
|
+
const isWorking = engineer.workingDays.includes(day);
|
|
345
|
+
const isOnCall = engineer.onCallDays.includes(day);
|
|
346
|
+
return (
|
|
347
|
+
<div
|
|
348
|
+
key={day}
|
|
349
|
+
className={`py-2 px-1 text-center rounded-md border text-xs flex flex-col items-center justify-between min-h-[52px] ${
|
|
350
|
+
isWorking
|
|
351
|
+
? 'bg-primary/10 border-primary/30 text-primary font-bold shadow-xs'
|
|
352
|
+
: isOnCall
|
|
353
|
+
? 'bg-amber-500/15 border-amber-500/30 text-amber-700 dark:text-amber-400 font-bold'
|
|
354
|
+
: 'bg-background/60 border-border/40 text-muted-foreground/50'
|
|
355
|
+
}`}
|
|
356
|
+
>
|
|
357
|
+
<span className="text-[11px]">{day}</span>
|
|
358
|
+
<span className="text-[10px] mt-0.5 font-normal">
|
|
359
|
+
{isWorking ? '🟢 Shift' : isOnCall ? '📞 Call' : 'Off'}
|
|
360
|
+
</span>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
})}
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
{/* 4. Department Coverage */}
|
|
368
|
+
<div>
|
|
369
|
+
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 flex items-center justify-between">
|
|
370
|
+
<span>Department Coverage ({engineer.coveredDepartments.length})</span>
|
|
371
|
+
{engineer.hospitalName && (
|
|
372
|
+
<span className="text-muted-foreground font-normal normal-case">{engineer.hospitalName}</span>
|
|
373
|
+
)}
|
|
374
|
+
</h4>
|
|
375
|
+
<div className="flex flex-wrap gap-1.5 p-3 rounded-lg bg-muted/30 border border-border/50">
|
|
376
|
+
{engineer.coveredDepartments.length > 0 ? (
|
|
377
|
+
engineer.coveredDepartments.map((dept) => {
|
|
378
|
+
const label = dept.name.includes(dept.code) ? dept.name : `${dept.name} (${dept.code})`;
|
|
379
|
+
return (
|
|
380
|
+
<Badge
|
|
381
|
+
key={dept.id}
|
|
382
|
+
variant="secondary"
|
|
383
|
+
className="text-xs px-2.5 py-1 font-normal flex items-center gap-1.5"
|
|
384
|
+
>
|
|
385
|
+
<Building2 className="w-3 h-3 text-primary" />
|
|
386
|
+
{label}
|
|
387
|
+
</Badge>
|
|
388
|
+
);
|
|
389
|
+
})
|
|
390
|
+
) : (
|
|
391
|
+
<span className="text-muted-foreground italic text-xs">
|
|
392
|
+
Assigned to hospital general biomedical pool
|
|
393
|
+
</span>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{/* 5. Notes & Qualifications */}
|
|
399
|
+
{engineer.notes && (
|
|
400
|
+
<div>
|
|
401
|
+
<h4 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1.5 flex items-center gap-1.5">
|
|
402
|
+
<FileText className="w-3.5 h-3.5 text-primary" />
|
|
403
|
+
Notes & Clinical Qualifications
|
|
404
|
+
</h4>
|
|
405
|
+
<p className="text-xs text-foreground bg-muted/30 p-3 rounded-lg border border-border/50 leading-relaxed whitespace-pre-wrap">
|
|
406
|
+
{engineer.notes}
|
|
407
|
+
</p>
|
|
408
|
+
</div>
|
|
409
|
+
)}
|
|
410
|
+
</div>
|
|
411
|
+
|
|
412
|
+
{/* Footer Actions */}
|
|
413
|
+
<DialogFooter className="p-4 border-t bg-muted/20 flex flex-row justify-between items-center sm:justify-between">
|
|
414
|
+
<Button variant="outline" size="sm" onClick={() => onOpenChange(false)}>
|
|
415
|
+
Close
|
|
416
|
+
</Button>
|
|
417
|
+
|
|
418
|
+
<div className="flex items-center gap-2">
|
|
419
|
+
{engineer.mobileNumber && (
|
|
420
|
+
<Button asChild size="sm" className="gap-1.5">
|
|
421
|
+
<a href={`tel:${engineer.mobileNumber}`}>
|
|
422
|
+
<Phone className="w-3.5 h-3.5" />
|
|
423
|
+
Call Engineer
|
|
424
|
+
</a>
|
|
425
|
+
</Button>
|
|
426
|
+
)}
|
|
427
|
+
</div>
|
|
428
|
+
</DialogFooter>
|
|
429
|
+
</DialogContent>
|
|
430
|
+
</Dialog>
|
|
431
|
+
);
|
|
432
|
+
}
|