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.
Files changed (34) hide show
  1. package/apps/HMSR/backend/.env.example +6 -0
  2. package/apps/HMSR/backend/config/db.js +14 -0
  3. package/apps/HMSR/backend/database/schema.sql +55 -0
  4. package/apps/HMSR/backend/database/seed.js +53 -0
  5. package/apps/HMSR/backend/middleware/auth.js +26 -0
  6. package/apps/HMSR/backend/package-lock.json +1130 -0
  7. package/apps/HMSR/backend/package.json +19 -0
  8. package/apps/HMSR/backend/routes/appointments.js +157 -0
  9. package/apps/HMSR/backend/routes/auth.js +115 -0
  10. package/apps/HMSR/backend/routes/medicalReports.js +126 -0
  11. package/apps/HMSR/backend/routes/patients.js +103 -0
  12. package/apps/HMSR/backend/routes/reports.js +60 -0
  13. package/apps/HMSR/backend/server.js +38 -0
  14. package/apps/HMSR/frontend/package-lock.json +17217 -0
  15. package/apps/HMSR/frontend/package.json +23 -0
  16. package/apps/HMSR/frontend/public/index.html +17 -0
  17. package/apps/HMSR/frontend/src/App.js +91 -0
  18. package/apps/HMSR/frontend/src/components/Layout.js +58 -0
  19. package/apps/HMSR/frontend/src/components/PrivateRoute.js +25 -0
  20. package/apps/HMSR/frontend/src/context/AuthContext.js +54 -0
  21. package/apps/HMSR/frontend/src/index.css +581 -0
  22. package/apps/HMSR/frontend/src/index.js +17 -0
  23. package/apps/HMSR/frontend/src/pages/Appointments.js +250 -0
  24. package/apps/HMSR/frontend/src/pages/Dashboard.js +116 -0
  25. package/apps/HMSR/frontend/src/pages/Login.js +73 -0
  26. package/apps/HMSR/frontend/src/pages/MedicalReports.js +217 -0
  27. package/apps/HMSR/frontend/src/pages/Patients.js +196 -0
  28. package/apps/HMSR/frontend/src/pages/Register.js +98 -0
  29. package/apps/HMSR/frontend/src/pages/Reports.js +170 -0
  30. package/apps/HMSR/frontend/src/services/api.js +15 -0
  31. package/apps/config.js +8 -0
  32. package/exclude.txt +1 -0
  33. package/index.js +55 -0
  34. package/package.json +14 -0
