@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,374 @@
|
|
|
1
|
+
import { auth } from "@/lib/auth";
|
|
2
|
+
import { redirect } from "next/navigation";
|
|
3
|
+
import { getOrganizations } from "@/lib/actions/organizations";
|
|
4
|
+
import { getHospitals } from "@/lib/actions/hospitals";
|
|
5
|
+
import { getDeviceCategories } from "@/lib/actions/device-categories";
|
|
6
|
+
import { getManufacturers } from "@/lib/actions/manufacturers";
|
|
7
|
+
import { QuickAddCategoryDialog } from "@/components/devices/quick-add-category-dialog";
|
|
8
|
+
import { QuickAddManufacturerDialog } from "@/components/devices/quick-add-manufacturer-dialog";
|
|
9
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
10
|
+
import { PageHeader } from "@/components/shared/page-header";
|
|
11
|
+
import { DataTable } from "@/components/shared/data-table";
|
|
12
|
+
import { StatusBadge } from "@/components/shared/status-badge";
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
15
|
+
import { Badge } from "@/components/ui/badge";
|
|
16
|
+
import Link from "next/link";
|
|
17
|
+
import { Building2, Hospital, Plus, Eye, Edit, Globe, Calendar, ShieldCheck, Cpu, Factory } from "lucide-react";
|
|
18
|
+
import { hasRole } from "@/lib/auth/rbac";
|
|
19
|
+
|
|
20
|
+
export const dynamic = "force-dynamic";
|
|
21
|
+
|
|
22
|
+
export default async function AdminGeneralPage() {
|
|
23
|
+
const session = await auth();
|
|
24
|
+
if (!session?.user) redirect("/login");
|
|
25
|
+
|
|
26
|
+
const user = session.user as { roles?: string[] };
|
|
27
|
+
const isSysAdmin = hasRole(user.roles || [], "SYS_ADMIN");
|
|
28
|
+
|
|
29
|
+
const [orgsResult, hospitalsResult, categoriesResult, manufacturersResult] = await Promise.all([
|
|
30
|
+
getOrganizations(),
|
|
31
|
+
getHospitals(),
|
|
32
|
+
getDeviceCategories(),
|
|
33
|
+
getManufacturers(),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const organizations = orgsResult.success && orgsResult.data ? orgsResult.data : [];
|
|
37
|
+
const hospitals = hospitalsResult.success && hospitalsResult.data ? hospitalsResult.data : [];
|
|
38
|
+
const categories = categoriesResult.success && categoriesResult.data ? categoriesResult.data : [];
|
|
39
|
+
const manufacturers = manufacturersResult.success && manufacturersResult.data ? manufacturersResult.data : [];
|
|
40
|
+
|
|
41
|
+
const currentOrg = organizations[0]; // Primary organization
|
|
42
|
+
|
|
43
|
+
const hospitalColumns = [
|
|
44
|
+
{
|
|
45
|
+
key: "code",
|
|
46
|
+
header: "Code",
|
|
47
|
+
render: (item: any) => (
|
|
48
|
+
<span className="font-mono text-xs font-semibold text-primary">
|
|
49
|
+
{item.code}
|
|
50
|
+
</span>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "name",
|
|
55
|
+
header: "Hospital Name",
|
|
56
|
+
render: (item: any) => (
|
|
57
|
+
<Link
|
|
58
|
+
href={`/admin/hospitals/${item.id}`}
|
|
59
|
+
className="font-medium hover:text-primary transition-colors"
|
|
60
|
+
>
|
|
61
|
+
{item.name}
|
|
62
|
+
</Link>
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "city",
|
|
67
|
+
header: "City / Campus",
|
|
68
|
+
render: (item: any) => item.city || item.address || "—",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "status",
|
|
72
|
+
header: "Status",
|
|
73
|
+
render: (item: any) => <StatusBadge status={item.status} />,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "actions",
|
|
77
|
+
header: "Actions",
|
|
78
|
+
render: (item: any) => (
|
|
79
|
+
<div className="flex items-center gap-1">
|
|
80
|
+
<Button variant="ghost" size="sm" asChild>
|
|
81
|
+
<Link href={`/admin/hospitals/${item.id}`}>
|
|
82
|
+
<Eye className="w-4 h-4 mr-1" />
|
|
83
|
+
View
|
|
84
|
+
</Link>
|
|
85
|
+
</Button>
|
|
86
|
+
<Button variant="ghost" size="sm" asChild>
|
|
87
|
+
<Link href={`/admin/hospitals/${item.id}/edit`}>
|
|
88
|
+
<Edit className="w-4 h-4" />
|
|
89
|
+
</Link>
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const categoryColumns = [
|
|
97
|
+
{
|
|
98
|
+
key: "code",
|
|
99
|
+
header: "Code",
|
|
100
|
+
render: (item: any) => (
|
|
101
|
+
<span className="font-mono text-xs font-semibold text-primary">
|
|
102
|
+
{item.code}
|
|
103
|
+
</span>
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
key: "name",
|
|
108
|
+
header: "Category Name",
|
|
109
|
+
render: (item: any) => (
|
|
110
|
+
<span className="font-medium text-foreground">{item.name}</span>
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "riskClassification",
|
|
115
|
+
header: "Risk Class",
|
|
116
|
+
render: (item: any) => item.riskClassification ? (
|
|
117
|
+
<Badge variant="outline" className="text-xs uppercase font-mono">
|
|
118
|
+
{item.riskClassification.replace('class_', 'Class ')}
|
|
119
|
+
</Badge>
|
|
120
|
+
) : "—",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
key: "criticalityLevel",
|
|
124
|
+
header: "Criticality",
|
|
125
|
+
render: (item: any) => item.criticalityLevel ? (
|
|
126
|
+
<Badge
|
|
127
|
+
variant={item.criticalityLevel === 'critical' ? 'destructive' : 'secondary'}
|
|
128
|
+
className="text-xs capitalize"
|
|
129
|
+
>
|
|
130
|
+
{item.criticalityLevel}
|
|
131
|
+
</Badge>
|
|
132
|
+
) : "—",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
key: "defaultPmIntervalDays",
|
|
136
|
+
header: "Default PM",
|
|
137
|
+
render: (item: any) => item.defaultPmIntervalDays ? `${item.defaultPmIntervalDays} days` : "—",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
key: "description",
|
|
141
|
+
header: "Description",
|
|
142
|
+
render: (item: any) => (
|
|
143
|
+
<span className="text-xs text-muted-foreground truncate max-w-[200px] block">
|
|
144
|
+
{item.description || "—"}
|
|
145
|
+
</span>
|
|
146
|
+
),
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const manufacturerColumns = [
|
|
151
|
+
{
|
|
152
|
+
key: "code",
|
|
153
|
+
header: "Code",
|
|
154
|
+
render: (item: any) => (
|
|
155
|
+
<span className="font-mono text-xs font-semibold text-primary">
|
|
156
|
+
{item.code || "—"}
|
|
157
|
+
</span>
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
key: "name",
|
|
162
|
+
header: "Manufacturer Name",
|
|
163
|
+
render: (item: any) => (
|
|
164
|
+
<span className="font-medium text-foreground">{item.name}</span>
|
|
165
|
+
),
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
key: "country",
|
|
169
|
+
header: "Country",
|
|
170
|
+
render: (item: any) => item.country || "—",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: "contacts",
|
|
174
|
+
header: "Support Contact",
|
|
175
|
+
render: (item: any) => (
|
|
176
|
+
<div className="text-xs space-y-0.5">
|
|
177
|
+
{item.supportPhone && <div>{item.supportPhone}</div>}
|
|
178
|
+
{item.supportEmail && <div className="text-muted-foreground">{item.supportEmail}</div>}
|
|
179
|
+
{!item.supportPhone && !item.supportEmail && "—"}
|
|
180
|
+
</div>
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
key: "website",
|
|
185
|
+
header: "Website",
|
|
186
|
+
render: (item: any) => item.website ? (
|
|
187
|
+
<a
|
|
188
|
+
href={item.website}
|
|
189
|
+
target="_blank"
|
|
190
|
+
rel="noreferrer"
|
|
191
|
+
className="text-xs text-primary hover:underline truncate max-w-[160px] block"
|
|
192
|
+
>
|
|
193
|
+
{item.website.replace(/^https?:\/\//, '')}
|
|
194
|
+
</a>
|
|
195
|
+
) : "—",
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className="space-y-8">
|
|
201
|
+
<PageHeader
|
|
202
|
+
title="General Settings"
|
|
203
|
+
description="Manage your health system organization and hospital facilities"
|
|
204
|
+
/>
|
|
205
|
+
|
|
206
|
+
{/* ── Section 1: Health System Organization ── */}
|
|
207
|
+
<div className="space-y-3">
|
|
208
|
+
<div className="flex items-center justify-between">
|
|
209
|
+
<div className="flex items-center gap-2">
|
|
210
|
+
<Building2 className="w-5 h-5 text-primary" />
|
|
211
|
+
<h2 className="text-lg font-semibold tracking-tight">Organization Profile</h2>
|
|
212
|
+
</div>
|
|
213
|
+
{isSysAdmin && (
|
|
214
|
+
<div className="flex gap-2">
|
|
215
|
+
{organizations.length > 1 && (
|
|
216
|
+
<Button variant="outline" size="sm" asChild>
|
|
217
|
+
<Link href="/admin/organizations">Switch Organization</Link>
|
|
218
|
+
</Button>
|
|
219
|
+
)}
|
|
220
|
+
<Button variant="outline" size="sm" asChild>
|
|
221
|
+
<Link href="/admin/organizations/new">
|
|
222
|
+
<Plus className="w-4 h-4 mr-1" />
|
|
223
|
+
New Organization
|
|
224
|
+
</Link>
|
|
225
|
+
</Button>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
|
|
230
|
+
{currentOrg ? (
|
|
231
|
+
<Card className="border shadow-sm">
|
|
232
|
+
<CardContent className="p-5 sm:p-6 space-y-4">
|
|
233
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
234
|
+
<div className="flex items-start gap-3.5">
|
|
235
|
+
<div className="p-3 rounded-2xl bg-primary/10 text-primary shrink-0">
|
|
236
|
+
<ShieldCheck className="w-6 h-6" />
|
|
237
|
+
</div>
|
|
238
|
+
<div className="space-y-1">
|
|
239
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
240
|
+
<h3 className="text-xl font-bold">{currentOrg.name}</h3>
|
|
241
|
+
<Badge variant="outline" className="font-mono text-xs">
|
|
242
|
+
{currentOrg.code}
|
|
243
|
+
</Badge>
|
|
244
|
+
<StatusBadge status={currentOrg.status} />
|
|
245
|
+
</div>
|
|
246
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground pt-0.5">
|
|
247
|
+
<span className="flex items-center gap-1">
|
|
248
|
+
<Globe className="w-3.5 h-3.5" />
|
|
249
|
+
Timezone: {currentOrg.defaultTimezone || "UTC"}
|
|
250
|
+
</span>
|
|
251
|
+
{currentOrg.createdAt && (
|
|
252
|
+
<span className="flex items-center gap-1">
|
|
253
|
+
<Calendar className="w-3.5 h-3.5" />
|
|
254
|
+
Created: {new Date(currentOrg.createdAt).toLocaleDateString()}
|
|
255
|
+
</span>
|
|
256
|
+
)}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div className="flex items-center gap-2">
|
|
262
|
+
<Button asChild variant="outline" size="sm">
|
|
263
|
+
<Link href={`/admin/organizations/${currentOrg.id}/edit`}>
|
|
264
|
+
<Edit className="w-4 h-4 mr-1.5" />
|
|
265
|
+
Edit Details
|
|
266
|
+
</Link>
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</CardContent>
|
|
271
|
+
</Card>
|
|
272
|
+
) : (
|
|
273
|
+
<Card className="border-dashed">
|
|
274
|
+
<CardContent className="py-8 text-center text-muted-foreground text-sm">
|
|
275
|
+
No organization profile registered.
|
|
276
|
+
</CardContent>
|
|
277
|
+
</Card>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
{/* ── Section 2: Hospital Facilities ── */}
|
|
282
|
+
<div className="space-y-3">
|
|
283
|
+
<div className="flex items-center justify-between">
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<Hospital className="w-5 h-5 text-primary" />
|
|
286
|
+
<h2 className="text-lg font-semibold tracking-tight">Hospital Facilities & Campuses</h2>
|
|
287
|
+
<Badge variant="secondary" className="text-xs">
|
|
288
|
+
{hospitals.length}
|
|
289
|
+
</Badge>
|
|
290
|
+
</div>
|
|
291
|
+
<Button asChild size="sm">
|
|
292
|
+
<Link href="/admin/hospitals/new">
|
|
293
|
+
<Plus className="w-4 h-4 mr-1" />
|
|
294
|
+
Add Hospital
|
|
295
|
+
</Link>
|
|
296
|
+
</Button>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<DataTable
|
|
300
|
+
columns={hospitalColumns}
|
|
301
|
+
data={hospitals}
|
|
302
|
+
searchable
|
|
303
|
+
searchPlaceholder="Search hospitals by name, code, city..."
|
|
304
|
+
emptyMessage="No hospital facilities configured yet."
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* ── Section 3: Medical Device Reference Catalogs ── */}
|
|
309
|
+
<div className="space-y-4 pt-2">
|
|
310
|
+
<div className="space-y-1">
|
|
311
|
+
<div className="flex items-center gap-2">
|
|
312
|
+
<Cpu className="w-5 h-5 text-primary" />
|
|
313
|
+
<h2 className="text-lg font-semibold tracking-tight">Device Master Catalogs & Reference Data</h2>
|
|
314
|
+
</div>
|
|
315
|
+
<p className="text-xs text-muted-foreground">
|
|
316
|
+
Standardize device classifications, risk categories, and approved manufacturers across your health system
|
|
317
|
+
</p>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
<Tabs defaultValue="categories" className="space-y-4">
|
|
321
|
+
<TabsList>
|
|
322
|
+
<TabsTrigger value="categories" className="gap-2">
|
|
323
|
+
<Cpu className="w-3.5 h-3.5" />
|
|
324
|
+
Categories ({categories.length})
|
|
325
|
+
</TabsTrigger>
|
|
326
|
+
<TabsTrigger value="manufacturers" className="gap-2">
|
|
327
|
+
<Factory className="w-3.5 h-3.5" />
|
|
328
|
+
Manufacturers ({manufacturers.length})
|
|
329
|
+
</TabsTrigger>
|
|
330
|
+
</TabsList>
|
|
331
|
+
|
|
332
|
+
<TabsContent value="categories" className="space-y-3">
|
|
333
|
+
<div className="flex items-center justify-between">
|
|
334
|
+
<div className="text-sm font-medium text-muted-foreground">
|
|
335
|
+
Registered Device Categories
|
|
336
|
+
</div>
|
|
337
|
+
<QuickAddCategoryDialog
|
|
338
|
+
triggerLabel="Add Category"
|
|
339
|
+
triggerVariant="default"
|
|
340
|
+
/>
|
|
341
|
+
</div>
|
|
342
|
+
<DataTable
|
|
343
|
+
columns={categoryColumns}
|
|
344
|
+
data={categories}
|
|
345
|
+
searchable
|
|
346
|
+
searchPlaceholder="Search categories by name, code, risk..."
|
|
347
|
+
emptyMessage="No device categories configured yet."
|
|
348
|
+
/>
|
|
349
|
+
</TabsContent>
|
|
350
|
+
|
|
351
|
+
<TabsContent value="manufacturers" className="space-y-3">
|
|
352
|
+
<div className="flex items-center justify-between">
|
|
353
|
+
<div className="text-sm font-medium text-muted-foreground">
|
|
354
|
+
Approved Manufacturers & Vendors
|
|
355
|
+
</div>
|
|
356
|
+
<QuickAddManufacturerDialog
|
|
357
|
+
triggerLabel="Add Manufacturer"
|
|
358
|
+
triggerVariant="default"
|
|
359
|
+
/>
|
|
360
|
+
</div>
|
|
361
|
+
<DataTable
|
|
362
|
+
columns={manufacturerColumns}
|
|
363
|
+
data={manufacturers}
|
|
364
|
+
searchable
|
|
365
|
+
searchPlaceholder="Search manufacturers by name, code, country..."
|
|
366
|
+
emptyMessage="No manufacturers configured yet."
|
|
367
|
+
/>
|
|
368
|
+
</TabsContent>
|
|
369
|
+
</Tabs>
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { PageHeader } from "@/components/shared/page-header";
|
|
4
|
+
import { Button } from "@/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
Form,
|
|
7
|
+
FormControl,
|
|
8
|
+
FormField,
|
|
9
|
+
FormItem,
|
|
10
|
+
FormLabel,
|
|
11
|
+
FormMessage,
|
|
12
|
+
} from "@/components/ui/form";
|
|
13
|
+
import { Input } from "@/components/ui/input";
|
|
14
|
+
import {
|
|
15
|
+
Select,
|
|
16
|
+
SelectContent,
|
|
17
|
+
SelectItem,
|
|
18
|
+
SelectTrigger,
|
|
19
|
+
SelectValue,
|
|
20
|
+
} from "@/components/ui/select";
|
|
21
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
22
|
+
import { useForm } from "react-hook-form";
|
|
23
|
+
import * as z from "zod";
|
|
24
|
+
import { useRouter } from "next/navigation";
|
|
25
|
+
import { useEffect, useState, use } from "react";
|
|
26
|
+
import { getHospitalById, updateHospital } from "@/lib/actions/hospitals";
|
|
27
|
+
import { getOrganizations } from "@/lib/actions/organizations";
|
|
28
|
+
|
|
29
|
+
const formSchema = z.object({
|
|
30
|
+
organizationId: z.string().min(1, "Please select an organization"),
|
|
31
|
+
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
32
|
+
code: z.string().min(2, "Code must be at least 2 characters").toUpperCase(),
|
|
33
|
+
address: z.string().min(5, "Address is required"),
|
|
34
|
+
city: z.string().min(2, "City is required"),
|
|
35
|
+
country: z.string().min(2, "Country is required"),
|
|
36
|
+
phone: z.string().optional(),
|
|
37
|
+
email: z.string().email("Invalid email").optional().or(z.literal("")),
|
|
38
|
+
timezone: z.string().min(1, "Please select a timezone"),
|
|
39
|
+
status: z.enum(["active", "inactive"]),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default function EditHospitalPage({ params }: { params: Promise<{ hospitalId: string }> }) {
|
|
43
|
+
const resolvedParams = use(params);
|
|
44
|
+
const router = useRouter();
|
|
45
|
+
const [organizations, setOrganizations] = useState<any[]>([]);
|
|
46
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
47
|
+
|
|
48
|
+
const form = useForm<z.infer<typeof formSchema>>({
|
|
49
|
+
resolver: zodResolver(formSchema),
|
|
50
|
+
defaultValues: {
|
|
51
|
+
organizationId: "",
|
|
52
|
+
name: "",
|
|
53
|
+
code: "",
|
|
54
|
+
address: "",
|
|
55
|
+
city: "",
|
|
56
|
+
country: "",
|
|
57
|
+
phone: "",
|
|
58
|
+
email: "",
|
|
59
|
+
timezone: "UTC",
|
|
60
|
+
status: "active",
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
async function loadData() {
|
|
66
|
+
const [orgsResult, hospitalResult] = await Promise.all([
|
|
67
|
+
getOrganizations(),
|
|
68
|
+
getHospitalById(resolvedParams.hospitalId),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
if (orgsResult.success && orgsResult.data) {
|
|
72
|
+
setOrganizations(orgsResult.data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (hospitalResult.success && hospitalResult.data) {
|
|
76
|
+
const h = hospitalResult.data;
|
|
77
|
+
form.reset({
|
|
78
|
+
organizationId: h.organizationId,
|
|
79
|
+
name: h.name,
|
|
80
|
+
code: h.code,
|
|
81
|
+
address: h.address || "",
|
|
82
|
+
city: h.city || "",
|
|
83
|
+
country: h.country || "",
|
|
84
|
+
phone: h.phone || "",
|
|
85
|
+
email: h.email || "",
|
|
86
|
+
timezone: h.timezone || "UTC",
|
|
87
|
+
status: (h.status === "inactive" ? "inactive" : "active") as "active" | "inactive",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
setIsLoading(false);
|
|
91
|
+
}
|
|
92
|
+
loadData();
|
|
93
|
+
}, [resolvedParams.hospitalId, form]);
|
|
94
|
+
|
|
95
|
+
async function onSubmit(values: z.infer<typeof formSchema>) {
|
|
96
|
+
const result = await updateHospital(resolvedParams.hospitalId, values);
|
|
97
|
+
if (result.success) {
|
|
98
|
+
router.push(`/admin/hospitals/${resolvedParams.hospitalId}`);
|
|
99
|
+
} else {
|
|
100
|
+
console.error((result as any).error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (isLoading) {
|
|
105
|
+
return <div className="p-8 text-center">Loading...</div>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="space-y-6 max-w-2xl">
|
|
110
|
+
<PageHeader title="Edit Hospital" description="Update hospital details" />
|
|
111
|
+
|
|
112
|
+
<Form {...form}>
|
|
113
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
|
114
|
+
<FormField
|
|
115
|
+
control={form.control}
|
|
116
|
+
name="organizationId"
|
|
117
|
+
render={({ field }) => (
|
|
118
|
+
<FormItem>
|
|
119
|
+
<FormLabel>Organization</FormLabel>
|
|
120
|
+
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
121
|
+
<FormControl>
|
|
122
|
+
<SelectTrigger>
|
|
123
|
+
<SelectValue placeholder="Select an organization" />
|
|
124
|
+
</SelectTrigger>
|
|
125
|
+
</FormControl>
|
|
126
|
+
<SelectContent>
|
|
127
|
+
{organizations.map((org) => (
|
|
128
|
+
<SelectItem key={org.id} value={org.id}>
|
|
129
|
+
{org.name}
|
|
130
|
+
</SelectItem>
|
|
131
|
+
))}
|
|
132
|
+
</SelectContent>
|
|
133
|
+
</Select>
|
|
134
|
+
<FormMessage />
|
|
135
|
+
</FormItem>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
140
|
+
<FormField
|
|
141
|
+
control={form.control}
|
|
142
|
+
name="name"
|
|
143
|
+
render={({ field }) => (
|
|
144
|
+
<FormItem>
|
|
145
|
+
<FormLabel>Name</FormLabel>
|
|
146
|
+
<FormControl>
|
|
147
|
+
<Input placeholder="General Hospital" {...field} />
|
|
148
|
+
</FormControl>
|
|
149
|
+
<FormMessage />
|
|
150
|
+
</FormItem>
|
|
151
|
+
)}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<FormField
|
|
155
|
+
control={form.control}
|
|
156
|
+
name="code"
|
|
157
|
+
render={({ field }) => (
|
|
158
|
+
<FormItem>
|
|
159
|
+
<FormLabel>Code</FormLabel>
|
|
160
|
+
<FormControl>
|
|
161
|
+
<Input placeholder="GEN" {...field} />
|
|
162
|
+
</FormControl>
|
|
163
|
+
<FormMessage />
|
|
164
|
+
</FormItem>
|
|
165
|
+
)}
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<FormField
|
|
170
|
+
control={form.control}
|
|
171
|
+
name="address"
|
|
172
|
+
render={({ field }) => (
|
|
173
|
+
<FormItem>
|
|
174
|
+
<FormLabel>Address</FormLabel>
|
|
175
|
+
<FormControl>
|
|
176
|
+
<Input placeholder="123 Health Ave" {...field} />
|
|
177
|
+
</FormControl>
|
|
178
|
+
<FormMessage />
|
|
179
|
+
</FormItem>
|
|
180
|
+
)}
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
184
|
+
<FormField
|
|
185
|
+
control={form.control}
|
|
186
|
+
name="city"
|
|
187
|
+
render={({ field }) => (
|
|
188
|
+
<FormItem>
|
|
189
|
+
<FormLabel>City</FormLabel>
|
|
190
|
+
<FormControl>
|
|
191
|
+
<Input placeholder="Metropolis" {...field} />
|
|
192
|
+
</FormControl>
|
|
193
|
+
<FormMessage />
|
|
194
|
+
</FormItem>
|
|
195
|
+
)}
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
<FormField
|
|
199
|
+
control={form.control}
|
|
200
|
+
name="country"
|
|
201
|
+
render={({ field }) => (
|
|
202
|
+
<FormItem>
|
|
203
|
+
<FormLabel>Country</FormLabel>
|
|
204
|
+
<FormControl>
|
|
205
|
+
<Input placeholder="USA" {...field} />
|
|
206
|
+
</FormControl>
|
|
207
|
+
<FormMessage />
|
|
208
|
+
</FormItem>
|
|
209
|
+
)}
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
214
|
+
<FormField
|
|
215
|
+
control={form.control}
|
|
216
|
+
name="phone"
|
|
217
|
+
render={({ field }) => (
|
|
218
|
+
<FormItem>
|
|
219
|
+
<FormLabel>Phone (Optional)</FormLabel>
|
|
220
|
+
<FormControl>
|
|
221
|
+
<Input placeholder="+1 555-0123" {...field} />
|
|
222
|
+
</FormControl>
|
|
223
|
+
<FormMessage />
|
|
224
|
+
</FormItem>
|
|
225
|
+
)}
|
|
226
|
+
/>
|
|
227
|
+
|
|
228
|
+
<FormField
|
|
229
|
+
control={form.control}
|
|
230
|
+
name="email"
|
|
231
|
+
render={({ field }) => (
|
|
232
|
+
<FormItem>
|
|
233
|
+
<FormLabel>Email (Optional)</FormLabel>
|
|
234
|
+
<FormControl>
|
|
235
|
+
<Input placeholder="info@hospital.com" type="email" {...field} />
|
|
236
|
+
</FormControl>
|
|
237
|
+
<FormMessage />
|
|
238
|
+
</FormItem>
|
|
239
|
+
)}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
|
|
243
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
244
|
+
<FormField
|
|
245
|
+
control={form.control}
|
|
246
|
+
name="timezone"
|
|
247
|
+
render={({ field }) => (
|
|
248
|
+
<FormItem>
|
|
249
|
+
<FormLabel>Timezone</FormLabel>
|
|
250
|
+
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
251
|
+
<FormControl>
|
|
252
|
+
<SelectTrigger>
|
|
253
|
+
<SelectValue placeholder="Select a timezone" />
|
|
254
|
+
</SelectTrigger>
|
|
255
|
+
</FormControl>
|
|
256
|
+
<SelectContent>
|
|
257
|
+
<SelectItem value="UTC">UTC</SelectItem>
|
|
258
|
+
<SelectItem value="America/New_York">Eastern Time (US & Canada)</SelectItem>
|
|
259
|
+
<SelectItem value="America/Chicago">Central Time (US & Canada)</SelectItem>
|
|
260
|
+
<SelectItem value="America/Denver">Mountain Time (US & Canada)</SelectItem>
|
|
261
|
+
<SelectItem value="America/Los_Angeles">Pacific Time (US & Canada)</SelectItem>
|
|
262
|
+
<SelectItem value="Europe/London">London</SelectItem>
|
|
263
|
+
<SelectItem value="Europe/Paris">Paris</SelectItem>
|
|
264
|
+
</SelectContent>
|
|
265
|
+
</Select>
|
|
266
|
+
<FormMessage />
|
|
267
|
+
</FormItem>
|
|
268
|
+
)}
|
|
269
|
+
/>
|
|
270
|
+
|
|
271
|
+
<FormField
|
|
272
|
+
control={form.control}
|
|
273
|
+
name="status"
|
|
274
|
+
render={({ field }) => (
|
|
275
|
+
<FormItem>
|
|
276
|
+
<FormLabel>Status</FormLabel>
|
|
277
|
+
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
|
278
|
+
<FormControl>
|
|
279
|
+
<SelectTrigger>
|
|
280
|
+
<SelectValue placeholder="Select status" />
|
|
281
|
+
</SelectTrigger>
|
|
282
|
+
</FormControl>
|
|
283
|
+
<SelectContent>
|
|
284
|
+
<SelectItem value="active">Active</SelectItem>
|
|
285
|
+
<SelectItem value="inactive">Inactive</SelectItem>
|
|
286
|
+
</SelectContent>
|
|
287
|
+
</Select>
|
|
288
|
+
<FormMessage />
|
|
289
|
+
</FormItem>
|
|
290
|
+
)}
|
|
291
|
+
/>
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<div className="flex gap-4">
|
|
295
|
+
<Button type="submit">Save Changes</Button>
|
|
296
|
+
<Button type="button" variant="outline" onClick={() => router.back()}>
|
|
297
|
+
Cancel
|
|
298
|
+
</Button>
|
|
299
|
+
</div>
|
|
300
|
+
</form>
|
|
301
|
+
</Form>
|
|
302
|
+
</div>
|
|
303
|
+
);
|
|
304
|
+
}
|