@omersx/bemms 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +203 -2
- package/drizzle.config.ts +11 -0
- package/next.config.ts +51 -0
- package/package.json +99 -3
- package/postcss.config.mjs +7 -0
- package/public/hospital_demo_devices.csv +32 -0
- package/public/icons/icon-192.png +0 -0
- package/public/icons/icon-512.png +0 -0
- package/public/icons/icon-maskable-512.png +0 -0
- package/public/manifest.webmanifest +31 -0
- package/public/sw.js +177 -0
- package/src/app/(auth)/layout.tsx +20 -0
- package/src/app/(auth)/login/page.tsx +135 -0
- package/src/app/(dashboard)/admin/audit-logs/page.tsx +178 -0
- package/src/app/(dashboard)/admin/data/page.tsx +37 -0
- package/src/app/(dashboard)/admin/departments/[deptId]/edit/page.tsx +206 -0
- package/src/app/(dashboard)/admin/departments/[deptId]/page.tsx +71 -0
- package/src/app/(dashboard)/admin/departments/new/page.tsx +6 -0
- package/src/app/(dashboard)/admin/departments/page.tsx +6 -0
- package/src/app/(dashboard)/admin/general/page.tsx +374 -0
- package/src/app/(dashboard)/admin/hospitals/[hospitalId]/edit/page.tsx +304 -0
- package/src/app/(dashboard)/admin/hospitals/[hospitalId]/page.tsx +94 -0
- package/src/app/(dashboard)/admin/hospitals/new/page.tsx +302 -0
- package/src/app/(dashboard)/admin/hospitals/page.tsx +6 -0
- package/src/app/(dashboard)/admin/layout.tsx +49 -0
- package/src/app/(dashboard)/admin/organizations/[orgId]/edit/page.tsx +172 -0
- package/src/app/(dashboard)/admin/organizations/[orgId]/page.tsx +82 -0
- package/src/app/(dashboard)/admin/organizations/new/page.tsx +150 -0
- package/src/app/(dashboard)/admin/organizations/page.tsx +6 -0
- package/src/app/(dashboard)/admin/page.tsx +258 -0
- package/src/app/(dashboard)/admin/roles/page.tsx +153 -0
- package/src/app/(dashboard)/admin/settings/page.tsx +69 -0
- package/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +153 -0
- package/src/app/(dashboard)/admin/users/[userId]/page.tsx +317 -0
- package/src/app/(dashboard)/admin/users/invite/page.tsx +3 -0
- package/src/app/(dashboard)/admin/users/new/page.tsx +5 -0
- package/src/app/(dashboard)/admin/users/page.tsx +23 -0
- package/src/app/(dashboard)/dashboard/page.tsx +462 -0
- package/src/app/(dashboard)/departments/[deptId]/page.tsx +39 -0
- package/src/app/(dashboard)/departments/new/page.tsx +330 -0
- package/src/app/(dashboard)/departments/page.tsx +52 -0
- package/src/app/(dashboard)/devices/[id]/edit/page.tsx +131 -0
- package/src/app/(dashboard)/devices/[id]/page.tsx +408 -0
- package/src/app/(dashboard)/devices/[id]/qr/page.tsx +54 -0
- package/src/app/(dashboard)/devices/new/page.tsx +382 -0
- package/src/app/(dashboard)/devices/page.tsx +80 -0
- package/src/app/(dashboard)/engineering-team/page.tsx +41 -0
- package/src/app/(dashboard)/layout.tsx +43 -0
- package/src/app/(dashboard)/maintenance/checklists/page.tsx +57 -0
- package/src/app/(dashboard)/maintenance/page.tsx +231 -0
- package/src/app/(dashboard)/maintenance/plans/page.tsx +142 -0
- package/src/app/(dashboard)/maintenance/tasks/[id]/page.tsx +997 -0
- package/src/app/(dashboard)/maintenance/tasks/page.tsx +119 -0
- package/src/app/(dashboard)/more/page.tsx +310 -0
- package/src/app/(dashboard)/my-department/page.tsx +151 -0
- package/src/app/(dashboard)/notifications/notification-list.tsx +103 -0
- package/src/app/(dashboard)/notifications/page.tsx +59 -0
- package/src/app/(dashboard)/profile/page.tsx +27 -0
- package/src/app/(dashboard)/reports/costs/page.tsx +127 -0
- package/src/app/(dashboard)/reports/history/page.tsx +105 -0
- package/src/app/(dashboard)/reports/inventory/page.tsx +116 -0
- package/src/app/(dashboard)/reports/maintenance/page.tsx +136 -0
- package/src/app/(dashboard)/reports/page.tsx +120 -0
- package/src/app/(dashboard)/reports/tickets/page.tsx +139 -0
- package/src/app/(dashboard)/reports/workload/page.tsx +135 -0
- package/src/app/(dashboard)/rounds/[id]/page.tsx +197 -0
- package/src/app/(dashboard)/rounds/history/page.tsx +82 -0
- package/src/app/(dashboard)/rounds/page.tsx +107 -0
- package/src/app/(dashboard)/scan/[code]/page.tsx +154 -0
- package/src/app/(dashboard)/scan/page.tsx +73 -0
- package/src/app/(dashboard)/team/page.tsx +42 -0
- package/src/app/(dashboard)/tickets/[id]/page.tsx +465 -0
- package/src/app/(dashboard)/tickets/create/page.tsx +722 -0
- package/src/app/(dashboard)/tickets/page.tsx +55 -0
- package/src/app/(dashboard)/tickets/triage/page.tsx +161 -0
- package/src/app/api/admin/data/export/route.ts +316 -0
- package/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/src/app/api/health/route.ts +46 -0
- package/src/app/api/notifications/sse/route.ts +91 -0
- package/src/app/api/notifications/subscribe/route.ts +54 -0
- package/src/app/api/notifications/vapid-key/route.ts +7 -0
- package/src/app/api/reports/export/route.ts +102 -0
- package/src/app/api/tickets/[id]/attachments/route.ts +129 -0
- package/src/app/api/tickets/[id]/live-sync/route.ts +52 -0
- package/src/app/error.tsx +54 -0
- package/src/app/globals.css +89 -0
- package/src/app/layout.tsx +59 -0
- package/src/app/not-found.tsx +32 -0
- package/src/app/page.tsx +5 -0
- package/src/components/admin/add-department-user-dialog.tsx +759 -0
- package/src/components/admin/add-engineer-dialog.tsx +773 -0
- package/src/components/admin/add-user-type-dialog.tsx +89 -0
- package/src/components/admin/audit-log-export.tsx +113 -0
- package/src/components/admin/audit-log-filters.tsx +255 -0
- package/src/components/admin/audit-log-table.tsx +383 -0
- package/src/components/admin/data-management/danger-zone.tsx +301 -0
- package/src/components/admin/data-management/data-management-tabs.tsx +61 -0
- package/src/components/admin/data-management/export-center.tsx +301 -0
- package/src/components/admin/data-management/import-wizard.tsx +558 -0
- package/src/components/admin/settings-forms/features-form.tsx +115 -0
- package/src/components/admin/settings-forms/health-panel.tsx +182 -0
- package/src/components/admin/settings-forms/localization-form.tsx +203 -0
- package/src/components/admin/settings-forms/retention-form.tsx +164 -0
- package/src/components/admin/settings-forms/security-form.tsx +186 -0
- package/src/components/admin/system-settings-tabs.tsx +88 -0
- package/src/components/admin/user-role-dialog.tsx +293 -0
- package/src/components/admin/user-status-dialog.tsx +166 -0
- package/src/components/admin/users-page-client.tsx +514 -0
- package/src/components/dashboard/action-buttons.tsx +93 -0
- package/src/components/dashboard/admin-alerts.tsx +86 -0
- package/src/components/dashboard/availability-alerts.tsx +65 -0
- package/src/components/dashboard/maintenance-due-list.tsx +87 -0
- package/src/components/dashboard/round-widget.tsx +110 -0
- package/src/components/dashboard/stat-card.tsx +38 -0
- package/src/components/dashboard/ticket-list-compact.tsx +86 -0
- package/src/components/departments/department-cards-view.tsx +244 -0
- package/src/components/departments/department-detail-view.tsx +467 -0
- package/src/components/departments/department-visuals.tsx +190 -0
- package/src/components/departments/my-department-view.tsx +333 -0
- package/src/components/devices/change-status-dialog.tsx +146 -0
- package/src/components/devices/device-action-buttons.tsx +125 -0
- package/src/components/devices/device-availability-banner.tsx +59 -0
- package/src/components/devices/device-badges.tsx +55 -0
- package/src/components/devices/device-documents-tab.tsx +93 -0
- package/src/components/devices/device-maintenance-tab.tsx +113 -0
- package/src/components/devices/device-mini-qr.tsx +109 -0
- package/src/components/devices/device-qr-card.tsx +187 -0
- package/src/components/devices/device-signatures-tab.tsx +607 -0
- package/src/components/devices/device-tickets-tab.tsx +139 -0
- package/src/components/devices/device-timelines.tsx +124 -0
- package/src/components/devices/qr-camera-scanner.tsx +167 -0
- package/src/components/devices/qr-code-display.tsx +139 -0
- package/src/components/devices/quick-add-category-dialog.tsx +135 -0
- package/src/components/devices/quick-add-manufacturer-dialog.tsx +212 -0
- package/src/components/devices/transfer-device-dialog.tsx +190 -0
- package/src/components/layout/header.tsx +80 -0
- package/src/components/layout/mobile-nav.tsx +91 -0
- package/src/components/layout/more-item-badge.tsx +15 -0
- package/src/components/layout/nav-badge.tsx +72 -0
- package/src/components/layout/sidebar.tsx +297 -0
- package/src/components/maintenance/maintenance-badges.tsx +579 -0
- package/src/components/maintenance/maintenance-nav.tsx +107 -0
- package/src/components/maintenance/parts-costs-manager.tsx +909 -0
- package/src/components/maintenance/signature-card.tsx +413 -0
- package/src/components/maintenance/task-action-bar.tsx +955 -0
- package/src/components/notifications/notification-bell.tsx +367 -0
- package/src/components/notifications/notification-item.tsx +93 -0
- package/src/components/notifications/notification-preferences.tsx +182 -0
- package/src/components/profile/user-profile-view.tsx +626 -0
- package/src/components/providers/nav-counts-provider.tsx +235 -0
- package/src/components/providers/session-provider.tsx +7 -0
- package/src/components/providers/theme-provider.tsx +124 -0
- package/src/components/pwa/install-prompt.tsx +82 -0
- package/src/components/pwa/sw-register.tsx +43 -0
- package/src/components/reports/export-button.tsx +62 -0
- package/src/components/reports/filter-chips.tsx +47 -0
- package/src/components/reports/summary-cards.tsx +40 -0
- package/src/components/reports/transparency-header.tsx +54 -0
- package/src/components/rounds/department-visit-sheet.tsx +199 -0
- package/src/components/rounds/mention-textarea.tsx +187 -0
- package/src/components/rounds/round-calendar.tsx +233 -0
- package/src/components/rounds/round-department-list.tsx +281 -0
- package/src/components/rounds/round-history-client.tsx +218 -0
- package/src/components/rounds/start-round-card.tsx +184 -0
- package/src/components/shared/confirm-dialog.tsx +67 -0
- package/src/components/shared/data-table.tsx +104 -0
- package/src/components/shared/page-header.tsx +20 -0
- package/src/components/shared/sign-out-button.tsx +52 -0
- package/src/components/shared/status-badge.tsx +42 -0
- package/src/components/shared/table-search.tsx +55 -0
- package/src/components/signatures/sign-action-dialog.tsx +253 -0
- package/src/components/signatures/signature-history.tsx +121 -0
- package/src/components/signatures/signature-status-panel.tsx +164 -0
- package/src/components/team/department-coverage.tsx +141 -0
- package/src/components/team/edit-engineer-dialog.tsx +242 -0
- package/src/components/team/engineer-card.tsx +180 -0
- package/src/components/team/engineer-details-dialog.tsx +432 -0
- package/src/components/team/schedule-calendar.tsx +204 -0
- package/src/components/team/staff-engineer-team-view.tsx +368 -0
- package/src/components/team/team-header-stats.tsx +88 -0
- package/src/components/team/team-page-client.tsx +216 -0
- package/src/components/theme/theme-selector-card.tsx +63 -0
- package/src/components/theme/theme-toggle.tsx +65 -0
- package/src/components/tickets/staff-status-label.tsx +58 -0
- package/src/components/tickets/staff-ticket-card.tsx +109 -0
- package/src/components/tickets/staff-ticket-progress-tracker.tsx +405 -0
- package/src/components/tickets/staff-tickets-view.tsx +182 -0
- package/src/components/tickets/ticket-action-buttons.tsx +873 -0
- package/src/components/tickets/ticket-auto-refresh.tsx +197 -0
- package/src/components/tickets/ticket-badges.tsx +43 -0
- package/src/components/tickets/ticket-card.tsx +197 -0
- package/src/components/tickets/ticket-cards-view.tsx +395 -0
- package/src/components/tickets/ticket-comment-section.tsx +684 -0
- package/src/components/tickets/ticket-dispatch-status.tsx +184 -0
- package/src/components/tickets/ticket-list-auto-refresh.tsx +55 -0
- package/src/components/tickets/ticket-reporter-profile.tsx +235 -0
- package/src/components/tickets/ticket-signatures-panel.tsx +32 -0
- package/src/components/tickets/ticket-timeline.tsx +131 -0
- package/src/components/tickets/ticket-triage-panel.tsx +124 -0
- package/src/components/tickets/tickets-nav.tsx +107 -0
- package/src/components/ui/avatar.tsx +43 -0
- package/src/components/ui/badge.tsx +34 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/card.tsx +77 -0
- package/src/components/ui/checkbox.tsx +31 -0
- package/src/components/ui/dialog.tsx +123 -0
- package/src/components/ui/dropdown-menu.tsx +126 -0
- package/src/components/ui/form.tsx +179 -0
- package/src/components/ui/input.tsx +24 -0
- package/src/components/ui/label.tsx +27 -0
- package/src/components/ui/popover.tsx +34 -0
- package/src/components/ui/progress.tsx +36 -0
- package/src/components/ui/scroll-area.tsx +47 -0
- package/src/components/ui/select.tsx +160 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +131 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +121 -0
- package/src/components/ui/tabs.tsx +62 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +27 -0
- package/src/lib/actions/__tests__/devices.test.ts +176 -0
- package/src/lib/actions/__tests__/signatures.test.ts +303 -0
- package/src/lib/actions/__tests__/tickets.test.ts +243 -0
- package/src/lib/actions/audit-logs.ts +298 -0
- package/src/lib/actions/checklists.ts +429 -0
- package/src/lib/actions/dashboard.ts +358 -0
- package/src/lib/actions/data-management.ts +723 -0
- package/src/lib/actions/departments.ts +369 -0
- package/src/lib/actions/device-categories.ts +117 -0
- package/src/lib/actions/devices.ts +748 -0
- package/src/lib/actions/engineer-rounds.ts +973 -0
- package/src/lib/actions/engineers.ts +285 -0
- package/src/lib/actions/hospitals.ts +148 -0
- package/src/lib/actions/locations.ts +115 -0
- package/src/lib/actions/maintenance-plans.ts +316 -0
- package/src/lib/actions/maintenance-tasks.ts +415 -0
- package/src/lib/actions/manufacturers.ts +132 -0
- package/src/lib/actions/nav-counts.ts +144 -0
- package/src/lib/actions/notifications.ts +136 -0
- package/src/lib/actions/organizations.ts +99 -0
- package/src/lib/actions/qr.ts +147 -0
- package/src/lib/actions/report-history.ts +77 -0
- package/src/lib/actions/reports.ts +192 -0
- package/src/lib/actions/roles.ts +32 -0
- package/src/lib/actions/signature-auth.ts +377 -0
- package/src/lib/actions/signatures.ts +532 -0
- package/src/lib/actions/system-settings.ts +274 -0
- package/src/lib/actions/tickets.ts +1445 -0
- package/src/lib/actions/users.ts +740 -0
- package/src/lib/audit/index.ts +103 -0
- package/src/lib/auth/__tests__/rbac.test.ts +33 -0
- package/src/lib/auth/index.ts +338 -0
- package/src/lib/auth/password.ts +10 -0
- package/src/lib/auth/rbac.ts +103 -0
- package/src/lib/auth/types.ts +57 -0
- package/src/lib/constants/departments.ts +16 -0
- package/src/lib/constants/manufacturers.ts +32 -0
- package/src/lib/db/index.ts +23 -0
- package/src/lib/db/schema/audit-logs.ts +42 -0
- package/src/lib/db/schema/departments.ts +39 -0
- package/src/lib/db/schema/device-categories.ts +38 -0
- package/src/lib/db/schema/devices.ts +195 -0
- package/src/lib/db/schema/engineer-profiles.ts +63 -0
- package/src/lib/db/schema/engineer-rounds.ts +115 -0
- package/src/lib/db/schema/enums.ts +148 -0
- package/src/lib/db/schema/hospitals.ts +34 -0
- package/src/lib/db/schema/index.ts +24 -0
- package/src/lib/db/schema/locations.ts +46 -0
- package/src/lib/db/schema/maintenance-plans.ts +99 -0
- package/src/lib/db/schema/maintenance-records.ts +161 -0
- package/src/lib/db/schema/maintenance-tasks.ts +196 -0
- package/src/lib/db/schema/manufacturers.ts +60 -0
- package/src/lib/db/schema/notifications.ts +48 -0
- package/src/lib/db/schema/organizations.ts +24 -0
- package/src/lib/db/schema/report-history.ts +28 -0
- package/src/lib/db/schema/roles.ts +59 -0
- package/src/lib/db/schema/sessions.ts +46 -0
- package/src/lib/db/schema/signature-events.ts +48 -0
- package/src/lib/db/schema/signature-policies.ts +60 -0
- package/src/lib/db/schema/signatures.ts +87 -0
- package/src/lib/db/schema/tickets.ts +169 -0
- package/src/lib/db/schema/user-assignments.ts +85 -0
- package/src/lib/db/schema/users.ts +39 -0
- package/src/lib/db/seed.ts +131 -0
- package/src/lib/logger/__tests__/logger.test.ts +154 -0
- package/src/lib/logger/formatters.ts +159 -0
- package/src/lib/logger/index.ts +184 -0
- package/src/lib/logger/types.ts +48 -0
- package/src/lib/notifications/create.ts +71 -0
- package/src/lib/notifications/dispatcher.ts +490 -0
- package/src/lib/notifications/index.ts +7 -0
- package/src/lib/notifications/push-sender.ts +128 -0
- package/src/lib/notifications/push.ts +64 -0
- package/src/lib/notifications/triggers.ts +166 -0
- package/src/lib/utils/__tests__/crypto.test.ts +30 -0
- package/src/lib/utils/__tests__/export.test.ts +55 -0
- package/src/lib/utils/__tests__/nav-badge.test.ts +147 -0
- package/src/lib/utils/__tests__/staff-status.test.ts +50 -0
- package/src/lib/utils/attestations.ts +19 -0
- package/src/lib/utils/crypto.ts +18 -0
- package/src/lib/utils/csv-parser.ts +181 -0
- package/src/lib/utils/export.ts +45 -0
- package/src/lib/utils/nav-badge.ts +225 -0
- package/src/lib/utils/sound.ts +152 -0
- package/src/lib/utils/staff-status.ts +115 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validators/__tests__/auth.test.ts +127 -0
- package/src/lib/validators/__tests__/data-management.test.ts +182 -0
- package/src/lib/validators/__tests__/devices-profile.test.ts +129 -0
- package/src/lib/validators/__tests__/devices.test.ts +98 -0
- package/src/lib/validators/__tests__/engineer-rounds.test.ts +170 -0
- package/src/lib/validators/__tests__/maintenance.test.ts +178 -0
- package/src/lib/validators/__tests__/signatures.test.ts +154 -0
- package/src/lib/validators/__tests__/system-settings.test.ts +119 -0
- package/src/lib/validators/__tests__/tickets.test.ts +176 -0
- package/src/lib/validators/__tests__/users.test.ts +92 -0
- package/src/lib/validators/auth.ts +32 -0
- package/src/lib/validators/data-management.ts +67 -0
- package/src/lib/validators/departments.ts +14 -0
- package/src/lib/validators/devices.ts +94 -0
- package/src/lib/validators/engineer-rounds.ts +70 -0
- package/src/lib/validators/hospitals.ts +17 -0
- package/src/lib/validators/locations.ts +15 -0
- package/src/lib/validators/maintenance.ts +211 -0
- package/src/lib/validators/organizations.ts +11 -0
- package/src/lib/validators/reports.ts +31 -0
- package/src/lib/validators/signatures.ts +49 -0
- package/src/lib/validators/system-settings.ts +55 -0
- package/src/lib/validators/tickets.ts +125 -0
- package/src/lib/validators/users.ts +43 -0
- package/src/middleware.ts +45 -0
- package/tailwind.config.ts +76 -0
- package/tsconfig.json +28 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { auth } from '@/lib/auth';
|
|
2
|
+
import { redirect } from 'next/navigation';
|
|
3
|
+
import { getTodayRound, isEngineerRoundEnabled } from '@/lib/actions/engineer-rounds';
|
|
4
|
+
import { hasRole } from '@/lib/auth/rbac';
|
|
5
|
+
import { PageHeader } from '@/components/shared/page-header';
|
|
6
|
+
import { Button } from '@/components/ui/button';
|
|
7
|
+
import { Card, CardContent } from '@/components/ui/card';
|
|
8
|
+
import { StartRoundCard } from '@/components/rounds/start-round-card';
|
|
9
|
+
import { RoundDepartmentList } from '@/components/rounds/round-department-list';
|
|
10
|
+
import { History, CalendarDays, Users, AlertCircle, Coffee } from 'lucide-react';
|
|
11
|
+
import Link from 'next/link';
|
|
12
|
+
|
|
13
|
+
export const dynamic = 'force-dynamic';
|
|
14
|
+
|
|
15
|
+
export default async function RoundsPage() {
|
|
16
|
+
const session = await auth();
|
|
17
|
+
if (!session?.user) redirect('/login');
|
|
18
|
+
|
|
19
|
+
const user = session.user as any;
|
|
20
|
+
const enabled = await isEngineerRoundEnabled();
|
|
21
|
+
|
|
22
|
+
if (!enabled) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="max-w-2xl mx-auto space-y-6">
|
|
25
|
+
<PageHeader
|
|
26
|
+
title="Engineer's Round"
|
|
27
|
+
description="Daily department rounds"
|
|
28
|
+
/>
|
|
29
|
+
<Card>
|
|
30
|
+
<CardContent className="pt-6 text-center space-y-3">
|
|
31
|
+
<AlertCircle className="h-10 w-10 mx-auto text-muted-foreground" />
|
|
32
|
+
<h3 className="font-semibold">Feature Not Enabled</h3>
|
|
33
|
+
<p className="text-sm text-muted-foreground">
|
|
34
|
+
The Engineer's Round feature is not enabled for your organization.
|
|
35
|
+
An administrator can enable it in Organization Settings → Features.
|
|
36
|
+
</p>
|
|
37
|
+
</CardContent>
|
|
38
|
+
</Card>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = await getTodayRound();
|
|
44
|
+
|
|
45
|
+
const isManager = hasRole(user.roles || [], 'BIOMED_MGR', 'HOSP_ADMIN', 'ORG_ADMIN', 'SYS_ADMIN');
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="max-w-2xl mx-auto space-y-6">
|
|
49
|
+
<PageHeader
|
|
50
|
+
title="Engineer's Round"
|
|
51
|
+
description="Daily department inspection round"
|
|
52
|
+
>
|
|
53
|
+
<Link href="/rounds/history">
|
|
54
|
+
<Button variant="outline" size="sm" className="gap-1.5">
|
|
55
|
+
<History className="h-4 w-4" />
|
|
56
|
+
History
|
|
57
|
+
</Button>
|
|
58
|
+
</Link>
|
|
59
|
+
</PageHeader>
|
|
60
|
+
|
|
61
|
+
{/* Error state */}
|
|
62
|
+
{!result.success && (
|
|
63
|
+
<Card>
|
|
64
|
+
<CardContent className="pt-6 text-center space-y-3">
|
|
65
|
+
<AlertCircle className="h-10 w-10 mx-auto text-destructive" />
|
|
66
|
+
<h3 className="font-semibold">Unable to load round</h3>
|
|
67
|
+
<p className="text-sm text-muted-foreground">{result.error}</p>
|
|
68
|
+
</CardContent>
|
|
69
|
+
</Card>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{/* Off day */}
|
|
73
|
+
{result.success && result.isOffDay && (
|
|
74
|
+
<Card>
|
|
75
|
+
<CardContent className="pt-6 text-center space-y-3">
|
|
76
|
+
<Coffee className="h-10 w-10 mx-auto text-muted-foreground" />
|
|
77
|
+
<h3 className="font-semibold">Day Off</h3>
|
|
78
|
+
<p className="text-sm text-muted-foreground">
|
|
79
|
+
Today is not a scheduled working day. Enjoy your time off!
|
|
80
|
+
</p>
|
|
81
|
+
<Link href="/rounds/history">
|
|
82
|
+
<Button variant="outline" size="sm" className="mt-2 gap-1.5">
|
|
83
|
+
<History className="h-4 w-4" />
|
|
84
|
+
View Round History
|
|
85
|
+
</Button>
|
|
86
|
+
</Link>
|
|
87
|
+
</CardContent>
|
|
88
|
+
</Card>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{/* Can start a round */}
|
|
92
|
+
{result.success && result.canStart && !result.data && (
|
|
93
|
+
<StartRoundCard
|
|
94
|
+
hospitalId={result.hospitalId!}
|
|
95
|
+
hospitalName={result.hospitalName}
|
|
96
|
+
departments={result.departments || []}
|
|
97
|
+
shiftType={result.shiftType || 'morning'}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{/* Round in progress or completed */}
|
|
102
|
+
{result.success && result.data && (
|
|
103
|
+
<RoundDepartmentList round={result.data as any} />
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { resolveDeviceScan } from "@/lib/actions/qr"
|
|
2
|
+
import { getDeviceActiveTickets } from "@/lib/actions/tickets"
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card"
|
|
4
|
+
import { AlertCircle, ArrowLeft, AlertTriangle, ExternalLink, Ticket } from "lucide-react"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { DeviceStatusBadge } from "@/components/devices/device-badges"
|
|
7
|
+
import { DeviceAvailabilityBanner } from "@/components/devices/device-availability-banner"
|
|
8
|
+
import { TicketStatusBadge } from "@/components/tickets/ticket-badges"
|
|
9
|
+
import Link from "next/link"
|
|
10
|
+
|
|
11
|
+
export default async function ScanResolverPage({ params }: { params: Promise<{ code: string }> }) {
|
|
12
|
+
const { code } = await params
|
|
13
|
+
return <ScanResolverContent code={code} />
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function ScanResolverContent({ code }: { code: string }) {
|
|
17
|
+
const result = await resolveDeviceScan(code)
|
|
18
|
+
|
|
19
|
+
if (result?.success && result.data) {
|
|
20
|
+
const device = result.data
|
|
21
|
+
const ticketsResult = await getDeviceActiveTickets(device.deviceId)
|
|
22
|
+
const activeTickets = (ticketsResult?.success && Array.isArray(ticketsResult.data)) ? ticketsResult.data : []
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="max-w-lg mx-auto space-y-5 pb-8">
|
|
26
|
+
{/* Navigation link */}
|
|
27
|
+
<Link
|
|
28
|
+
href="/scan"
|
|
29
|
+
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors"
|
|
30
|
+
>
|
|
31
|
+
<ArrowLeft className="w-4 h-4 mr-1.5" />
|
|
32
|
+
Back to Scanner
|
|
33
|
+
</Link>
|
|
34
|
+
|
|
35
|
+
{/* 1. Clinical Availability & Safety Banner */}
|
|
36
|
+
<DeviceAvailabilityBanner
|
|
37
|
+
status={device.currentStatus}
|
|
38
|
+
limitationsNote={device.statusLimitationsNote}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
{/* 2. Device Identity Summary Card */}
|
|
42
|
+
<Card className="shadow-xs">
|
|
43
|
+
<CardHeader className="pb-3">
|
|
44
|
+
<div className="flex items-start justify-between gap-3">
|
|
45
|
+
<div>
|
|
46
|
+
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
47
|
+
{device.hospitalName || "Hospital"}
|
|
48
|
+
</p>
|
|
49
|
+
<CardTitle className="text-xl font-bold mt-0.5">{device.deviceName}</CardTitle>
|
|
50
|
+
</div>
|
|
51
|
+
<DeviceStatusBadge status={device.currentStatus} />
|
|
52
|
+
</div>
|
|
53
|
+
</CardHeader>
|
|
54
|
+
<CardContent className="space-y-3 pt-0 text-sm">
|
|
55
|
+
<div className="flex justify-between py-1.5 border-b text-xs">
|
|
56
|
+
<span className="text-muted-foreground">Asset Number</span>
|
|
57
|
+
<span className="font-mono font-semibold">{device.assetNumber}</span>
|
|
58
|
+
</div>
|
|
59
|
+
<div className="flex justify-between py-1.5 border-b text-xs">
|
|
60
|
+
<span className="text-muted-foreground">Internal Code</span>
|
|
61
|
+
<span className="font-mono">{device.internalCode}</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="flex items-start justify-between py-1.5 border-b text-xs">
|
|
64
|
+
<span className="text-muted-foreground">Location</span>
|
|
65
|
+
<span className="text-right font-medium">
|
|
66
|
+
{device.departmentName ? `${device.departmentName} — ` : ""}
|
|
67
|
+
{device.locationName || "General Area"}
|
|
68
|
+
</span>
|
|
69
|
+
</div>
|
|
70
|
+
</CardContent>
|
|
71
|
+
|
|
72
|
+
<CardFooter className="flex flex-col gap-2.5 pt-2">
|
|
73
|
+
<Button
|
|
74
|
+
asChild
|
|
75
|
+
className="w-full bg-amber-600 hover:bg-amber-700 text-white font-semibold py-5"
|
|
76
|
+
>
|
|
77
|
+
<Link href={`/tickets/create?deviceId=${device.deviceId}&source=qr_scan`}>
|
|
78
|
+
<AlertTriangle className="w-4 h-4 mr-2" />
|
|
79
|
+
Report a Problem / Request Maintenance
|
|
80
|
+
</Link>
|
|
81
|
+
</Button>
|
|
82
|
+
|
|
83
|
+
<Button asChild variant="outline" className="w-full py-5">
|
|
84
|
+
<Link href={`/devices/${device.deviceId}`}>
|
|
85
|
+
<ExternalLink className="w-4 h-4 mr-2" />
|
|
86
|
+
View Full Device Profile & History
|
|
87
|
+
</Link>
|
|
88
|
+
</Button>
|
|
89
|
+
</CardFooter>
|
|
90
|
+
</Card>
|
|
91
|
+
|
|
92
|
+
{/* 3. Active Tickets Alert Card */}
|
|
93
|
+
{activeTickets.length > 0 && (
|
|
94
|
+
<Card className="border-amber-200 dark:border-amber-900/40 bg-amber-50/40 dark:bg-amber-950/10">
|
|
95
|
+
<CardHeader className="pb-3">
|
|
96
|
+
<CardTitle className="text-sm font-semibold text-amber-900 dark:text-amber-300 flex items-center gap-2">
|
|
97
|
+
<Ticket className="w-4 h-4 text-amber-600" />
|
|
98
|
+
Active Service Ticket in Progress ({activeTickets.length})
|
|
99
|
+
</CardTitle>
|
|
100
|
+
</CardHeader>
|
|
101
|
+
<CardContent className="space-y-3 pt-0">
|
|
102
|
+
{activeTickets.map((t: any) => (
|
|
103
|
+
<div
|
|
104
|
+
key={t.id}
|
|
105
|
+
className="p-3 bg-white dark:bg-card rounded-md border text-xs space-y-1.5"
|
|
106
|
+
>
|
|
107
|
+
<div className="flex items-center justify-between">
|
|
108
|
+
<span className="font-mono font-medium text-primary">{t.ticketNumber}</span>
|
|
109
|
+
<TicketStatusBadge status={t.statusCode} />
|
|
110
|
+
</div>
|
|
111
|
+
<p className="font-medium text-foreground text-sm">{t.title}</p>
|
|
112
|
+
<p className="text-muted-foreground line-clamp-1">{t.description}</p>
|
|
113
|
+
<div className="pt-1">
|
|
114
|
+
<Link
|
|
115
|
+
href={`/tickets/${t.id}`}
|
|
116
|
+
className="text-primary hover:underline font-medium inline-flex items-center gap-1"
|
|
117
|
+
>
|
|
118
|
+
Track this ticket & add updates →
|
|
119
|
+
</Link>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
))}
|
|
123
|
+
</CardContent>
|
|
124
|
+
</Card>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Error state for unknown or revoked QR code
|
|
131
|
+
return (
|
|
132
|
+
<div className="flex items-center justify-center min-h-[60vh] max-w-md mx-auto">
|
|
133
|
+
<Card className="w-full text-center">
|
|
134
|
+
<CardContent className="pt-8 pb-6 space-y-4">
|
|
135
|
+
<AlertCircle className="w-12 h-12 text-destructive mx-auto" />
|
|
136
|
+
<h2 className="text-lg font-semibold">QR Code Not Recognized</h2>
|
|
137
|
+
<p className="text-muted-foreground text-sm leading-relaxed px-4">
|
|
138
|
+
{result?.code === "INACTIVE"
|
|
139
|
+
? "This QR label has been revoked or replaced with a new label. Please check the current label affixed to the device."
|
|
140
|
+
: "The scanned QR code is not associated with any active device in the BEMMS registry."}
|
|
141
|
+
</p>
|
|
142
|
+
<div className="flex flex-col sm:flex-row gap-2 justify-center pt-2">
|
|
143
|
+
<Button asChild variant="outline" className="w-full sm:w-auto">
|
|
144
|
+
<Link href="/scan">Scan Another</Link>
|
|
145
|
+
</Button>
|
|
146
|
+
<Button asChild className="w-full sm:w-auto">
|
|
147
|
+
<Link href="/devices">Browse Device Inventory</Link>
|
|
148
|
+
</Button>
|
|
149
|
+
</div>
|
|
150
|
+
</CardContent>
|
|
151
|
+
</Card>
|
|
152
|
+
</div>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { PageHeader } from "@/components/shared/page-header"
|
|
4
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
5
|
+
import { Button } from "@/components/ui/button"
|
|
6
|
+
import { Input } from "@/components/ui/input"
|
|
7
|
+
import { Camera, Search, QrCode } from "lucide-react"
|
|
8
|
+
import { useRouter } from "next/navigation"
|
|
9
|
+
import { useState } from "react"
|
|
10
|
+
import { QrCameraScanner } from "@/components/devices/qr-camera-scanner"
|
|
11
|
+
|
|
12
|
+
export default function ScanPage() {
|
|
13
|
+
const router = useRouter()
|
|
14
|
+
const [manualInput, setManualInput] = useState("")
|
|
15
|
+
|
|
16
|
+
const handleManualSearch = (e: React.FormEvent) => {
|
|
17
|
+
e.preventDefault()
|
|
18
|
+
if (manualInput.trim()) {
|
|
19
|
+
// If it looks like a QR reference (starts with dv_), go to scan resolver
|
|
20
|
+
if (manualInput.startsWith("dv_")) {
|
|
21
|
+
router.push(`/scan/${manualInput.trim()}`)
|
|
22
|
+
} else {
|
|
23
|
+
// Otherwise search by asset number in devices list
|
|
24
|
+
router.push(`/devices?search=${encodeURIComponent(manualInput.trim())}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-6 max-w-lg mx-auto">
|
|
31
|
+
<PageHeader
|
|
32
|
+
title="Scan Device QR"
|
|
33
|
+
description="Scan a QR code or enter an asset number to find a device"
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<Card>
|
|
37
|
+
<CardHeader className="pb-3">
|
|
38
|
+
<CardTitle className="flex items-center gap-2 text-base">
|
|
39
|
+
<Camera className="w-5 h-5 text-primary" />
|
|
40
|
+
Live Camera Scanner
|
|
41
|
+
</CardTitle>
|
|
42
|
+
</CardHeader>
|
|
43
|
+
<CardContent>
|
|
44
|
+
<QrCameraScanner />
|
|
45
|
+
</CardContent>
|
|
46
|
+
</Card>
|
|
47
|
+
|
|
48
|
+
<Card>
|
|
49
|
+
<CardHeader>
|
|
50
|
+
<CardTitle className="flex items-center gap-2">
|
|
51
|
+
<Search className="w-5 h-5" />
|
|
52
|
+
Manual Lookup
|
|
53
|
+
</CardTitle>
|
|
54
|
+
</CardHeader>
|
|
55
|
+
<CardContent>
|
|
56
|
+
<form onSubmit={handleManualSearch} className="flex gap-2">
|
|
57
|
+
<Input
|
|
58
|
+
value={manualInput}
|
|
59
|
+
onChange={(e) => setManualInput(e.target.value)}
|
|
60
|
+
placeholder="Enter asset number or QR reference (dv_xxx)..."
|
|
61
|
+
className="flex-1"
|
|
62
|
+
/>
|
|
63
|
+
<Button type="submit">
|
|
64
|
+
<Search className="w-4 h-4 mr-2" />
|
|
65
|
+
Find
|
|
66
|
+
</Button>
|
|
67
|
+
</form>
|
|
68
|
+
</CardContent>
|
|
69
|
+
</Card>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { requireAuth } from '@/lib/auth/rbac';
|
|
2
|
+
import { getEngineersTeam, getDepartmentCoverageMatrix } from '@/lib/actions/engineers';
|
|
3
|
+
import { TeamPageClient } from '@/components/team/team-page-client';
|
|
4
|
+
import { Metadata } from 'next';
|
|
5
|
+
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export const metadata: Metadata = {
|
|
9
|
+
title: 'Engineering Team & Duty Schedule | BEMMS',
|
|
10
|
+
description: 'Hospital biomedical engineering roster, on-call schedules, and department coverage.',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default async function TeamPage() {
|
|
14
|
+
await requireAuth();
|
|
15
|
+
|
|
16
|
+
const [teamRes, coverageRes] = await Promise.all([
|
|
17
|
+
getEngineersTeam(),
|
|
18
|
+
getDepartmentCoverageMatrix(),
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const members = teamRes.data || [];
|
|
22
|
+
const stats = teamRes.stats || {
|
|
23
|
+
totalEngineers: members.length,
|
|
24
|
+
onDutyToday: members.filter((m) => m.dutyStatus === 'on_duty').length,
|
|
25
|
+
onCallToday: members.filter((m) => m.dutyStatus === 'on_call').length,
|
|
26
|
+
inMaintenanceToday: members.filter((m) => m.dutyStatus === 'in_maintenance').length,
|
|
27
|
+
offDutyToday: members.filter((m) => m.dutyStatus === 'off_duty').length,
|
|
28
|
+
totalActiveWorkOrders: 0,
|
|
29
|
+
};
|
|
30
|
+
const coverage = coverageRes.departments || [];
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="space-y-6">
|
|
34
|
+
<TeamPageClient
|
|
35
|
+
initialMembers={members}
|
|
36
|
+
stats={stats}
|
|
37
|
+
coverage={coverage}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|