@@ -0,0 +1,250 @@
1
+ import { useEffect, useState } from 'react';
2
+ import api from '../services/api';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ const emptyForm = {
6
+ patient_id: '',
7
+ doctor_id: '',
8
+ appointment_date: '',
9
+ appointment_time: '',
10
+ status: 'scheduled',
11
+ };
12
+
13
+ export default function Appointments() {
14
+ const { user } = useAuth();
15
+ const isReceptionist = user?.role === 'receptionist';
16
+ const [appointments, setAppointments] = useState([]);
17
+ const [patients, setPatients] = useState([]);
18
+ const [doctors, setDoctors] = useState([]);
19
+ const [form, setForm] = useState(emptyForm);
20
+ const [editingId, setEditingId] = useState(null);
21
+ const [showForm, setShowForm] = useState(false);
22
+ const [error, setError] = useState('');
23
+ const [loading, setLoading] = useState(true);
24
+
25
+ const fetchData = async () => {
26
+ try {
27
+ const apptRes = await api.get('/appointments');
28
+ setAppointments(apptRes.data);
29
+
30
+ if (isReceptionist) {
31
+ const [patRes, docRes] = await Promise.all([
32
+ api.get('/patients'),
33
+ api.get('/auth/doctors'),
34
+ ]);
35
+ setPatients(patRes.data);
36
+ setDoctors(docRes.data);
37
+ }
38
+ } catch (err) {
39
+ setError(err.response?.data?.message || 'Failed to load appointments.');
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ };
44
+
45
+ useEffect(() => {
46
+ fetchData();
47
+ }, [isReceptionist]);
48
+
49
+ const handleChange = (e) => {
50
+ setForm({ ...form, [e.target.name]: e.target.value });
51
+ };
52
+
53
+ const handleSubmit = async (e) => {
54
+ e.preventDefault();
55
+ setError('');
56
+ try {
57
+ if (editingId) {
58
+ await api.put(`/appointments/${editingId}`, form);
59
+ } else {
60
+ await api.post('/appointments', form);
61
+ }
62
+ setForm(emptyForm);
63
+ setEditingId(null);
64
+ setShowForm(false);
65
+ fetchData();
66
+ } catch (err) {
67
+ setError(err.response?.data?.message || 'Operation failed.');
68
+ }
69
+ };
70
+
71
+ const handleEdit = (appt) => {
72
+ setForm({
73
+ patient_id: appt.patient_id,
74
+ doctor_id: appt.doctor_id,
75
+ appointment_date: appt.appointment_date?.split('T')[0],
76
+ appointment_time: appt.appointment_time?.slice(0, 5),
77
+ status: appt.status,
78
+ });
79
+ setEditingId(appt.id);
80
+ setShowForm(true);
81
+ };
82
+
83
+ const handleCancel = async (id) => {
84
+ if (!window.confirm('Cancel this appointment?')) return;
85
+ try {
86
+ await api.patch(`/appointments/${id}/cancel`);
87
+ fetchData();
88
+ } catch (err) {
89
+ setError(err.response?.data?.message || 'Cancel failed.');
90
+ }
91
+ };
92
+
93
+ const handleDelete = async (id) => {
94
+ if (!window.confirm('Delete this appointment?')) return;
95
+ try {
96
+ await api.delete(`/appointments/${id}`);
97
+ fetchData();
98
+ } catch (err) {
99
+ setError(err.response?.data?.message || 'Delete failed.');
100
+ }
101
+ };
102
+
103
+ const statusClass = (status) => `status-badge status-${status}`;
104
+
105
+ return (
106
+ <div className="page">
107
+ <header className="page-header">
108
+ <div>
109
+ <h1>Appointment Management</h1>
110
+ <p>{isReceptionist ? 'Schedule and manage patient appointments' : 'Your assigned appointments'}</p>
111
+ </div>
112
+ {isReceptionist && (
113
+ <button
114
+ type="button"
115
+ className="btn btn-primary"
116
+ onClick={() => {
117
+ setShowForm(!showForm);
118
+ if (showForm) {
119
+ setForm(emptyForm);
120
+ setEditingId(null);
121
+ }
122
+ }}
123
+ >
124
+ {showForm ? 'Close Form' : '+ New Appointment'}
125
+ </button>
126
+ )}
127
+ </header>
128
+
129
+ {error && <div className="alert alert-error">{error}</div>}
130
+
131
+ {showForm && isReceptionist && (
132
+ <div className="card form-card">
133
+ <h2>{editingId ? 'Update Appointment' : 'Create Appointment'}</h2>
134
+ <form onSubmit={handleSubmit} className="form-grid">
135
+ <div className="form-group">
136
+ <label>Patient</label>
137
+ <select name="patient_id" value={form.patient_id} onChange={handleChange} required>
138
+ <option value="">Select patient</option>
139
+ {patients.map((p) => (
140
+ <option key={p.id} value={p.id}>
141
+ {p.full_name}
142
+ </option>
143
+ ))}
144
+ </select>
145
+ </div>
146
+ <div className="form-group">
147
+ <label>Doctor</label>
148
+ <select name="doctor_id" value={form.doctor_id} onChange={handleChange} required>
149
+ <option value="">Select doctor</option>
150
+ {doctors.map((d) => (
151
+ <option key={d.id} value={d.id}>
152
+ {d.full_name}
153
+ </option>
154
+ ))}
155
+ </select>
156
+ </div>
157
+ <div className="form-group">
158
+ <label>Date</label>
159
+ <input
160
+ name="appointment_date"
161
+ type="date"
162
+ value={form.appointment_date}
163
+ onChange={handleChange}
164
+ required
165
+ />
166
+ </div>
167
+ <div className="form-group">
168
+ <label>Time</label>
169
+ <input
170
+ name="appointment_time"
171
+ type="time"
172
+ value={form.appointment_time}
173
+ onChange={handleChange}
174
+ required
175
+ />
176
+ </div>
177
+ {editingId && (
178
+ <div className="form-group">
179
+ <label>Status</label>
180
+ <select name="status" value={form.status} onChange={handleChange}>
181
+ <option value="scheduled">Scheduled</option>
182
+ <option value="completed">Completed</option>
183
+ <option value="cancelled">Cancelled</option>
184
+ </select>
185
+ </div>
186
+ )}
187
+ <div className="form-actions full-width">
188
+ <button type="submit" className="btn btn-primary">
189
+ {editingId ? 'Update' : 'Create'} Appointment
190
+ </button>
191
+ </div>
192
+ </form>
193
+ </div>
194
+ )}
195
+
196
+ <div className="card">
197
+ {loading ? (
198
+ <p className="text-muted">Loading...</p>
199
+ ) : appointments.length === 0 ? (
200
+ <p className="text-muted">No appointments found.</p>
201
+ ) : (
202
+ <div className="table-responsive">
203
+ <table>
204
+ <thead>
205
+ <tr>
206
+ <th>Patient</th>
207
+ <th>Doctor</th>
208
+ <th>Date</th>
209
+ <th>Time</th>
210
+ <th>Status</th>
211
+ {isReceptionist && <th>Actions</th>}
212
+ </tr>
213
+ </thead>
214
+ <tbody>
215
+ {appointments.map((a) => (
216
+ <tr key={a.id}>
217
+ <td>{a.patient_name}</td>
218
+ <td>{a.doctor_name}</td>
219
+ <td>{a.appointment_date?.split('T')[0]}</td>
220
+ <td>{a.appointment_time?.slice(0, 5)}</td>
221
+ <td>
222
+ <span className={statusClass(a.status)}>{a.status}</span>
223
+ </td>
224
+ {isReceptionist && (
225
+ <td className="actions">
226
+ {a.status !== 'cancelled' && (
227
+ <button type="button" className="btn btn-sm btn-outline" onClick={() => handleEdit(a)}>
228
+ Edit
229
+ </button>
230
+ )}
231
+ {a.status === 'scheduled' && (
232
+ <button type="button" className="btn btn-sm btn-warning" onClick={() => handleCancel(a.id)}>
233
+ Cancel
234
+ </button>
235
+ )}
236
+ <button type="button" className="btn btn-sm btn-danger" onClick={() => handleDelete(a.id)}>
237
+ Delete
238
+ </button>
239
+ </td>
240
+ )}
241
+ </tr>
242
+ ))}
243
+ </tbody>
244
+ </table>
245
+ </div>
246
+ )}
247
+ </div>
248
+ </div>
249
+ );
250
+ }
@@ -0,0 +1,116 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Link } from 'react-router-dom';
3
+ import api from '../services/api';
4
+ import { useAuth } from '../context/AuthContext';
5
+
6
+ export default function Dashboard() {
7
+ const { user } = useAuth();
8
+ const [stats, setStats] = useState({
9
+ patients: 0,
10
+ appointments: 0,
11
+ completed: 0,
12
+ reports: 0,
13
+ });
14
+
15
+ useEffect(() => {
16
+ const today = new Date().toISOString().split('T')[0];
17
+ const startOfMonth = new Date();
18
+ startOfMonth.setDate(1);
19
+ const start = startOfMonth.toISOString().split('T')[0];
20
+
21
+ const fetchStats = async () => {
22
+ try {
23
+ const promises = [
24
+ user.role === 'receptionist'
25
+ ? api.get('/patients').then((r) => r.data.length)
26
+ : Promise.resolve(0),
27
+ api.get('/appointments').then((r) => r.data),
28
+ api.get('/reports/summary', { params: { start_date: start, end_date: today } }),
29
+ user.role === 'doctor'
30
+ ? api.get('/medical-reports').then((r) => r.data.length)
31
+ : Promise.resolve(0),
32
+ ];
33
+
34
+ const [patientCount, appointments, reportData, reportCount] =
35
+ await Promise.all(promises);
36
+
37
+ setStats({
38
+ patients: patientCount,
39
+ appointments: appointments.length,
40
+ completed: reportData.data.total_completed,
41
+ reports: user.role === 'doctor' ? reportCount : reportData.data.patients_attended?.length || 0,
42
+ });
43
+ } catch {
44
+ /* stats optional */
45
+ }
46
+ };
47
+
48
+ fetchStats();
49
+ }, [user.role]);
50
+
51
+ return (
52
+ <div className="page">
53
+ <header className="page-header">
54
+ <div>
55
+ <h1>Dashboard</h1>
56
+ <p>Welcome back, {user?.full_name}</p>
57
+ </div>
58
+ </header>
59
+
60
+ <div className="stats-grid">
61
+ {user.role === 'receptionist' && (
62
+ <div className="stat-card">
63
+ <span className="stat-label">Registered Patients</span>
64
+ <span className="stat-value">{stats.patients}</span>
65
+ </div>
66
+ )}
67
+ <div className="stat-card">
68
+ <span className="stat-label">Appointments</span>
69
+ <span className="stat-value">{stats.appointments}</span>
70
+ </div>
71
+ <div className="stat-card">
72
+ <span className="stat-label">Completed (This Month)</span>
73
+ <span className="stat-value">{stats.completed}</span>
74
+ </div>
75
+ <div className="stat-card">
76
+ <span className="stat-label">
77
+ {user.role === 'doctor' ? 'Medical Reports' : 'Patients Attended'}
78
+ </span>
79
+ <span className="stat-value">{stats.reports}</span>
80
+ </div>
81
+ </div>
82
+
83
+ <div className="quick-actions">
84
+ <h2>Quick Actions</h2>
85
+ <div className="action-grid">
86
+ {user.role === 'receptionist' && (
87
+ <>
88
+ <Link to="/patients" className="action-card">
89
+ <h3>Manage Patients</h3>
90
+ <p>Add, update, or view patient records</p>
91
+ </Link>
92
+ <Link to="/appointments" className="action-card">
93
+ <h3>Appointments</h3>
94
+ <p>Schedule and manage appointments</p>
95
+ </Link>
96
+ </>
97
+ )}
98
+ {user.role === 'doctor' && (
99
+ <Link to="/medical-reports" className="action-card">
100
+ <h3>Medical Reports</h3>
101
+ <p>View appointments and save diagnoses</p>
102
+ </Link>
103
+ )}
104
+ <Link to="/appointments" className="action-card">
105
+ <h3>View Appointments</h3>
106
+ <p>See all scheduled visits</p>
107
+ </Link>
108
+ <Link to="/reports" className="action-card">
109
+ <h3>Generate Reports</h3>
110
+ <p>Filter by date and view statistics</p>
111
+ </Link>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
@@ -0,0 +1,73 @@
1
+ import { useState } from 'react';
2
+ import { Link, useNavigate } from 'react-router-dom';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ export default function Login() {
6
+ const [email, setEmail] = useState('');
7
+ const [password, setPassword] = useState('');
8
+ const [error, setError] = useState('');
9
+ const [loading, setLoading] = useState(false);
10
+ const { login } = useAuth();
11
+ const navigate = useNavigate();
12
+
13
+ const handleSubmit = async (e) => {
14
+ e.preventDefault();
15
+ setError('');
16
+ setLoading(true);
17
+ try {
18
+ await login(email, password);
19
+ navigate('/dashboard');
20
+ } catch (err) {
21
+ setError(err.response?.data?.message || 'Login failed. Please try again.');
22
+ } finally {
23
+ setLoading(false);
24
+ }
25
+ };
26
+
27
+ return (
28
+ <div className="auth-page">
29
+ <div className="auth-card">
30
+ <div className="auth-header">
31
+ <span className="brand-icon large">+</span>
32
+ <h1>King Faisal Hospital Rwanda</h1>
33
+ <p>Appointment & Medical Report Management</p>
34
+ </div>
35
+ <form onSubmit={handleSubmit}>
36
+ {error && <div className="alert alert-error">{error}</div>}
37
+ <div className="form-group">
38
+ <label htmlFor="email">Email</label>
39
+ <input
40
+ id="email"
41
+ type="email"
42
+ value={email}
43
+ onChange={(e) => setEmail(e.target.value)}
44
+ placeholder="reception@kfh.rw"
45
+ required
46
+ />
47
+ </div>
48
+ <div className="form-group">
49
+ <label htmlFor="password">Password</label>
50
+ <input
51
+ id="password"
52
+ type="password"
53
+ value={password}
54
+ onChange={(e) => setPassword(e.target.value)}
55
+ required
56
+ />
57
+ </div>
58
+ <button type="submit" className="btn btn-primary btn-block" disabled={loading}>
59
+ {loading ? 'Signing in...' : 'Login'}
60
+ </button>
61
+ </form>
62
+ <p className="auth-footer">
63
+ No account? <Link to="/register">Register here</Link>
64
+ </p>
65
+ <div className="demo-credentials">
66
+ <p><strong>Demo accounts:</strong></p>
67
+ <p>Receptionist: reception@kfh.rw / receptionist123</p>
68
+ <p>Doctor: doctor1@kfh.rw / doctor123</p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,217 @@
1
+ import { useEffect, useState } from 'react';
2
+ import api from '../services/api';
3
+
4
+ const emptyReport = {
5
+ appointment_id: '',
6
+ diagnosis: '',
7
+ prescription: '',
8
+ report_date: new Date().toISOString().split('T')[0],
9
+ };
10
+
11
+ export default function MedicalReports() {
12
+ const [assignedAppointments, setAssignedAppointments] = useState([]);
13
+ const [reports, setReports] = useState([]);
14
+ const [form, setForm] = useState(emptyReport);
15
+ const [showForm, setShowForm] = useState(false);
16
+ const [error, setError] = useState('');
17
+ const [success, setSuccess] = useState('');
18
+ const [loading, setLoading] = useState(true);
19
+
20
+ const fetchData = async () => {
21
+ try {
22
+ const [apptRes, reportRes] = await Promise.all([
23
+ api.get('/appointments/assigned'),
24
+ api.get('/medical-reports'),
25
+ ]);
26
+ setAssignedAppointments(apptRes.data);
27
+ setReports(reportRes.data);
28
+ } catch (err) {
29
+ setError(err.response?.data?.message || 'Failed to load data.');
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+
35
+ useEffect(() => {
36
+ fetchData();
37
+ }, []);
38
+
39
+ const handleChange = (e) => {
40
+ setForm({ ...form, [e.target.name]: e.target.value });
41
+ };
42
+
43
+ const handleSubmit = async (e) => {
44
+ e.preventDefault();
45
+ setError('');
46
+ setSuccess('');
47
+ try {
48
+ await api.post('/medical-reports', form);
49
+ setSuccess('Medical report saved successfully.');
50
+ setForm(emptyReport);
51
+ setShowForm(false);
52
+ fetchData();
53
+ } catch (err) {
54
+ setError(err.response?.data?.message || 'Failed to save report.');
55
+ }
56
+ };
57
+
58
+ const selectAppointment = (appt) => {
59
+ setForm({
60
+ ...emptyReport,
61
+ appointment_id: appt.id,
62
+ });
63
+ setShowForm(true);
64
+ };
65
+
66
+ return (
67
+ <div className="page">
68
+ <header className="page-header">
69
+ <div>
70
+ <h1>Medical Report Management</h1>
71
+ <p>View assigned appointments and create medical reports</p>
72
+ </div>
73
+ <button
74
+ type="button"
75
+ className="btn btn-primary"
76
+ onClick={() => setShowForm(!showForm)}
77
+ >
78
+ {showForm ? 'Close Form' : '+ New Report'}
79
+ </button>
80
+ </header>
81
+
82
+ {error && <div className="alert alert-error">{error}</div>}
83
+ {success && <div className="alert alert-success">{success}</div>}
84
+
85
+ <div className="card">
86
+ <h2>Assigned Appointments (Scheduled)</h2>
87
+ {loading ? (
88
+ <p className="text-muted">Loading...</p>
89
+ ) : assignedAppointments.length === 0 ? (
90
+ <p className="text-muted">No scheduled appointments assigned to you.</p>
91
+ ) : (
92
+ <div className="table-responsive">
93
+ <table>
94
+ <thead>
95
+ <tr>
96
+ <th>Patient</th>
97
+ <th>Date</th>
98
+ <th>Time</th>
99
+ <th>Action</th>
100
+ </tr>
101
+ </thead>
102
+ <tbody>
103
+ {assignedAppointments.map((a) => (
104
+ <tr key={a.id}>
105
+ <td>{a.patient_name}</td>
106
+ <td>{a.appointment_date?.split('T')[0]}</td>
107
+ <td>{a.appointment_time?.slice(0, 5)}</td>
108
+ <td>
109
+ <button
110
+ type="button"
111
+ className="btn btn-sm btn-primary"
112
+ onClick={() => selectAppointment(a)}
113
+ >
114
+ Add Report
115
+ </button>
116
+ </td>
117
+ </tr>
118
+ ))}
119
+ </tbody>
120
+ </table>
121
+ </div>
122
+ )}
123
+ </div>
124
+
125
+ {showForm && (
126
+ <div className="card form-card">
127
+ <h2>Create Medical Report</h2>
128
+ <form onSubmit={handleSubmit}>
129
+ <div className="form-group">
130
+ <label>Appointment</label>
131
+ <select
132
+ name="appointment_id"
133
+ value={form.appointment_id}
134
+ onChange={handleChange}
135
+ required
136
+ >
137
+ <option value="">Select appointment</option>
138
+ {assignedAppointments.map((a) => (
139
+ <option key={a.id} value={a.id}>
140
+ {a.patient_name} — {a.appointment_date?.split('T')[0]} {a.appointment_time?.slice(0, 5)}
141
+ </option>
142
+ ))}
143
+ </select>
144
+ </div>
145
+ <div className="form-group">
146
+ <label>Diagnosis</label>
147
+ <textarea
148
+ name="diagnosis"
149
+ value={form.diagnosis}
150
+ onChange={handleChange}
151
+ rows={3}
152
+ required
153
+ placeholder="Enter diagnosis..."
154
+ />
155
+ </div>
156
+ <div className="form-group">
157
+ <label>Prescription</label>
158
+ <textarea
159
+ name="prescription"
160
+ value={form.prescription}
161
+ onChange={handleChange}
162
+ rows={3}
163
+ required
164
+ placeholder="Enter prescription..."
165
+ />
166
+ </div>
167
+ <div className="form-group">
168
+ <label>Report Date</label>
169
+ <input
170
+ name="report_date"
171
+ type="date"
172
+ value={form.report_date}
173
+ onChange={handleChange}
174
+ required
175
+ />
176
+ </div>
177
+ <button type="submit" className="btn btn-primary">
178
+ Save Medical Report
179
+ </button>
180
+ </form>
181
+ </div>
182
+ )}
183
+
184
+ <div className="card">
185
+ <h2>Saved Medical Reports</h2>
186
+ {reports.length === 0 ? (
187
+ <p className="text-muted">No medical reports yet.</p>
188
+ ) : (
189
+ <div className="table-responsive">
190
+ <table>
191
+ <thead>
192
+ <tr>
193
+ <th>Patient</th>
194
+ <th>Doctor</th>
195
+ <th>Diagnosis</th>
196
+ <th>Prescription</th>
197
+ <th>Report Date</th>
198
+ </tr>
199
+ </thead>
200
+ <tbody>
201
+ {reports.map((r) => (
202
+ <tr key={r.id}>
203
+ <td>{r.patient_name}</td>
204
+ <td>{r.doctor_name}</td>
205
+ <td className="text-wrap">{r.diagnosis}</td>
206
+ <td className="text-wrap">{r.prescription}</td>
207
+ <td>{r.report_date?.split('T')[0]}</td>
208
+ </tr>
209
+ ))}
210
+ </tbody>
211
+ </table>
212
+ </div>
213
+ )}
214
+ </div>
215
+ </div>
216
+ );
217
+ }