Skip to content

Commit e005f86

Browse files
committed
Bed management
1 parent a0247a5 commit e005f86

63 files changed

Lines changed: 1811 additions & 225 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api-gateway/src/app.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import healthRoutes from './routes/health';
44
import { patientAuthProxy } from './proxy/patient.auth.proxy';
55
import { patientDataProxy } from './proxy/patient.data.proxy';
66
import { staffAuthProxy } from './proxy/staff.auth.proxy';
7-
import staffDataRouter from './routes/staff.data.router';
7+
// import staffDataRouter from './routes/staff.data.router';
88
import { authenticate } from './middlewares/auth.middleware';
99
import { requirePatientSelf } from './middlewares/patient.guard';
1010
import { createProxyMiddleware } from 'http-proxy-middleware';
11-
11+
import { requirePermission } from './middlewares/rbac.middleware';
12+
import { staffDataProxy } from './proxy/staff.data.proxy';
1213
const app = express();
1314

1415
app.use(
@@ -39,8 +40,29 @@ app.use(
3940
);
4041
// STAFF auth
4142
app.use('/staff/public', staffAuthProxy);
42-
app.use('/staff', authenticate, staffDataRouter);
4343

44+
app.use('/staff', authenticate, staffDataProxy);
45+
46+
app.use('/admin', authenticate, staffDataProxy);
47+
48+
app.use(
49+
'/admissions',
50+
authenticate,
51+
(req: any, _res, next) => {
52+
if (req.user?.sub) req.headers['x-user-id'] = String(req.user.sub);
53+
54+
if (req.user?.role) req.headers['x-user-role'] = String(req.user.role);
55+
56+
if (req.user?.type) req.headers['x-user-type'] = String(req.user.type);
57+
58+
next();
59+
},
60+
createProxyMiddleware({
61+
target: process.env.PATIENT_SERVICE_URL,
62+
changeOrigin: true,
63+
pathRewrite: (path) => `/admissions${path}`,
64+
})
65+
);
4466
//Appointment routes (accessible by PATIENT + STAFF + ADMIN)
4567

4668
app.use(
Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { Response, NextFunction } from 'express';
22
import { AuthenticatedRequest } from './auth.middleware';
33

4-
// what actions exist in the system
5-
export type Permission = 'CREATE_STAFF' | 'UPDATE_STAFF' | 'DEACTIVATE_STAFF';
4+
export type Permission =
5+
| 'CREATE_STAFF'
6+
| 'UPDATE_STAFF'
7+
| 'DEACTIVATE_STAFF'
8+
| 'MANAGE_BEDS';
69

7-
// staff role → permissions
810
const STAFF_ROLE_PERMISSIONS: Record<string, Permission[]> = {
911
DOCTOR: [],
10-
NURSE: [],
12+
NURSE: ['MANAGE_BEDS'],
13+
RECEPTIONIST: ['MANAGE_BEDS'],
1114
};
1215

1316
export function requirePermission(permission: Permission) {
@@ -18,12 +21,22 @@ export function requirePermission(permission: Permission) {
1821
return res.status(401).json({ error: 'Unauthorized' });
1922
}
2023

21-
// ADMIN can do everything
24+
// ADMIN can do everything
2225
if (user.type === 'ADMIN') {
2326
return next();
2427
}
2528

26-
// ❌ STAFF cannot do admin actions
27-
return res.status(403).json({ error: 'Forbidden' });
29+
// Only STAFF can have role permissions
30+
if (user.type !== 'STAFF' || !user.role) {
31+
return res.status(403).json({ error: 'Forbidden' });
32+
}
33+
34+
const rolePermissions = STAFF_ROLE_PERMISSIONS[user.role] ?? [];
35+
36+
if (!rolePermissions.includes(permission)) {
37+
return res.status(403).json({ error: 'Forbidden' });
38+
}
39+
40+
next();
2841
};
2942
}

api-gateway/src/proxy/staff.data.proxy.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import { createProxyMiddleware } from 'http-proxy-middleware';
33
export const staffDataProxy = createProxyMiddleware({
44
target: 'http://localhost:3002',
55
changeOrigin: true,
6+
pathRewrite: (path, req: any) => {
7+
if (req.originalUrl.startsWith('/admin')) {
8+
return `/admin${path}`;
9+
}
610

7-
// /staff/* → /staff/*
8-
pathRewrite: (path) => `/staff${path}`,
11+
if (req.originalUrl.startsWith('/staff')) {
12+
return `/staff${path}`;
13+
}
14+
15+
return path;
16+
},
917
});

api-gateway/src/routes/staff.data.router.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { staffDataProxy } from '../proxy/staff.data.proxy';
44

55
const router = express.Router();
66

7-
// 🔒 ADMIN ONLY: create staff
7+
// 🔒 ADMIN ONLY
88
router.post('/', requirePermission('CREATE_STAFF'), staffDataProxy);
99

10-
// everything else just passes through
10+
// 🔒 BED MANAGEMENT
11+
router.use('/beds', requirePermission('MANAGE_BEDS'), staffDataProxy);
12+
13+
// everything else
1114
router.use(staffDataProxy);
1215

1316
export default router;

frontend/src/app/admin/dashboard/page.tsx

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,59 @@
11
import CreateStaffForm from "@/src/features/admin/components/CreateStaffForm";
2-
2+
import CreateWardForm from "@/src/features/admin/components/CreateWardForm";
3+
import CreateRoomForm from "@/src/features/admin/components/CreateRoomForm";
4+
import CreateBedForm from "@/src/features/admin/components/CreateBedForm";
35

46
export default function AdminDashboard() {
57
return (
6-
<div>
7-
<h1 className="text-2xl font-bold">
8-
Admin Dashboard
9-
</h1>
8+
<div className="min-h-screen bg-gray-50 px-4 py-12 sm:px-6">
9+
<div className="max-w-4xl mx-auto space-y-12"> {/* Narrower container for better readability */}
10+
11+
{/* Page Header */}
12+
<header className="border-b pb-6">
13+
<h1 className="text-3xl font-bold text-gray-900">Admin Management</h1>
14+
<p className="text-gray-500">Add new staff members and hospital infrastructure</p>
15+
</header>
16+
17+
{/* Form 1: Staff */}
18+
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
19+
<div className="bg-blue-50 px-6 py-4 border-b border-blue-100">
20+
<h2 className="text-lg font-bold text-blue-900">1. Staff Registration</h2>
21+
</div>
22+
<div className="p-8">
23+
<CreateStaffForm />
24+
</div>
25+
</section>
26+
27+
{/* Form 2: Wards */}
28+
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
29+
<div className="bg-green-50 px-6 py-4 border-b border-green-100">
30+
<h2 className="text-lg font-bold text-green-900">2. Ward Management</h2>
31+
</div>
32+
<div className="p-8">
33+
<CreateWardForm />
34+
</div>
35+
</section>
36+
37+
{/* Form 3: Rooms */}
38+
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
39+
<div className="bg-purple-50 px-6 py-4 border-b border-purple-100">
40+
<h2 className="text-lg font-bold text-purple-900">3. Room Assignment</h2>
41+
</div>
42+
<div className="p-8">
43+
<CreateRoomForm />
44+
</div>
45+
</section>
1046

11-
<p>Welcome Admin</p>
47+
{/* Form 4: Beds */}
48+
<section className="bg-white shadow-md border rounded-xl overflow-hidden">
49+
<div className="bg-amber-50 px-6 py-4 border-b border-amber-100">
50+
<h2 className="text-lg font-bold text-amber-900">4. Bed Setup</h2>
51+
</div>
52+
<div className="p-8">
53+
<CreateBedForm />
54+
</div>
55+
</section>
1256

13-
<div className="space-y-8" >
14-
<CreateStaffForm />
1557
</div>
1658
</div>
1759
);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client'
2+
3+
import AdmissionQueue from '@/src/features/admissions/components/AdmissionQueue'
4+
5+
export default function StaffAdmissionsPage() {
6+
7+
return (
8+
<div className="min-h-screen bg-gray-50 p-8">
9+
10+
<h1 className="text-2xl font-semibold mb-6">
11+
Admissions
12+
</h1>
13+
14+
<AdmissionQueue />
15+
16+
</div>
17+
)
18+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import BedsBoard from '@/src/features/beds/components/BedsBoard'
4+
import CreateWardForm from '@/src/features/admin/components/CreateWardForm'
5+
import CreateRoomForm from '@/src/features/admin/components/CreateRoomForm'
6+
import CreateBedForm from '@/src/features/admin/components/CreateBedForm'
7+
8+
export default function StaffBedsPage() {
9+
10+
return (
11+
12+
<div className="min-h-screen p-8 bg-gray-50 space-y-6">
13+
14+
<h1 className="text-2xl font-semibold">
15+
Bed Management
16+
</h1>
17+
18+
<BedsBoard />
19+
20+
</div>
21+
22+
)
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import DischargeRequests from "@/src/features/admissions/components/DischargeRequests";
2+
3+
export default function DischargeRequestsPage(){
4+
return(
5+
<div className=" min-h-screen bg-gray-50 p-8 space-y-8" >
6+
<h1 className="text-2xl font-semibold" >
7+
Discharge Requests
8+
</h1>
9+
<div>
10+
<DischargeRequests />
11+
</div>
12+
</div>
13+
)
14+
}

frontend/src/app/staff/dashboard/layout.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ export default function StaffLayout({
7070
label="Manage Availability"
7171
active={pathname === '/staff/dashboard/availability'}
7272
/>
73+
<SidebarLink
74+
href='/staff/dashboard/beds'
75+
label='Beds'
76+
active={pathname === '/staff/dashboard/beds'}
77+
/>
78+
<SidebarLink
79+
href='/staff/dashboard/admissions'
80+
label='Admissions'
81+
active={pathname === '/staff/dashboard/admissions'}
82+
/>
83+
<SidebarLink
84+
href='/staff/dashboard/discharge'
85+
label='Discharge'
86+
active={pathname === '/staff/dashboard/Discharge'}
87+
/>
88+
<SidebarLink
89+
href='/staff/dashboard/patients'
90+
label='Patients'
91+
active={pathname === '/staff/dashboard/patients'}
92+
/>
7393
</nav>
7494
</div>
7595

frontend/src/app/staff/dashboard/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useStaffAuth } from '../../../staff/auth/staff.auth.provider';
44
import { useRouter } from 'next/navigation';
55
import { useEffect } from 'react';
6+
import Link from 'next/link';
67

78
export default function StaffDashboardPage() {
89
const { auth, logout } = useStaffAuth();
@@ -48,6 +49,8 @@ export default function StaffDashboardPage() {
4849
<p><strong>Role:</strong> {auth.staff?.role}</p>
4950
<p><strong>Job Title:</strong> {auth.staff?.job_title}</p>
5051
</div>
52+
53+
5154
</div>
5255
);
5356
}

0 commit comments

Comments
 (0)