@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
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BEMMS Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,204 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🏥 BEMMS — Biomedical Engineering & Maintenance Management System
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/omersx/bemms/actions/workflows/ci.yml)
|
|
4
|
+
[](https://hub.docker.com/r/omersx/bemms)
|
|
5
|
+
[](https://www.npmjs.com/package/@omersx/bemms)
|
|
6
|
+
[](https://nextjs.org/)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://tailwindcss.com/)
|
|
9
|
+
[](https://www.postgresql.org/)
|
|
10
|
+
[](https://orm.drizzle.team/)
|
|
11
|
+
[](https://vitest.dev/)
|
|
12
|
+
[](https://opensource.org/licenses/MIT)
|
|
13
|
+
[]()
|
|
14
|
+
|
|
15
|
+
**BEMMS** is a production-grade, full-stack Biomedical Engineering and Computerized Maintenance Management System (CMMS) designed for hospitals, clinical healthcare networks, and medical device service teams.
|
|
16
|
+
|
|
17
|
+
It bridges the gap between frontline clinical healthcare workers and biomedical engineering teams—enabling instant equipment triage, QR-based mobile fault reporting, preventive maintenance tracking, and FDA 21 CFR Part 11 compliant cryptographic electronic signatures.
|
|
18
|
+
|
|
19
|
+
> 📖 **Comprehensive Manual**: For in-depth architectural specs, interactive Mermaid workflows, complete database ERDs, state machines, and operational guidelines, see the **[Full Architecture & Operations Manual (full-readme.md)](full-readme.md)**.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🌟 Key Features
|
|
24
|
+
|
|
25
|
+
### 1. 📋 Medical Equipment Inventory & Lifecycle Management
|
|
26
|
+
- **Centralized Asset Registry**: Track equipment across hospitals, departments, wards, and rooms.
|
|
27
|
+
- **Criticality & Risk Scoring**: Classify devices by WHO risk tiers (Class I, IIa, IIb, III) and operational criticality.
|
|
28
|
+
- **Availability State Machine**: Real-time status tracking (`operational`, `operational_with_limitations`, `under_maintenance`, `under_repair`, `waiting_for_parts`, `awaiting_release`, `standby`, `out_of_service`, `decommissioned`).
|
|
29
|
+
- **Complete Audit Trail**: Every status change, location relocation, and calibration event is permanently logged with timestamps and actor IDs.
|
|
30
|
+
|
|
31
|
+
### 2. 📱 QR Code Generation & Mobile Field Scanning
|
|
32
|
+
- **Instant Camera Scanner**: Built-in QR camera scanner (`html5-qrcode`) for instant mobile device lookup.
|
|
33
|
+
- **High-Resolution QR Labels**: Printable labels with hospital branding, equipment name, asset tag, and direct URL.
|
|
34
|
+
- **Public & Authenticated Scan Workflow**: Clinical staff can scan any unit from their phone to immediately view availability and report faults without typing serial numbers.
|
|
35
|
+
|
|
36
|
+
### 3. 🎫 Clinical Helpdesk & Ticket Triage System
|
|
37
|
+
- **Color-Coded Triage Queue**: Visual priority cards with left accent rails:
|
|
38
|
+
- 🔴 **Red / Rose**: New, unassigned, and high-urgency problem tickets.
|
|
39
|
+
- 🟡 **Yellow / Amber**: Active repairs underway.
|
|
40
|
+
- 📦 **Orange**: Paused awaiting spare parts from vendor.
|
|
41
|
+
- 🟢 **Emerald Green**: Repaired and certified operational.
|
|
42
|
+
- ⚪ **Slate Blue**: Signed off and permanently archived.
|
|
43
|
+
- **Card View vs. Table View**: Seamless switcher between touch-friendly responsive cards and compact data tables.
|
|
44
|
+
- **Live Status Filtering**: Real-time search by title, ticket #, equipment model, asset tag, and department.
|
|
45
|
+
|
|
46
|
+
### 4. 📦 Spare Parts Hold & Vendor Workflow
|
|
47
|
+
- **Integrated Supply Chain Hold**: Pause active repairs when replacement boards, sensors, or cables must be ordered.
|
|
48
|
+
- **Clinical Department Visibility**: Automatically notifies ward staff and dashboard that equipment is on hold.
|
|
49
|
+
- **One-Click Resumption**: Resume work instantly when parts arrive at the hospital workshop.
|
|
50
|
+
|
|
51
|
+
### 5. 🔏 21 CFR Part 11 Compliant Electronic Signatures
|
|
52
|
+
- **Cryptographic Tamper-Evidence**: Password re-authentication generates SHA-256 hash digests permanently chained into regulatory audit tables.
|
|
53
|
+
- **Binding Attestation Statements**: Legal attestation captured for Acceptance, Resolution, and Formal Closure.
|
|
54
|
+
- **Multi-Party Sign-Off**: Performer, Reviewer, and Release Authority signature hierarchies.
|
|
55
|
+
|
|
56
|
+
### 6. 🔧 Preventive Maintenance (PM) & Compliance
|
|
57
|
+
- **Automated Recurrence Engine**: Scheduled maintenance occurrences generated based on calendar frequency (monthly, quarterly, semi-annual, annual).
|
|
58
|
+
- **Inspection Checklists**: Step-by-step biomedical verification procedures with electrical safety and calibration standards.
|
|
59
|
+
|
|
60
|
+
### 7. 🏥 Hospital Hierarchy & Role-Based Access Control (RBAC)
|
|
61
|
+
- **Multi-Tenant Architecture**: Organization ➔ Hospital ➔ Department ➔ Location.
|
|
62
|
+
- **Strict Role Separation**:
|
|
63
|
+
- `SYS_ADMIN`: Complete system configuration, user provisioning, and audit logs.
|
|
64
|
+
- `BIOMED_MGR`: Work order allocation, QA approval, and compliance reporting.
|
|
65
|
+
- `BIOMED_ENG` / `BIOMED_TECH`: Active repair execution, electronic signatures, and parts tracking.
|
|
66
|
+
- `DEPT_MGR` / `CLINICAL_STAFF`: Equipment scanning, department availability tracking, and fault reporting.
|
|
67
|
+
|
|
68
|
+
### 8. 🚨 Real-Time Ticket Dispatch & Native Push Notifications
|
|
69
|
+
- **Instant Engineering Broadcast**: Submitting a ticket triggers native VAPID Web Push notifications and real-time SSE in-app alerts to all scoped biomedical engineers.
|
|
70
|
+
- **Waiting-for-Engineer Indicators**: Clear live wait durations and visual urgency markers ("⏳ Waiting for engineer to accept").
|
|
71
|
+
- **1-Click Responsibility Acceptance**: Engineers can accept ticket responsibility with 21 CFR Part 11 signatures directly from the dispatch alert.
|
|
72
|
+
- **Clinical Reporter Updates**: Instant confirmation notifications sent to ward staff when an engineer takes ownership.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🛠️ Architecture & Tech Stack
|
|
77
|
+
|
|
78
|
+
| Layer | Technology |
|
|
79
|
+
|---|---|
|
|
80
|
+
| **Framework** | [Next.js 15](https://nextjs.org/) (App Router, Server Actions, React 19) |
|
|
81
|
+
| **Language** | [TypeScript 5.7](https://www.typescriptlang.org/) (Strict Mode) |
|
|
82
|
+
| **Styling & UI** | [Tailwind CSS](https://tailwindcss.com/) + [shadcn/ui](https://ui.shadcn.com/) + [Radix UI](https://www.radix-ui.com/) |
|
|
83
|
+
| **Database & ORM**| [PostgreSQL 16](https://www.postgresql.org/) + [Drizzle ORM](https://orm.drizzle.team/) |
|
|
84
|
+
| **Authentication** | [Auth.js / NextAuth v5](https://authjs.dev/) with bcrypt password hashing |
|
|
85
|
+
| **Validation** | [Zod](https://zod.dev/) type validation schemas |
|
|
86
|
+
| **Testing** | [Vitest](https://vitest.dev/) (Unit & integration test suites) |
|
|
87
|
+
| **Deployment** | [Docker Compose](https://docs.docker.com/compose/) + [Caddy](https://caddyserver.com/) Reverse Proxy |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🚀 Quick Start Guide
|
|
92
|
+
|
|
93
|
+
### Prerequisites
|
|
94
|
+
- [Node.js](https://nodejs.org/) (v20+ recommended)
|
|
95
|
+
- [pnpm](https://pnpm.io/) (v9+) or npm
|
|
96
|
+
- [Docker & Docker Compose](https://www.docker.com/)
|
|
97
|
+
|
|
98
|
+
### 1. Clone the Repository
|
|
99
|
+
```bash
|
|
100
|
+
git clone https://github.com/omersx/bemms.git
|
|
101
|
+
cd bemms
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Configure Environment Variables
|
|
105
|
+
Copy `.env.example` to `.env.local`:
|
|
106
|
+
```bash
|
|
107
|
+
cp .env.example .env.local
|
|
108
|
+
```
|
|
109
|
+
*(The default `.env.example` is pre-configured to work out-of-the-box with the local Docker database).*
|
|
110
|
+
|
|
111
|
+
### 3. Start Database via Docker
|
|
112
|
+
```bash
|
|
113
|
+
docker compose up -d db
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 4. Install Dependencies & Initialize Database
|
|
117
|
+
```bash
|
|
118
|
+
pnpm install
|
|
119
|
+
pnpm db:push
|
|
120
|
+
pnpm db:seed
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 5. Start the Development Server
|
|
124
|
+
```bash
|
|
125
|
+
pnpm dev
|
|
126
|
+
```
|
|
127
|
+
Open **[http://localhost:3000](http://localhost:3000)** in your browser.
|
|
128
|
+
|
|
129
|
+
**Default Administrator Account:**
|
|
130
|
+
- **Email:** `admin@bemms.local`
|
|
131
|
+
- **Password:** `admin123`
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🧪 Testing & Code Quality
|
|
136
|
+
|
|
137
|
+
Run automated test suites with Vitest:
|
|
138
|
+
```bash
|
|
139
|
+
# Run all tests once
|
|
140
|
+
pnpm test
|
|
141
|
+
|
|
142
|
+
# Run tests in watch mode
|
|
143
|
+
pnpm test:watch
|
|
144
|
+
|
|
145
|
+
# Generate code coverage report
|
|
146
|
+
pnpm test:coverage
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Type checking:
|
|
150
|
+
```bash
|
|
151
|
+
pnpm exec tsc --noEmit
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 📁 Project Structure
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
BEMMS-app/
|
|
160
|
+
├── src/
|
|
161
|
+
│ ├── app/ # Next.js 15 App Router pages & API routes
|
|
162
|
+
│ │ ├── (auth)/ # Login & authentication routes
|
|
163
|
+
│ │ ├── (dashboard)/ # Authenticated application modules
|
|
164
|
+
│ │ │ ├── admin/ # Hospital, department, user, and system administration
|
|
165
|
+
│ │ │ ├── departments/ # Department overview and device availability
|
|
166
|
+
│ │ │ ├── devices/ # Equipment lifecycle, profiles, and QR codes
|
|
167
|
+
│ │ │ ├── maintenance/ # Tasks, work orders, and electronic signatures
|
|
168
|
+
│ │ │ ├── reports/ # KPI dashboards, compliance, and workload reports
|
|
169
|
+
│ │ │ ├── scan/ # Mobile camera QR scanner & lookup
|
|
170
|
+
│ │ │ └── tickets/ # Helpdesk triage, status cards, and resolution
|
|
171
|
+
│ │ └── api/ # REST endpoints (export, QR, web push)
|
|
172
|
+
│ ├── components/ # React UI components (shadcn/ui + domain components)
|
|
173
|
+
│ │ ├── admin/ # Settings, audit logs, and data management
|
|
174
|
+
│ │ ├── departments/ # Department visual cards and metrics
|
|
175
|
+
│ │ ├── devices/ # Status badges, QR generators, and transfer dialogs
|
|
176
|
+
│ │ ├── signatures/ # 21 CFR Part 11 signature dialogs and audit chain
|
|
177
|
+
│ │ ├── tickets/ # Ticket cards view, triage panels, and resolution flows
|
|
178
|
+
│ │ └── ui/ # Reusable shadcn/ui primitives
|
|
179
|
+
│ ├── lib/
|
|
180
|
+
│ │ ├── actions/ # Next.js Server Actions (type-safe business logic)
|
|
181
|
+
│ │ ├── auth/ # RBAC, session verification, and permission checks
|
|
182
|
+
│ │ ├── db/ # Drizzle ORM schema definitions and migrations
|
|
183
|
+
│ │ ├── utils/ # Cryptography (SHA-256), attestations, and exports
|
|
184
|
+
│ │ └── validators/ # Zod validation schemas and unit tests
|
|
185
|
+
├── docker-compose.yml # Local development PostgreSQL configuration
|
|
186
|
+
├── docker-compose.prod.yml # Production container deployment
|
|
187
|
+
├── Dockerfile # Multi-stage optimized production Docker build
|
|
188
|
+
├── Caddyfile # Automatic HTTPS Caddy configuration
|
|
189
|
+
└── package.json # Dependencies and scripts
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 📜 Compliance & Regulatory Standards
|
|
195
|
+
|
|
196
|
+
- **FDA 21 CFR Part 11**: Cryptographic password re-authentication, immutable audit logging, electronic signature manifest, and SHA-256 chain of custody.
|
|
197
|
+
- **IEC 62353**: In-service and post-repair medical electrical equipment safety testing workflows.
|
|
198
|
+
- **Role Separation of Duties**: Prevents engineers from approving their own work orders when managerial sign-off policy is active.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 📄 License
|
|
203
|
+
|
|
204
|
+
This project is licensed under the [MIT License](LICENSE).
|
package/next.config.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const nextConfig: NextConfig = {
|
|
5
|
+
outputFileTracingRoot: path.resolve(__dirname),
|
|
6
|
+
// Standalone output enabled in Docker / production environments (avoids Windows symlink EPERM)
|
|
7
|
+
output: process.env.BUILD_STANDALONE === "true" ? "standalone" : undefined,
|
|
8
|
+
poweredByHeader: false,
|
|
9
|
+
serverExternalPackages: ["pg", "xlsx"],
|
|
10
|
+
// ESLint is handled separately by `tsc --noEmit` in CI; skip during `next build`
|
|
11
|
+
eslint: {
|
|
12
|
+
ignoreDuringBuilds: true,
|
|
13
|
+
},
|
|
14
|
+
redirects: async () => [
|
|
15
|
+
{
|
|
16
|
+
source: "/tickets/report",
|
|
17
|
+
destination: "/tickets/create",
|
|
18
|
+
permanent: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
source: "/dashboard/my-work",
|
|
22
|
+
destination: "/maintenance/tasks",
|
|
23
|
+
permanent: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
headers: async () => [
|
|
27
|
+
{
|
|
28
|
+
source: "/:path*",
|
|
29
|
+
headers: [
|
|
30
|
+
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
31
|
+
{ key: "X-Frame-Options", value: "DENY" },
|
|
32
|
+
{ key: "X-XSS-Protection", value: "1; mode=block" },
|
|
33
|
+
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
34
|
+
{
|
|
35
|
+
key: "Content-Security-Policy",
|
|
36
|
+
value: [
|
|
37
|
+
"default-src 'self'",
|
|
38
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
|
39
|
+
"style-src 'self' 'unsafe-inline'",
|
|
40
|
+
"img-src 'self' data: blob:",
|
|
41
|
+
"font-src 'self' data:",
|
|
42
|
+
"connect-src 'self'",
|
|
43
|
+
"frame-ancestors 'none'",
|
|
44
|
+
].join("; "),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default nextConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,102 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omersx/bemms",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Biomedical Engineering & Maintenance Management System (BEMMS)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/omersx/bemms.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"registry": "https://registry.npmjs.org/"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"public",
|
|
18
|
+
"drizzle",
|
|
19
|
+
"next.config.ts",
|
|
20
|
+
"tailwind.config.ts",
|
|
21
|
+
"postcss.config.mjs",
|
|
22
|
+
"tsconfig.json",
|
|
23
|
+
"drizzle.config.ts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@auth/drizzle-adapter": "^1.7.4",
|
|
29
|
+
"@hookform/resolvers": "^5.9.1",
|
|
30
|
+
"@radix-ui/react-avatar": "^1.1.3",
|
|
31
|
+
"@radix-ui/react-checkbox": "^1.1.4",
|
|
32
|
+
"@radix-ui/react-dialog": "^1.1.6",
|
|
33
|
+
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
|
34
|
+
"@radix-ui/react-label": "^2.1.2",
|
|
35
|
+
"@radix-ui/react-navigation-menu": "^1.2.5",
|
|
36
|
+
"@radix-ui/react-popover": "^1.1.23",
|
|
37
|
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
|
38
|
+
"@radix-ui/react-select": "^2.1.6",
|
|
39
|
+
"@radix-ui/react-separator": "^1.1.2",
|
|
40
|
+
"@radix-ui/react-slot": "^1.1.2",
|
|
41
|
+
"@radix-ui/react-switch": "^1.3.7",
|
|
42
|
+
"@radix-ui/react-tabs": "^1.1.3",
|
|
43
|
+
"@radix-ui/react-toast": "^1.2.6",
|
|
44
|
+
"@radix-ui/react-tooltip": "^1.1.8",
|
|
45
|
+
"@types/qrcode.react": "^3.0.0",
|
|
46
|
+
"bcryptjs": "^2.4.3",
|
|
47
|
+
"class-variance-authority": "^0.7.1",
|
|
48
|
+
"clsx": "^2.1.1",
|
|
49
|
+
"date-fns": "^4.4.0",
|
|
50
|
+
"drizzle-orm": "^0.36.4",
|
|
51
|
+
"html5-qrcode": "^2.3.8",
|
|
52
|
+
"lucide-react": "^0.468.0",
|
|
53
|
+
"nanoid": "^6.0.1",
|
|
54
|
+
"next": "^15.1.0",
|
|
55
|
+
"next-auth": "5.0.0-beta.25",
|
|
56
|
+
"pg": "^8.13.1",
|
|
57
|
+
"qrcode": "^1.5.4",
|
|
58
|
+
"qrcode.react": "^4.2.0",
|
|
59
|
+
"react": "^19.0.0",
|
|
60
|
+
"react-dom": "^19.0.0",
|
|
61
|
+
"react-hook-form": "^7.86.0",
|
|
62
|
+
"sonner": "^1.7.2",
|
|
63
|
+
"tailwind-merge": "^2.6.0",
|
|
64
|
+
"tailwindcss-animate": "^1.0.7",
|
|
65
|
+
"web-push": "^3.6.7",
|
|
66
|
+
"xlsx": "^0.18.5",
|
|
67
|
+
"zod": "^3.24.1"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/bcryptjs": "^2.4.6",
|
|
71
|
+
"@types/node": "^22.10.0",
|
|
72
|
+
"@types/pg": "^8.11.10",
|
|
73
|
+
"@types/qrcode": "^1.5.6",
|
|
74
|
+
"@types/react": "^19.0.0",
|
|
75
|
+
"@types/react-dom": "^19.0.0",
|
|
76
|
+
"@types/web-push": "^3.6.4",
|
|
77
|
+
"@vitest/coverage-v8": "^5.0.0",
|
|
78
|
+
"autoprefixer": "^10.4.20",
|
|
79
|
+
"drizzle-kit": "^0.28.1",
|
|
80
|
+
"eslint": "^9.16.0",
|
|
81
|
+
"eslint-config-next": "^15.1.0",
|
|
82
|
+
"postcss": "^8.4.49",
|
|
83
|
+
"tailwindcss": "^3.4.16",
|
|
84
|
+
"tsx": "^4.19.2",
|
|
85
|
+
"typescript": "^5.7.2",
|
|
86
|
+
"vitest": "^5.0.0"
|
|
87
|
+
},
|
|
88
|
+
"scripts": {
|
|
89
|
+
"dev": "next dev",
|
|
90
|
+
"build": "next build",
|
|
91
|
+
"start": "next start",
|
|
92
|
+
"lint": "next lint",
|
|
93
|
+
"db:generate": "drizzle-kit generate",
|
|
94
|
+
"db:migrate": "drizzle-kit migrate",
|
|
95
|
+
"db:push": "drizzle-kit push",
|
|
96
|
+
"db:seed": "tsx --env-file=.env.local src/lib/db/seed.ts",
|
|
97
|
+
"db:studio": "drizzle-kit studio",
|
|
98
|
+
"test": "vitest run",
|
|
99
|
+
"test:watch": "vitest",
|
|
100
|
+
"test:coverage": "vitest run --coverage"
|
|
101
|
+
}
|
|
6
102
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
asset_number,name,model,serial_number,category,manufacturer,department,location,risk_classification,criticality,purchase_date,purchase_cost,installation_date,warranty_start_date,warranty_end_date
|
|
2
|
+
HOSP-ICU-001,Mechanical Ventilator,Puritan Bennett 980,PB980-88412,Ventilator,Medtronic,Intensive Care Unit (ICU),ICU Bed 01,class_iib,high,2023-03-15,38500.00,2023-04-01,2023-04-01,2026-04-01
|
|
3
|
+
HOSP-ICU-002,Patient Monitor IntelliVue,MX750,MX75-99210,Patient Monitor,Philips Healthcare,Intensive Care Unit (ICU),ICU Bed 01,class_iib,high,2023-03-15,14500.00,2023-04-01,2023-04-01,2026-04-01
|
|
4
|
+
HOSP-ICU-003,Volumetric Infusion Pump,Infusomat Space,INF-43209,Infusion Pump,B. Braun,Intensive Care Unit (ICU),ICU Bed 02,class_iib,high,2023-05-10,3200.00,2023-05-20,2023-05-20,2025-05-20
|
|
5
|
+
HOSP-ICU-004,Syringe Driver Pump,Perfusor Space,PRF-88124,Syringe Pump,B. Braun,Intensive Care Unit (ICU),ICU Bed 02,class_iib,high,2023-05-10,2800.00,2023-05-20,2023-05-20,2025-05-20
|
|
6
|
+
HOSP-ICU-005,Blood Gas Analyzer,ABL90 FLEX,ABL-77401,Laboratory Analyzer,Radiometer,Intensive Care Unit (ICU),Stat Lab Station,class_iia,high,2023-02-18,22000.00,2023-03-01,2023-03-01,2026-03-01
|
|
7
|
+
HOSP-EM-001,Crash Cart Biphasic Defibrillator,R Series Plus,R-992481,Defibrillator,Zoll Medical,Emergency Department,Resuscitation Bay 1,class_iib,high,2023-01-10,16800.00,2023-01-25,2023-01-25,2026-01-25
|
|
8
|
+
HOSP-EM-002,12-Lead Diagnostic ECG,MAC 2000,MAC-55201,Electrocardiograph,GE Healthcare,Emergency Department,Triage Assessment Room,class_iia,high,2023-02-05,8500.00,2023-02-15,2023-02-15,2025-02-15
|
|
9
|
+
HOSP-EM-003,Vital Signs Spot Monitor,Connex Spot 7100,CS-11849,Vital Signs Monitor,Welch Allyn,Emergency Department,Observation Ward,class_iia,medium,2023-04-12,4200.00,2023-04-20,2023-04-20,2025-04-20
|
|
10
|
+
HOSP-EM-004,Point-of-Care Ultrasound,Sonosite Edge II,EDGE-44910,Ultrasound Scanner,Fujifilm Sonosite,Emergency Department,Trauma Bay 2,class_iia,medium,2023-06-01,29000.00,2023-06-15,2023-06-15,2026-06-15
|
|
11
|
+
HOSP-EM-005,Portable Emergency Suction,LSU 4000,LSU-33109,Suction Unit,Laerdal Medical,Emergency Department,Crash Cart 2,class_iia,high,2023-01-15,1850.00,2023-01-25,2023-01-25,2025-01-25
|
|
12
|
+
HOSP-OR-001,Anesthesia Workstation,Perseus A500,ANES-77192,Anesthesia Machine,Dräger,Surgery & Operating Theatre,OR Suite 1,class_iib,high,2022-11-20,68000.00,2022-12-05,2022-12-05,2025-12-05
|
|
13
|
+
HOSP-OR-002,Advanced Electrosurgical Unit,Valleylab FT10,ESU-88231,Electrosurgical Unit,Medtronic,Surgery & Operating Theatre,OR Suite 1,class_iib,high,2023-04-18,24500.00,2023-05-02,2023-05-02,2026-05-02
|
|
14
|
+
HOSP-OR-003,Electro-Hydraulic Operating Table,Alphamaquet 1150,OT-44109,Surgical Table,Maquet Getinge,Surgery & Operating Theatre,OR Suite 2,class_i,high,2022-10-15,42000.00,2022-11-01,2022-11-01,2025-11-01
|
|
15
|
+
HOSP-OR-004,Dual LED Surgical Ceiling Light,Polaris 600,LT-66281,Surgical Light,Dräger,Surgery & Operating Theatre,OR Suite 1,class_i,medium,2022-10-20,18000.00,2022-11-05,2022-11-05,2025-11-05
|
|
16
|
+
HOSP-OR-005,HD Laparoscopy Video Tower,IMAGE1 S,LAP-22019,Endoscopy System,Karl Storz,Surgery & Operating Theatre,OR Suite 2,class_iia,medium,2023-03-08,55000.00,2023-03-25,2023-03-25,2026-03-25
|
|
17
|
+
HOSP-RAD-001,Mobile Digital C-Arm System,Cios Alpha,CARM-99120,Fluoroscopy System,Siemens Healthineers,Radiology & Medical Imaging,Special Procedures Room,class_iib,high,2022-09-10,95000.00,2022-10-01,2022-10-01,2025-10-01
|
|
18
|
+
HOSP-RAD-002,Digital Radiography X-Ray System,Ysio Max,XRAY-33291,X-Ray System,Siemens Healthineers,Radiology & Medical Imaging,X-Ray Room 1,class_iib,high,2022-08-15,120000.00,2022-09-01,2022-09-01,2025-09-01
|
|
19
|
+
HOSP-RAD-003,Premium Diagnostic Ultrasound,EPIQ 7,US-77102,Ultrasound Scanner,Philips Healthcare,Radiology & Medical Imaging,Ultrasound Suite,class_iia,medium,2023-02-12,72000.00,2023-03-01,2023-03-01,2026-03-01
|
|
20
|
+
HOSP-RAD-004,Mobile Motorized X-Ray Unit,MobileDaRt Evolution,MOB-44918,Mobile X-Ray,Shimadzu,Radiology & Medical Imaging,Mobile Fleet Bay,class_iib,high,2023-05-20,62000.00,2023-06-05,2023-06-05,2026-06-05
|
|
21
|
+
HOSP-LAB-001,Automated Clinical Chemistry Analyzer,Cobas c 311,COB-66291,Chemistry Analyzer,Roche Diagnostics,Clinical Laboratory,Main Lab Bench A,class_iia,high,2023-01-15,48000.00,2023-02-01,2023-02-01,2026-02-01
|
|
22
|
+
HOSP-LAB-002,Automated CBC Blood Analyzer (Hematology),XN-550,HEM-88210,CBC Analyzer,Sysmex,Clinical Laboratory,Hematology Section,class_iia,high,2023-01-15,36000.00,2023-02-01,2023-02-01,2026-02-01
|
|
23
|
+
HOSP-LAB-003,Refrigerated Benchtop Centrifuge,5810 R,CENT-11029,Laboratory Centrifuge,Eppendorf,Clinical Laboratory,Processing Area,class_i,medium,2023-04-10,9200.00,2023-04-22,2023-04-22,2025-04-22
|
|
24
|
+
HOSP-LAB-004,Class II Biological Safety Cabinet,Herasafe 2030i,BSC-44192,Biosafety Cabinet,Thermo Fisher Scientific,Clinical Laboratory,Microbiology Room,class_iia,high,2023-03-01,13500.00,2023-03-15,2023-03-15,2026-03-15
|
|
25
|
+
HOSP-PED-001,Neonatal Intensive Care Incubator,Isolette 8000 Plus,INC-77109,Infant Incubator,Dräger,Pediatrics,NICU Bay 1,class_iib,high,2023-02-25,28000.00,2023-03-10,2023-03-10,2026-03-10
|
|
26
|
+
HOSP-PED-002,Infant Radiant Warmer,Giraffe Warmer,GWRM-33218,Infant Warmer,GE Healthcare,Pediatrics,Delivery Room 1,class_iib,high,2023-02-25,19500.00,2023-03-10,2023-03-10,2026-03-10
|
|
27
|
+
HOSP-PED-003,LED Phototherapy Treatment System,neoBLUE LED,PHT-55410,Phototherapy Unit,Natus Medical,Pediatrics,Nursery Room 2,class_iia,medium,2023-05-05,5800.00,2023-05-18,2023-05-18,2025-05-18
|
|
28
|
+
HOSP-PED-004,Infant Bubble CPAP System,Bubble CPAP BC100,CPAP-88129,Respiratory Support,Fisher & Paykel,Pediatrics,Step-down Nursery,class_iib,high,2023-06-12,8900.00,2023-06-25,2023-06-25,2026-06-25
|
|
29
|
+
HOSP-DENT-001,Comprehensive Dental Operating Unit,Intego Pro,DNT-11920,Dental Chair Unit,Dentsply Sirona,Dental Clinic,Operatory 1,class_iia,high,2023-01-20,32000.00,2023-02-10,2023-02-10,2026-02-10
|
|
30
|
+
HOSP-DENT-002,High-Frequency Intraoral X-Ray,Planmeca ProX,DXR-44129,Dental X-Ray,Planmeca,Dental Clinic,Operatory 1,class_iib,high,2023-01-20,11500.00,2023-02-10,2023-02-10,2026-02-10
|
|
31
|
+
HOSP-DENT-003,Class B Dental Vacuum Autoclave,Lisa 500,AUTO-77218,Steam Sterilizer,W&H Dental,Dental Clinic,Sterilization Room,class_iia,high,2023-02-15,8200.00,2023-03-01,2023-03-01,2025-03-01
|
|
32
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "BEMMS - Equipment Management",
|
|
3
|
+
"short_name": "BEMMS",
|
|
4
|
+
"description": "Biomedical Equipment Management and Maintenance System",
|
|
5
|
+
"start_url": "/dashboard",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#0a0a0a",
|
|
9
|
+
"theme_color": "#2563eb",
|
|
10
|
+
"orientation": "any",
|
|
11
|
+
"categories": ["medical", "business", "utilities"],
|
|
12
|
+
"icons": [
|
|
13
|
+
{
|
|
14
|
+
"src": "/icons/icon-192.png",
|
|
15
|
+
"sizes": "192x192",
|
|
16
|
+
"type": "image/png"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"src": "/icons/icon-512.png",
|
|
20
|
+
"sizes": "512x512",
|
|
21
|
+
"type": "image/png"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"src": "/icons/icon-maskable-512.png",
|
|
25
|
+
"sizes": "512x512",
|
|
26
|
+
"type": "image/png",
|
|
27
|
+
"purpose": "maskable"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
|
package/public/sw.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// BEMMS Service Worker
|
|
2
|
+
// Handles: caching, push notifications, offline shell
|
|
3
|
+
|
|
4
|
+
const CACHE_NAME = 'bemms-v3';
|
|
5
|
+
const STATIC_ASSETS = [
|
|
6
|
+
'/login',
|
|
7
|
+
'/dashboard',
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
// Install — pre-cache shell
|
|
11
|
+
self.addEventListener('install', (event) => {
|
|
12
|
+
event.waitUntil(
|
|
13
|
+
caches.open(CACHE_NAME).then((cache) => {
|
|
14
|
+
return cache.addAll(STATIC_ASSETS).catch(() => {
|
|
15
|
+
// Non-critical if some assets fail to cache
|
|
16
|
+
console.log('[SW] Some assets failed to cache, continuing...');
|
|
17
|
+
});
|
|
18
|
+
})
|
|
19
|
+
);
|
|
20
|
+
self.skipWaiting();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Activate — clean old caches
|
|
24
|
+
self.addEventListener('activate', (event) => {
|
|
25
|
+
event.waitUntil(
|
|
26
|
+
caches.keys().then((keys) => {
|
|
27
|
+
return Promise.all(
|
|
28
|
+
keys
|
|
29
|
+
.filter((key) => key !== CACHE_NAME)
|
|
30
|
+
.map((key) => caches.delete(key))
|
|
31
|
+
);
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
self.clients.claim();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Fetch — network-first for navigation/API, cache-first for static
|
|
38
|
+
self.addEventListener('fetch', (event) => {
|
|
39
|
+
const { request } = event;
|
|
40
|
+
const url = new URL(request.url);
|
|
41
|
+
|
|
42
|
+
// Skip non-GET, cross-origin, or localhost development
|
|
43
|
+
if (request.method !== 'GET' || url.origin !== self.location.origin) return;
|
|
44
|
+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') return;
|
|
45
|
+
|
|
46
|
+
// API/data requests — network only
|
|
47
|
+
if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/data/')) return;
|
|
48
|
+
|
|
49
|
+
// Static assets — cache first
|
|
50
|
+
if (
|
|
51
|
+
url.pathname.startsWith('/_next/static/') ||
|
|
52
|
+
url.pathname.startsWith('/icons/') ||
|
|
53
|
+
url.pathname.endsWith('.png') ||
|
|
54
|
+
url.pathname.endsWith('.ico')
|
|
55
|
+
) {
|
|
56
|
+
event.respondWith(
|
|
57
|
+
caches.match(request).then((cached) => {
|
|
58
|
+
if (cached) return cached;
|
|
59
|
+
return fetch(request).then((response) => {
|
|
60
|
+
if (response.ok) {
|
|
61
|
+
const clone = response.clone();
|
|
62
|
+
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
});
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Navigation — network first, fallback to cache
|
|
72
|
+
if (request.mode === 'navigate') {
|
|
73
|
+
event.respondWith(
|
|
74
|
+
fetch(request).catch(() => {
|
|
75
|
+
return caches.match(request).then((cached) => {
|
|
76
|
+
return cached || caches.match('/dashboard');
|
|
77
|
+
});
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Push — receive and show native notification
|
|
85
|
+
self.addEventListener('push', (event) => {
|
|
86
|
+
let data = { title: 'BEMMS', body: 'You have a new notification', url: '/notifications' };
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
if (event.data) {
|
|
90
|
+
data = event.data.json();
|
|
91
|
+
}
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (event.data) {
|
|
94
|
+
data.body = event.data.text();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Determine urgency from data
|
|
99
|
+
const isCritical = data.data?.priority === 'p1_critical' ||
|
|
100
|
+
data.data?.impact === 'patient_care_affected';
|
|
101
|
+
const isNewTicket = data.data?.type === 'new_ticket';
|
|
102
|
+
const isAccepted = data.data?.type === 'ticket_accepted';
|
|
103
|
+
|
|
104
|
+
const options = {
|
|
105
|
+
body: data.body,
|
|
106
|
+
icon: '/icons/icon-192.png',
|
|
107
|
+
badge: '/icons/icon-192.png',
|
|
108
|
+
tag: data.tag || 'bemms-notification',
|
|
109
|
+
renotify: true,
|
|
110
|
+
silent: false, // Ensure native device notification sound plays
|
|
111
|
+
data: {
|
|
112
|
+
url: data.url || '/notifications',
|
|
113
|
+
...data.data,
|
|
114
|
+
},
|
|
115
|
+
actions: data.actions || [],
|
|
116
|
+
vibrate: data.vibrate || (isCritical ? [200, 100, 200, 100, 200] : [100, 50, 100]),
|
|
117
|
+
requireInteraction: data.requireInteraction || isCritical || false,
|
|
118
|
+
// On Android, set notification color based on urgency
|
|
119
|
+
...(isCritical && { badge: '/icons/icon-192.png' }),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// For new tickets, add View action if not already present
|
|
123
|
+
if (isNewTicket && options.actions.length === 0) {
|
|
124
|
+
options.actions = [
|
|
125
|
+
{ action: 'view', title: '📋 View Ticket' },
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
event.waitUntil(
|
|
130
|
+
self.registration.showNotification(data.title, options)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Also notify all open app windows to refresh their notification count
|
|
134
|
+
event.waitUntil(
|
|
135
|
+
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
|
|
136
|
+
clients.forEach((client) => {
|
|
137
|
+
client.postMessage({
|
|
138
|
+
type: 'PUSH_RECEIVED',
|
|
139
|
+
notification: {
|
|
140
|
+
title: data.title,
|
|
141
|
+
body: data.body,
|
|
142
|
+
url: data.url,
|
|
143
|
+
data: data.data,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Notification click — open app to the right page
|
|
152
|
+
self.addEventListener('notificationclick', (event) => {
|
|
153
|
+
event.notification.close();
|
|
154
|
+
|
|
155
|
+
const notificationData = event.notification.data || {};
|
|
156
|
+
const action = event.action; // 'view', 'accept', or empty (body click)
|
|
157
|
+
let url = notificationData.url || '/notifications';
|
|
158
|
+
|
|
159
|
+
// If they clicked the View action or the body, go to the ticket
|
|
160
|
+
if (action === 'view' || !action) {
|
|
161
|
+
url = notificationData.url || '/notifications';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
event.waitUntil(
|
|
165
|
+
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clients) => {
|
|
166
|
+
// Try to focus an existing window
|
|
167
|
+
for (const client of clients) {
|
|
168
|
+
if (client.url.includes(self.location.origin) && 'focus' in client) {
|
|
169
|
+
client.navigate(url);
|
|
170
|
+
return client.focus();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Open new window
|
|
174
|
+
return self.clients.openWindow(url);
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
});
|