create-steve-rogers 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/apps/HMSR/backend/.env.example +6 -0
- package/apps/HMSR/backend/config/db.js +14 -0
- package/apps/HMSR/backend/database/schema.sql +55 -0
- package/apps/HMSR/backend/database/seed.js +53 -0
- package/apps/HMSR/backend/middleware/auth.js +26 -0
- package/apps/HMSR/backend/package-lock.json +1130 -0
- package/apps/HMSR/backend/package.json +19 -0
- package/apps/HMSR/backend/routes/appointments.js +157 -0
- package/apps/HMSR/backend/routes/auth.js +115 -0
- package/apps/HMSR/backend/routes/medicalReports.js +126 -0
- package/apps/HMSR/backend/routes/patients.js +103 -0
- package/apps/HMSR/backend/routes/reports.js +60 -0
- package/apps/HMSR/backend/server.js +38 -0
- package/apps/HMSR/frontend/package-lock.json +17217 -0
- package/apps/HMSR/frontend/package.json +23 -0
- package/apps/HMSR/frontend/public/index.html +17 -0
- package/apps/HMSR/frontend/src/App.js +91 -0
- package/apps/HMSR/frontend/src/components/Layout.js +58 -0
- package/apps/HMSR/frontend/src/components/PrivateRoute.js +25 -0
- package/apps/HMSR/frontend/src/context/AuthContext.js +54 -0
- package/apps/HMSR/frontend/src/index.css +581 -0
- package/apps/HMSR/frontend/src/index.js +17 -0
- package/apps/HMSR/frontend/src/pages/Appointments.js +250 -0
- package/apps/HMSR/frontend/src/pages/Dashboard.js +116 -0
- package/apps/HMSR/frontend/src/pages/Login.js +73 -0
- package/apps/HMSR/frontend/src/pages/MedicalReports.js +217 -0
- package/apps/HMSR/frontend/src/pages/Patients.js +196 -0
- package/apps/HMSR/frontend/src/pages/Register.js +98 -0
- package/apps/HMSR/frontend/src/pages/Reports.js +170 -0
- package/apps/HMSR/frontend/src/services/api.js +15 -0
- package/apps/config.js +8 -0
- package/exclude.txt +1 -0
- package/index.js +55 -0
- package/package.json +14 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hms-frontend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"axios": "^1.7.7",
|
|
7
|
+
"react": "^18.3.1",
|
|
8
|
+
"react-dom": "^18.3.1",
|
|
9
|
+
"react-router-dom": "^6.26.2",
|
|
10
|
+
"react-scripts": "5.0.1"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "react-scripts start",
|
|
14
|
+
"build": "react-scripts build",
|
|
15
|
+
"test": "react-scripts test",
|
|
16
|
+
"eject": "react-scripts eject"
|
|
17
|
+
},
|
|
18
|
+
"proxy": "http://localhost:5000",
|
|
19
|
+
"browserslist": {
|
|
20
|
+
"production": [">0.2%", "not dead", "not op_mini all"],
|
|
21
|
+
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="theme-color" content="#0d6e6e" />
|
|
7
|
+
<meta name="description" content="King Faisal Hospital Rwanda - Appointment & Medical Report System" />
|
|
8
|
+
<title>KFH Rwanda HMS</title>
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&display=swap" rel="stylesheet" />
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
15
|
+
<div id="root"></div>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
2
|
+
import { useAuth } from './context/AuthContext';
|
|
3
|
+
import PrivateRoute from './components/PrivateRoute';
|
|
4
|
+
import Layout from './components/Layout';
|
|
5
|
+
import Login from './pages/Login';
|
|
6
|
+
import Register from './pages/Register';
|
|
7
|
+
import Dashboard from './pages/Dashboard';
|
|
8
|
+
import Patients from './pages/Patients';
|
|
9
|
+
import Appointments from './pages/Appointments';
|
|
10
|
+
import MedicalReports from './pages/MedicalReports';
|
|
11
|
+
import Reports from './pages/Reports';
|
|
12
|
+
|
|
13
|
+
function App() {
|
|
14
|
+
const { user, loading } = useAuth();
|
|
15
|
+
|
|
16
|
+
if (loading) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="loading-screen">
|
|
19
|
+
<div className="spinner" />
|
|
20
|
+
<p>Loading...</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Routes>
|
|
27
|
+
<Route
|
|
28
|
+
path="/login"
|
|
29
|
+
element={user ? <Navigate to="/dashboard" replace /> : <Login />}
|
|
30
|
+
/>
|
|
31
|
+
<Route
|
|
32
|
+
path="/register"
|
|
33
|
+
element={user ? <Navigate to="/dashboard" replace /> : <Register />}
|
|
34
|
+
/>
|
|
35
|
+
<Route
|
|
36
|
+
path="/dashboard"
|
|
37
|
+
element={
|
|
38
|
+
<PrivateRoute>
|
|
39
|
+
<Layout>
|
|
40
|
+
<Dashboard />
|
|
41
|
+
</Layout>
|
|
42
|
+
</PrivateRoute>
|
|
43
|
+
}
|
|
44
|
+
/>
|
|
45
|
+
<Route
|
|
46
|
+
path="/patients"
|
|
47
|
+
element={
|
|
48
|
+
<PrivateRoute roles={['receptionist']}>
|
|
49
|
+
<Layout>
|
|
50
|
+
<Patients />
|
|
51
|
+
</Layout>
|
|
52
|
+
</PrivateRoute>
|
|
53
|
+
}
|
|
54
|
+
/>
|
|
55
|
+
<Route
|
|
56
|
+
path="/appointments"
|
|
57
|
+
element={
|
|
58
|
+
<PrivateRoute roles={['receptionist', 'doctor']}>
|
|
59
|
+
<Layout>
|
|
60
|
+
<Appointments />
|
|
61
|
+
</Layout>
|
|
62
|
+
</PrivateRoute>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
<Route
|
|
66
|
+
path="/medical-reports"
|
|
67
|
+
element={
|
|
68
|
+
<PrivateRoute roles={['doctor']}>
|
|
69
|
+
<Layout>
|
|
70
|
+
<MedicalReports />
|
|
71
|
+
</Layout>
|
|
72
|
+
</PrivateRoute>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
<Route
|
|
76
|
+
path="/reports"
|
|
77
|
+
element={
|
|
78
|
+
<PrivateRoute roles={['receptionist', 'doctor']}>
|
|
79
|
+
<Layout>
|
|
80
|
+
<Reports />
|
|
81
|
+
</Layout>
|
|
82
|
+
</PrivateRoute>
|
|
83
|
+
}
|
|
84
|
+
/>
|
|
85
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
86
|
+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
87
|
+
</Routes>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export default App;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
2
|
+
import { useAuth } from '../context/AuthContext';
|
|
3
|
+
|
|
4
|
+
export default function Layout({ children }) {
|
|
5
|
+
const { user, logout } = useAuth();
|
|
6
|
+
const location = useLocation();
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
|
|
9
|
+
const handleLogout = () => {
|
|
10
|
+
logout();
|
|
11
|
+
navigate('/login');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const navItems = [
|
|
15
|
+
{ path: '/dashboard', label: 'Dashboard', roles: ['receptionist', 'doctor'] },
|
|
16
|
+
{ path: '/patients', label: 'Patients', roles: ['receptionist'] },
|
|
17
|
+
{ path: '/appointments', label: 'Appointments', roles: ['receptionist', 'doctor'] },
|
|
18
|
+
{ path: '/medical-reports', label: 'Medical Reports', roles: ['doctor'] },
|
|
19
|
+
{ path: '/reports', label: 'Reports', roles: ['receptionist', 'doctor'] },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const visibleNav = navItems.filter((item) => item.roles.includes(user?.role));
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="app-layout">
|
|
26
|
+
<aside className="sidebar">
|
|
27
|
+
<div className="sidebar-brand">
|
|
28
|
+
<span className="brand-icon">+</span>
|
|
29
|
+
<div>
|
|
30
|
+
<h1>KFH Rwanda</h1>
|
|
31
|
+
<p>Hospital Management</p>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<nav className="sidebar-nav">
|
|
35
|
+
{visibleNav.map((item) => (
|
|
36
|
+
<Link
|
|
37
|
+
key={item.path}
|
|
38
|
+
to={item.path}
|
|
39
|
+
className={location.pathname === item.path ? 'active' : ''}
|
|
40
|
+
>
|
|
41
|
+
{item.label}
|
|
42
|
+
</Link>
|
|
43
|
+
))}
|
|
44
|
+
</nav>
|
|
45
|
+
<div className="sidebar-footer">
|
|
46
|
+
<div className="user-info">
|
|
47
|
+
<strong>{user?.full_name}</strong>
|
|
48
|
+
<span className={`role-badge role-${user?.role}`}>{user?.role}</span>
|
|
49
|
+
</div>
|
|
50
|
+
<button type="button" className="btn btn-outline btn-sm" onClick={handleLogout}>
|
|
51
|
+
Logout
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</aside>
|
|
55
|
+
<main className="main-content">{children}</main>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Navigate } from 'react-router-dom';
|
|
2
|
+
import { useAuth } from '../context/AuthContext';
|
|
3
|
+
|
|
4
|
+
export default function PrivateRoute({ children, roles }) {
|
|
5
|
+
const { user, loading } = useAuth();
|
|
6
|
+
|
|
7
|
+
if (loading) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="loading-screen">
|
|
10
|
+
<div className="spinner" />
|
|
11
|
+
<p>Loading...</p>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!user) {
|
|
17
|
+
return <Navigate to="/login" replace />;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (roles && !roles.includes(user.role)) {
|
|
21
|
+
return <Navigate to="/dashboard" replace />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return children;
|
|
25
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
2
|
+
import api from '../services/api';
|
|
3
|
+
|
|
4
|
+
const AuthContext = createContext(null);
|
|
5
|
+
|
|
6
|
+
export function AuthProvider({ children }) {
|
|
7
|
+
const [user, setUser] = useState(null);
|
|
8
|
+
const [loading, setLoading] = useState(true);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const token = localStorage.getItem('token');
|
|
12
|
+
const savedUser = localStorage.getItem('user');
|
|
13
|
+
if (token && savedUser) {
|
|
14
|
+
setUser(JSON.parse(savedUser));
|
|
15
|
+
api
|
|
16
|
+
.get('/auth/me')
|
|
17
|
+
.then((res) => {
|
|
18
|
+
setUser(res.data);
|
|
19
|
+
localStorage.setItem('user', JSON.stringify(res.data));
|
|
20
|
+
})
|
|
21
|
+
.catch(() => logout())
|
|
22
|
+
.finally(() => setLoading(false));
|
|
23
|
+
} else {
|
|
24
|
+
setLoading(false);
|
|
25
|
+
}
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const login = async (email, password) => {
|
|
29
|
+
const res = await api.post('/auth/login', { email, password });
|
|
30
|
+
localStorage.setItem('token', res.data.token);
|
|
31
|
+
localStorage.setItem('user', JSON.stringify(res.data.user));
|
|
32
|
+
setUser(res.data.user);
|
|
33
|
+
return res.data;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const register = async (data) => {
|
|
37
|
+
const res = await api.post('/auth/register', data);
|
|
38
|
+
return res.data;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const logout = () => {
|
|
42
|
+
localStorage.removeItem('token');
|
|
43
|
+
localStorage.removeItem('user');
|
|
44
|
+
setUser(null);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
|
|
49
|
+
{children}
|
|
50
|
+
</AuthContext.Provider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const useAuth = () => useContext(AuthContext);
|