@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,626 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { toast } from 'sonner';
|
|
6
|
+
import { updateCurrentUserProfile, DepartmentStaffProfile } from '@/lib/actions/users';
|
|
7
|
+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
8
|
+
import { Button } from '@/components/ui/button';
|
|
9
|
+
import { Input } from '@/components/ui/input';
|
|
10
|
+
import { Label } from '@/components/ui/label';
|
|
11
|
+
import { Badge } from '@/components/ui/badge';
|
|
12
|
+
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
|
13
|
+
import {
|
|
14
|
+
User,
|
|
15
|
+
Phone,
|
|
16
|
+
Mail,
|
|
17
|
+
Building2,
|
|
18
|
+
Briefcase,
|
|
19
|
+
IdCard,
|
|
20
|
+
TicketCheck,
|
|
21
|
+
CheckCircle,
|
|
22
|
+
Save,
|
|
23
|
+
Loader2,
|
|
24
|
+
Stethoscope,
|
|
25
|
+
ShieldCheck,
|
|
26
|
+
Wrench,
|
|
27
|
+
Clock,
|
|
28
|
+
Calendar,
|
|
29
|
+
PhoneCall,
|
|
30
|
+
CalendarDays,
|
|
31
|
+
Radio,
|
|
32
|
+
Moon,
|
|
33
|
+
Edit,
|
|
34
|
+
LogOut,
|
|
35
|
+
} from 'lucide-react';
|
|
36
|
+
import Link from 'next/link';
|
|
37
|
+
import { cn } from '@/lib/utils';
|
|
38
|
+
import { updateEngineerDutyStatus } from '@/lib/actions/engineers';
|
|
39
|
+
import { EditEngineerDialog } from '@/components/team/edit-engineer-dialog';
|
|
40
|
+
import { SignOutButton } from '@/components/shared/sign-out-button';
|
|
41
|
+
|
|
42
|
+
interface UserProfileViewProps {
|
|
43
|
+
profile: DepartmentStaffProfile;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const COMMON_SPECIALTIES = [
|
|
47
|
+
'Staff Nurse',
|
|
48
|
+
'Charge Nurse',
|
|
49
|
+
'ICU Specialist',
|
|
50
|
+
'Operating Theatre Nurse',
|
|
51
|
+
'Ward Medical Officer',
|
|
52
|
+
'Clinical Consultant',
|
|
53
|
+
'Department Head / Supervisor',
|
|
54
|
+
'Dialysis Technician',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export function UserProfileView({ profile }: UserProfileViewProps) {
|
|
58
|
+
const router = useRouter();
|
|
59
|
+
const [editing, setEditing] = useState(false);
|
|
60
|
+
const [loading, setLoading] = useState(false);
|
|
61
|
+
|
|
62
|
+
// Form states
|
|
63
|
+
const [fullName, setFullName] = useState(profile.fullName || '');
|
|
64
|
+
const [jobTitle, setJobTitle] = useState(profile.jobTitle || '');
|
|
65
|
+
const [phone, setPhone] = useState(profile.phone || '');
|
|
66
|
+
const [employeeIdentifier, setEmployeeIdentifier] = useState(profile.employeeIdentifier || '');
|
|
67
|
+
|
|
68
|
+
// Engineer state
|
|
69
|
+
const isEngineer = profile.roles.some((r) =>
|
|
70
|
+
['BIOMED_ENG', 'BIOMED_TECH', 'BIOMED_MGR', 'SYS_ADMIN'].includes(r)
|
|
71
|
+
) || Boolean(profile.engineerProfile);
|
|
72
|
+
|
|
73
|
+
const [dutyStatus, setDutyStatus] = useState<string>(
|
|
74
|
+
profile.engineerProfile?.dutyStatus || 'on_duty'
|
|
75
|
+
);
|
|
76
|
+
const [updatingDuty, setUpdatingDuty] = useState(false);
|
|
77
|
+
const [editScheduleOpen, setEditScheduleOpen] = useState(false);
|
|
78
|
+
|
|
79
|
+
const handleDutyStatusChange = async (newStatus: any) => {
|
|
80
|
+
if (!profile.engineerProfile?.id) return;
|
|
81
|
+
setUpdatingDuty(true);
|
|
82
|
+
try {
|
|
83
|
+
const res = await updateEngineerDutyStatus(profile.engineerProfile.id, newStatus);
|
|
84
|
+
if (res.success) {
|
|
85
|
+
setDutyStatus(newStatus);
|
|
86
|
+
const labels: Record<string, string> = {
|
|
87
|
+
on_duty: 'On Duty',
|
|
88
|
+
on_call: 'On Call',
|
|
89
|
+
in_maintenance: 'In Maintenance',
|
|
90
|
+
off_duty: 'Off Duty',
|
|
91
|
+
};
|
|
92
|
+
toast.success(`Duty status updated to ${labels[newStatus] || newStatus}`);
|
|
93
|
+
router.refresh();
|
|
94
|
+
} else {
|
|
95
|
+
toast.error(res.error || 'Failed to update duty status');
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
toast.error('An error occurred');
|
|
99
|
+
} finally {
|
|
100
|
+
setUpdatingDuty(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const initials = profile.fullName
|
|
105
|
+
.split(' ')
|
|
106
|
+
.map((n) => n[0])
|
|
107
|
+
.join('')
|
|
108
|
+
.substring(0, 2)
|
|
109
|
+
.toUpperCase();
|
|
110
|
+
|
|
111
|
+
const handleSave = async (e: React.FormEvent) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
setLoading(true);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
const res = await updateCurrentUserProfile({
|
|
117
|
+
fullName: fullName.trim(),
|
|
118
|
+
jobTitle: jobTitle.trim() || undefined,
|
|
119
|
+
phone: phone.trim() || undefined,
|
|
120
|
+
employeeIdentifier: employeeIdentifier.trim() || undefined,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (res.success) {
|
|
124
|
+
toast.success('Profile updated successfully');
|
|
125
|
+
setEditing(false);
|
|
126
|
+
router.refresh();
|
|
127
|
+
} else {
|
|
128
|
+
toast.error(res.error || 'Failed to update profile');
|
|
129
|
+
}
|
|
130
|
+
} catch (err: any) {
|
|
131
|
+
toast.error(err.message || 'An error occurred');
|
|
132
|
+
} finally {
|
|
133
|
+
setLoading(false);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div className="space-y-6">
|
|
139
|
+
{/* ── 1. Identity & Overview Card ───────────────────────────────── */}
|
|
140
|
+
<Card className="border shadow-sm overflow-hidden">
|
|
141
|
+
<div className="bg-gradient-to-r from-primary/10 via-primary/5 to-transparent p-6 border-b">
|
|
142
|
+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
|
143
|
+
<div className="flex items-center gap-4">
|
|
144
|
+
<Avatar className="h-16 w-16 border-2 border-background shadow-sm">
|
|
145
|
+
<AvatarFallback className="bg-primary text-primary-foreground text-xl font-bold">
|
|
146
|
+
{initials}
|
|
147
|
+
</AvatarFallback>
|
|
148
|
+
</Avatar>
|
|
149
|
+
<div className="space-y-1">
|
|
150
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
151
|
+
<h2 className="text-xl font-bold text-foreground">{profile.fullName}</h2>
|
|
152
|
+
{profile.roles.map((r) => (
|
|
153
|
+
<Badge key={r} variant="secondary" className="text-[10px] font-semibold">
|
|
154
|
+
{r.replace(/_/g, ' ')}
|
|
155
|
+
</Badge>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
<p className="text-sm text-muted-foreground flex items-center gap-1.5 font-medium">
|
|
159
|
+
<Stethoscope className="w-4 h-4 text-primary" />
|
|
160
|
+
{profile.jobTitle || 'Clinical Staff Member'}
|
|
161
|
+
</p>
|
|
162
|
+
<p className="text-xs text-muted-foreground">
|
|
163
|
+
{profile.departmentName ? `${profile.departmentName} · ` : ''}
|
|
164
|
+
{profile.hospitalName || 'Assigned Hospital'}
|
|
165
|
+
</p>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<Button
|
|
170
|
+
variant={editing ? 'outline' : 'default'}
|
|
171
|
+
size="sm"
|
|
172
|
+
onClick={() => setEditing(!editing)}
|
|
173
|
+
className="shrink-0"
|
|
174
|
+
>
|
|
175
|
+
{editing ? 'Cancel' : 'Edit Contact & Title'}
|
|
176
|
+
</Button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Quick Contact & Scope Strip */}
|
|
181
|
+
<CardContent className="p-4 grid sm:grid-cols-3 gap-3 text-xs bg-card">
|
|
182
|
+
<div className="flex items-center gap-2.5 p-2.5 rounded-lg bg-muted/40 border">
|
|
183
|
+
<Phone className="w-4 h-4 text-primary shrink-0" />
|
|
184
|
+
<div className="truncate">
|
|
185
|
+
<span className="block text-[10px] text-muted-foreground font-semibold uppercase">
|
|
186
|
+
Contact Phone / Ext.
|
|
187
|
+
</span>
|
|
188
|
+
<span className="font-medium text-foreground truncate">
|
|
189
|
+
{profile.phone || 'Not set (add phone)'}
|
|
190
|
+
</span>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<div className="flex items-center gap-2.5 p-2.5 rounded-lg bg-muted/40 border">
|
|
195
|
+
<Mail className="w-4 h-4 text-primary shrink-0" />
|
|
196
|
+
<div className="truncate">
|
|
197
|
+
<span className="block text-[10px] text-muted-foreground font-semibold uppercase">
|
|
198
|
+
Official Email
|
|
199
|
+
</span>
|
|
200
|
+
<span className="font-medium text-foreground truncate">{profile.email}</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div className="flex items-center gap-2.5 p-2.5 rounded-lg bg-muted/40 border">
|
|
205
|
+
<IdCard className="w-4 h-4 text-primary shrink-0" />
|
|
206
|
+
<div className="truncate">
|
|
207
|
+
<span className="block text-[10px] text-muted-foreground font-semibold uppercase">
|
|
208
|
+
Staff Badge / Extension
|
|
209
|
+
</span>
|
|
210
|
+
<span className="font-medium text-foreground truncate">
|
|
211
|
+
{profile.employeeIdentifier || 'Not registered'}
|
|
212
|
+
</span>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
</CardContent>
|
|
216
|
+
</Card>
|
|
217
|
+
|
|
218
|
+
{/* ── 2. Edit Profile Form (When Editing) ────────────────────────── */}
|
|
219
|
+
{editing ? (
|
|
220
|
+
<Card className="border-2 border-primary/20 shadow-md">
|
|
221
|
+
<CardHeader className="pb-3">
|
|
222
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
223
|
+
<User className="w-4 h-4 text-primary" />
|
|
224
|
+
Update Clinical Contact Details
|
|
225
|
+
</CardTitle>
|
|
226
|
+
<p className="text-xs text-muted-foreground">
|
|
227
|
+
This information is visible to biomedical engineers when you report equipment problems.
|
|
228
|
+
</p>
|
|
229
|
+
</CardHeader>
|
|
230
|
+
<CardContent className="space-y-4">
|
|
231
|
+
<div className="flex items-start gap-2.5 p-3 rounded-xl bg-blue-50/70 dark:bg-blue-950/20 border border-blue-200 dark:border-blue-900 text-xs text-blue-900 dark:text-blue-200">
|
|
232
|
+
<ShieldCheck className="w-4 h-4 text-blue-600 dark:text-blue-400 shrink-0 mt-0.5" />
|
|
233
|
+
<div>
|
|
234
|
+
<span className="font-semibold">Hospital & Department are pre-assigned:</span>
|
|
235
|
+
<p className="text-[11px] text-blue-800/80 dark:text-blue-300/80 mt-0.5">
|
|
236
|
+
You are registered under <strong>{profile.departmentName || 'Your Department'}</strong> at <strong>{profile.hospitalName || 'Your Hospital'}</strong>. No hospital or department entry is required. You only need to verify your name, clinical speciality, and direct phone so engineers can contact you.
|
|
237
|
+
</p>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<form onSubmit={handleSave} className="space-y-4">
|
|
242
|
+
<div>
|
|
243
|
+
<Label htmlFor="fullName" className="text-xs font-semibold">
|
|
244
|
+
Full Display Name *
|
|
245
|
+
</Label>
|
|
246
|
+
<Input
|
|
247
|
+
id="fullName"
|
|
248
|
+
value={fullName}
|
|
249
|
+
onChange={(e) => setFullName(e.target.value)}
|
|
250
|
+
placeholder="e.g. Dr. Sarah Al-Mansoor"
|
|
251
|
+
required
|
|
252
|
+
className="mt-1 text-sm"
|
|
253
|
+
/>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div>
|
|
257
|
+
<Label htmlFor="jobTitle" className="text-xs font-semibold">
|
|
258
|
+
Clinical Job Title / Speciality *
|
|
259
|
+
</Label>
|
|
260
|
+
<Input
|
|
261
|
+
id="jobTitle"
|
|
262
|
+
value={jobTitle}
|
|
263
|
+
onChange={(e) => setJobTitle(e.target.value)}
|
|
264
|
+
placeholder="e.g. Senior Charge Nurse - Intensive Care"
|
|
265
|
+
className="mt-1 text-sm"
|
|
266
|
+
/>
|
|
267
|
+
<div className="flex flex-wrap gap-1.5 mt-2">
|
|
268
|
+
<span className="text-[10px] text-muted-foreground self-center mr-1">Suggestions:</span>
|
|
269
|
+
{COMMON_SPECIALTIES.map((spec) => (
|
|
270
|
+
<button
|
|
271
|
+
key={spec}
|
|
272
|
+
type="button"
|
|
273
|
+
onClick={() => setJobTitle(spec)}
|
|
274
|
+
className="text-[10px] px-2 py-0.5 rounded-md border bg-muted/40 hover:bg-primary/10 hover:text-primary transition"
|
|
275
|
+
>
|
|
276
|
+
{spec}
|
|
277
|
+
</button>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
<div className="grid sm:grid-cols-2 gap-4">
|
|
283
|
+
<div>
|
|
284
|
+
<Label htmlFor="phone" className="text-xs font-semibold">
|
|
285
|
+
Direct Contact Phone / Mobile
|
|
286
|
+
</Label>
|
|
287
|
+
<Input
|
|
288
|
+
id="phone"
|
|
289
|
+
value={phone}
|
|
290
|
+
onChange={(e) => setPhone(e.target.value)}
|
|
291
|
+
placeholder="e.g. +966 50 123 4567"
|
|
292
|
+
className="mt-1 text-sm"
|
|
293
|
+
/>
|
|
294
|
+
<p className="text-[11px] text-muted-foreground mt-1">
|
|
295
|
+
Used by technicians to call you for urgent device triage.
|
|
296
|
+
</p>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div>
|
|
300
|
+
<Label htmlFor="employeeIdentifier" className="text-xs font-semibold">
|
|
301
|
+
Hospital Extension / Ward Bleep
|
|
302
|
+
</Label>
|
|
303
|
+
<Input
|
|
304
|
+
id="employeeIdentifier"
|
|
305
|
+
value={employeeIdentifier}
|
|
306
|
+
onChange={(e) => setEmployeeIdentifier(e.target.value)}
|
|
307
|
+
placeholder="e.g. Ext. 3120 (Nurse Station Bed 4)"
|
|
308
|
+
className="mt-1 text-sm"
|
|
309
|
+
/>
|
|
310
|
+
<p className="text-[11px] text-muted-foreground mt-1">
|
|
311
|
+
Your ward extension or internal bleep number.
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div className="flex justify-end gap-2 pt-2 border-t">
|
|
317
|
+
<Button type="button" variant="outline" size="sm" onClick={() => setEditing(false)}>
|
|
318
|
+
Cancel
|
|
319
|
+
</Button>
|
|
320
|
+
<Button type="submit" size="sm" disabled={loading} className="gap-1.5">
|
|
321
|
+
{loading ? (
|
|
322
|
+
<>
|
|
323
|
+
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
324
|
+
Saving...
|
|
325
|
+
</>
|
|
326
|
+
) : (
|
|
327
|
+
<>
|
|
328
|
+
<Save className="w-3.5 h-3.5" />
|
|
329
|
+
Save Profile
|
|
330
|
+
</>
|
|
331
|
+
)}
|
|
332
|
+
</Button>
|
|
333
|
+
</div>
|
|
334
|
+
</form>
|
|
335
|
+
</CardContent>
|
|
336
|
+
</Card>
|
|
337
|
+
) : null}
|
|
338
|
+
|
|
339
|
+
{/* ── 2.5 Biomedical Engineer Duty Status & Schedule Card ─────────── */}
|
|
340
|
+
{isEngineer && profile.engineerProfile && (
|
|
341
|
+
<Card className="border shadow-sm border-blue-200 dark:border-blue-900 bg-blue-50/20 dark:bg-blue-950/10">
|
|
342
|
+
<CardHeader className="pb-3 border-b border-blue-100 dark:border-blue-900/40">
|
|
343
|
+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
|
344
|
+
<div className="space-y-1">
|
|
345
|
+
<CardTitle className="text-base flex items-center gap-2 text-foreground">
|
|
346
|
+
<Wrench className="w-4 h-4 text-blue-600 dark:text-blue-400" />
|
|
347
|
+
Biomedical Engineer Duty Status & Schedule
|
|
348
|
+
</CardTitle>
|
|
349
|
+
<p className="text-xs text-muted-foreground">
|
|
350
|
+
Your live duty availability, on-call days, and hospital working hours.
|
|
351
|
+
</p>
|
|
352
|
+
</div>
|
|
353
|
+
<Button
|
|
354
|
+
variant="outline"
|
|
355
|
+
size="sm"
|
|
356
|
+
className="gap-1.5 text-xs bg-background"
|
|
357
|
+
onClick={() => setEditScheduleOpen(true)}
|
|
358
|
+
>
|
|
359
|
+
<Edit className="w-3.5 h-3.5" />
|
|
360
|
+
Edit My Schedule
|
|
361
|
+
</Button>
|
|
362
|
+
</div>
|
|
363
|
+
</CardHeader>
|
|
364
|
+
<CardContent className="pt-4 space-y-4">
|
|
365
|
+
{/* Quick 1-Click Duty Status Switcher */}
|
|
366
|
+
<div className="space-y-2">
|
|
367
|
+
<Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
368
|
+
Current Duty Availability
|
|
369
|
+
</Label>
|
|
370
|
+
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
|
|
371
|
+
{[
|
|
372
|
+
{ id: 'on_duty', label: 'On Duty', desc: 'In Hospital', color: 'border-emerald-500 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300' },
|
|
373
|
+
{ id: 'on_call', label: 'On Call', desc: 'Emergency 24/7', color: 'border-amber-500 bg-amber-500/10 text-amber-700 dark:text-amber-300' },
|
|
374
|
+
{ id: 'in_maintenance', label: 'In Maintenance', desc: 'Active Workshop', color: 'border-blue-500 bg-blue-500/10 text-blue-700 dark:text-blue-300' },
|
|
375
|
+
{ id: 'off_duty', label: 'Off Duty', desc: 'Resting / Off', color: 'border-slate-400 bg-muted text-muted-foreground' },
|
|
376
|
+
].map((item) => {
|
|
377
|
+
const isActive = dutyStatus === item.id;
|
|
378
|
+
return (
|
|
379
|
+
<button
|
|
380
|
+
key={item.id}
|
|
381
|
+
type="button"
|
|
382
|
+
disabled={updatingDuty}
|
|
383
|
+
onClick={() => handleDutyStatusChange(item.id)}
|
|
384
|
+
className={cn(
|
|
385
|
+
'p-2.5 rounded-xl border text-left transition-all relative cursor-pointer',
|
|
386
|
+
isActive
|
|
387
|
+
? `${item.color} ring-2 ring-primary/30 font-semibold shadow-xs`
|
|
388
|
+
: 'border-border/70 hover:bg-muted/40 text-muted-foreground'
|
|
389
|
+
)}
|
|
390
|
+
>
|
|
391
|
+
<div className="flex items-center justify-between">
|
|
392
|
+
<span className="text-xs font-bold">{item.label}</span>
|
|
393
|
+
{isActive && (
|
|
394
|
+
<span className="relative flex h-2 w-2">
|
|
395
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
|
396
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
|
397
|
+
</span>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
400
|
+
<span className="text-[10px] block opacity-80 mt-0.5">{item.desc}</span>
|
|
401
|
+
</button>
|
|
402
|
+
);
|
|
403
|
+
})}
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
{/* Schedule Details Grid */}
|
|
408
|
+
<div className="grid sm:grid-cols-3 gap-3 text-xs pt-2">
|
|
409
|
+
<div className="p-3 rounded-xl border bg-card space-y-1">
|
|
410
|
+
<span className="text-[10px] text-muted-foreground font-semibold uppercase flex items-center gap-1">
|
|
411
|
+
<Clock className="w-3 h-3 text-primary" /> Shift Hours
|
|
412
|
+
</span>
|
|
413
|
+
<p className="font-semibold text-foreground">
|
|
414
|
+
{profile.engineerProfile.shiftHours || '08:00 AM - 04:00 PM'}
|
|
415
|
+
</p>
|
|
416
|
+
</div>
|
|
417
|
+
|
|
418
|
+
<div className="p-3 rounded-xl border bg-card space-y-1">
|
|
419
|
+
<span className="text-[10px] text-muted-foreground font-semibold uppercase flex items-center gap-1">
|
|
420
|
+
<Calendar className="w-3 h-3 text-primary" /> Working Days
|
|
421
|
+
</span>
|
|
422
|
+
<div className="flex flex-wrap gap-1 pt-0.5">
|
|
423
|
+
{(profile.engineerProfile.workingDays || ['Sun', 'Mon', 'Tue', 'Wed', 'Thu']).map((day) => (
|
|
424
|
+
<Badge key={day} variant="secondary" className="text-[10px] px-1.5 py-0 font-medium">
|
|
425
|
+
{day}
|
|
426
|
+
</Badge>
|
|
427
|
+
))}
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
|
|
431
|
+
<div className="p-3 rounded-xl border bg-card space-y-1">
|
|
432
|
+
<span className="text-[10px] text-muted-foreground font-semibold uppercase flex items-center gap-1">
|
|
433
|
+
<PhoneCall className="w-3 h-3 text-amber-500" /> On-Call Days
|
|
434
|
+
</span>
|
|
435
|
+
<div className="flex flex-wrap gap-1 pt-0.5">
|
|
436
|
+
{(profile.engineerProfile.onCallDays && profile.engineerProfile.onCallDays.length > 0) ? (
|
|
437
|
+
profile.engineerProfile.onCallDays.map((day) => (
|
|
438
|
+
<Badge key={day} className="bg-amber-500/15 text-amber-700 dark:text-amber-300 border-amber-300 dark:border-amber-800 text-[10px] px-1.5 py-0 font-medium">
|
|
439
|
+
{day}
|
|
440
|
+
</Badge>
|
|
441
|
+
))
|
|
442
|
+
) : (
|
|
443
|
+
<span className="text-muted-foreground italic text-[11px]">None assigned</span>
|
|
444
|
+
)}
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
{/* Contact & Specialization */}
|
|
450
|
+
<div className="grid sm:grid-cols-2 gap-3 text-xs">
|
|
451
|
+
<div className="p-3 rounded-xl border bg-card space-y-1">
|
|
452
|
+
<span className="text-[10px] text-muted-foreground font-semibold uppercase">
|
|
453
|
+
Technical Specialization
|
|
454
|
+
</span>
|
|
455
|
+
<p className="font-medium text-foreground">
|
|
456
|
+
{profile.engineerProfile.specialization || 'General Biomedical Engineering'}
|
|
457
|
+
</p>
|
|
458
|
+
</div>
|
|
459
|
+
<div className="p-3 rounded-xl border bg-card space-y-1">
|
|
460
|
+
<span className="text-[10px] text-muted-foreground font-semibold uppercase">
|
|
461
|
+
Direct Mobile / Extension
|
|
462
|
+
</span>
|
|
463
|
+
<p className="font-medium text-foreground">
|
|
464
|
+
{profile.engineerProfile.mobileNumber || profile.phone || 'No mobile'} · Ext: {profile.engineerProfile.extension || profile.employeeIdentifier || '—'}
|
|
465
|
+
</p>
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
{/* Quick Link to Team Duty Calendar */}
|
|
470
|
+
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 pt-2 border-t border-border/60 text-xs">
|
|
471
|
+
<span className="text-muted-foreground">
|
|
472
|
+
View whole hospital biomedical engineering team duty roster:
|
|
473
|
+
</span>
|
|
474
|
+
<Button asChild variant="outline" size="sm" className="gap-1.5 text-xs">
|
|
475
|
+
<Link href="/team">
|
|
476
|
+
<CalendarDays className="w-3.5 h-3.5 text-primary" />
|
|
477
|
+
Hospital Duty Roster & Calendar →
|
|
478
|
+
</Link>
|
|
479
|
+
</Button>
|
|
480
|
+
</div>
|
|
481
|
+
</CardContent>
|
|
482
|
+
</Card>
|
|
483
|
+
)}
|
|
484
|
+
|
|
485
|
+
{/* Edit Engineer Dialog for Schedule */}
|
|
486
|
+
{isEngineer && profile.engineerProfile && (
|
|
487
|
+
<EditEngineerDialog
|
|
488
|
+
engineer={{
|
|
489
|
+
id: profile.engineerProfile.id,
|
|
490
|
+
userId: profile.id,
|
|
491
|
+
fullName: profile.fullName,
|
|
492
|
+
email: profile.email,
|
|
493
|
+
avatarUrl: profile.avatarUrl,
|
|
494
|
+
jobTitle: profile.engineerProfile.jobTitle,
|
|
495
|
+
specialization: profile.engineerProfile.specialization,
|
|
496
|
+
extension: profile.engineerProfile.extension,
|
|
497
|
+
mobileNumber: profile.engineerProfile.mobileNumber,
|
|
498
|
+
dutyStatus: profile.engineerProfile.dutyStatus as any,
|
|
499
|
+
shiftHours: profile.engineerProfile.shiftHours,
|
|
500
|
+
workingDays: profile.engineerProfile.workingDays,
|
|
501
|
+
onCallDays: profile.engineerProfile.onCallDays,
|
|
502
|
+
coveredDepartmentIds: profile.engineerProfile.coveredDepartmentIds,
|
|
503
|
+
coveredDepartments: [],
|
|
504
|
+
emergencyPriority: 'first_responder',
|
|
505
|
+
notes: profile.engineerProfile.notes,
|
|
506
|
+
hospitalName: profile.hospitalName,
|
|
507
|
+
activeTicketsCount: profile.activeTicketsCount,
|
|
508
|
+
activeMaintenanceCount: 0,
|
|
509
|
+
assignedDevicesCount: 0,
|
|
510
|
+
}}
|
|
511
|
+
open={editScheduleOpen}
|
|
512
|
+
onOpenChange={setEditScheduleOpen}
|
|
513
|
+
onSuccess={() => router.refresh()}
|
|
514
|
+
/>
|
|
515
|
+
)}
|
|
516
|
+
|
|
517
|
+
{/* ── 3. Department & Hospital Assignment Card ──────────────────── */}
|
|
518
|
+
<Card className="border shadow-sm">
|
|
519
|
+
<CardHeader className="pb-3">
|
|
520
|
+
<div className="flex items-center justify-between">
|
|
521
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
522
|
+
<Building2 className="w-4 h-4 text-primary" />
|
|
523
|
+
Hospital & Department Scope
|
|
524
|
+
</CardTitle>
|
|
525
|
+
<Badge variant="outline" className="text-[10px] text-green-700 dark:text-green-400 border-green-300 dark:border-green-800 bg-green-50/50 dark:bg-green-950/20 font-medium">
|
|
526
|
+
🔒 Pre-assigned & Verified
|
|
527
|
+
</Badge>
|
|
528
|
+
</div>
|
|
529
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
530
|
+
Pre-assigned by Hospital Administration. Clinical staff do not need to configure or enter hospital locations.
|
|
531
|
+
</p>
|
|
532
|
+
</CardHeader>
|
|
533
|
+
<CardContent className="space-y-4">
|
|
534
|
+
<div className="grid sm:grid-cols-2 gap-4 text-sm">
|
|
535
|
+
<div className="p-3.5 rounded-xl border bg-muted/20 space-y-1">
|
|
536
|
+
<span className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">
|
|
537
|
+
Assigned Hospital
|
|
538
|
+
</span>
|
|
539
|
+
<p className="font-bold text-foreground text-base">
|
|
540
|
+
{profile.hospitalName || 'General Hospital'}
|
|
541
|
+
</p>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<div className="p-3.5 rounded-xl border bg-muted/20 space-y-1">
|
|
545
|
+
<span className="text-xs text-muted-foreground font-semibold uppercase tracking-wider">
|
|
546
|
+
Primary Clinical Department
|
|
547
|
+
</span>
|
|
548
|
+
<p className="font-bold text-foreground text-base">
|
|
549
|
+
{profile.departmentName || 'All Assigned Departments'}
|
|
550
|
+
</p>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<div className="flex items-start gap-2.5 p-3 rounded-xl bg-muted/40 border text-xs text-muted-foreground">
|
|
555
|
+
<ShieldCheck className="w-4 h-4 text-primary shrink-0 mt-0.5" />
|
|
556
|
+
<div>
|
|
557
|
+
<span className="font-medium text-foreground">Zero-Config Clinical Routing:</span>
|
|
558
|
+
<p className="text-[11px] mt-0.5">
|
|
559
|
+
Whenever you report a problem or scan an asset, BEMMS automatically connects your report to {profile.departmentName || 'your department'} without requiring any hospital or department codes.
|
|
560
|
+
</p>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div className="flex flex-col sm:flex-row items-center justify-between gap-3 pt-3 border-t text-xs text-muted-foreground">
|
|
565
|
+
<div className="flex items-center gap-2">
|
|
566
|
+
<CheckCircle className="w-4 h-4 text-green-600 shrink-0" />
|
|
567
|
+
<span>
|
|
568
|
+
Authorized to view equipment and report issues for {profile.departmentName || 'your department'}.
|
|
569
|
+
</span>
|
|
570
|
+
</div>
|
|
571
|
+
<Button asChild variant="outline" size="sm" className="text-xs self-start sm:self-auto">
|
|
572
|
+
<Link href="/my-department">View Department Equipment →</Link>
|
|
573
|
+
</Button>
|
|
574
|
+
</div>
|
|
575
|
+
</CardContent>
|
|
576
|
+
</Card>
|
|
577
|
+
|
|
578
|
+
{/* ── 4. My Reported Activity Card ──────────────────────────────── */}
|
|
579
|
+
<Card className="border shadow-sm">
|
|
580
|
+
<CardHeader className="pb-3">
|
|
581
|
+
<CardTitle className="text-base flex items-center gap-2">
|
|
582
|
+
<TicketCheck className="w-4 h-4 text-primary" />
|
|
583
|
+
My Helpdesk Activity
|
|
584
|
+
</CardTitle>
|
|
585
|
+
</CardHeader>
|
|
586
|
+
<CardContent className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
|
587
|
+
<div className="space-y-1">
|
|
588
|
+
<p className="text-sm font-semibold text-foreground">
|
|
589
|
+
{profile.activeTicketsCount > 0
|
|
590
|
+
? `${profile.activeTicketsCount} active ticket(s) currently open`
|
|
591
|
+
: 'No active tickets currently open'}
|
|
592
|
+
</p>
|
|
593
|
+
<p className="text-xs text-muted-foreground">
|
|
594
|
+
Track real-time repair progress, spare parts arrivals, and engineer assignments.
|
|
595
|
+
</p>
|
|
596
|
+
</div>
|
|
597
|
+
|
|
598
|
+
<Button asChild size="sm" className="shrink-0">
|
|
599
|
+
<Link href="/tickets">View My Tickets</Link>
|
|
600
|
+
</Button>
|
|
601
|
+
</CardContent>
|
|
602
|
+
</Card>
|
|
603
|
+
|
|
604
|
+
{/* ── 5. Account Session & Sign Out Card ────────────────────────── */}
|
|
605
|
+
<Card className="border shadow-sm border-destructive/20 bg-destructive/5 dark:bg-destructive/10">
|
|
606
|
+
<CardContent className="p-4 sm:p-5 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
|
|
607
|
+
<div className="space-y-1">
|
|
608
|
+
<h3 className="font-semibold text-sm text-foreground flex items-center gap-2">
|
|
609
|
+
<LogOut className="w-4 h-4 text-destructive" />
|
|
610
|
+
Sign Out of Session
|
|
611
|
+
</h3>
|
|
612
|
+
<p className="text-xs text-muted-foreground">
|
|
613
|
+
End your active session on this device. You will need to enter your credentials again to log back in.
|
|
614
|
+
</p>
|
|
615
|
+
</div>
|
|
616
|
+
<SignOutButton
|
|
617
|
+
variant="destructive"
|
|
618
|
+
size="sm"
|
|
619
|
+
label="Log out"
|
|
620
|
+
className="shrink-0"
|
|
621
|
+
/>
|
|
622
|
+
</CardContent>
|
|
623
|
+
</Card>
|
|
624
|
+
</div>
|
|
625
|
+
);
|
|
626
|
+
}
|