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,196 @@
1
+ import { useEffect, useState } from 'react';
2
+ import api from '../services/api';
3
+
4
+ const emptyForm = {
5
+ full_name: '',
6
+ gender: 'Male',
7
+ date_of_birth: '',
8
+ phone_number: '',
9
+ address: '',
10
+ };
11
+
12
+ export default function Patients() {
13
+ const [patients, setPatients] = useState([]);
14
+ const [form, setForm] = useState(emptyForm);
15
+ const [editingId, setEditingId] = useState(null);
16
+ const [showForm, setShowForm] = useState(false);
17
+ const [error, setError] = useState('');
18
+ const [loading, setLoading] = useState(true);
19
+
20
+ const fetchPatients = async () => {
21
+ try {
22
+ const res = await api.get('/patients');
23
+ setPatients(res.data);
24
+ } catch (err) {
25
+ setError(err.response?.data?.message || 'Failed to load patients.');
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ };
30
+
31
+ useEffect(() => {
32
+ fetchPatients();
33
+ }, []);
34
+
35
+ const handleChange = (e) => {
36
+ setForm({ ...form, [e.target.name]: e.target.value });
37
+ };
38
+
39
+ const handleSubmit = async (e) => {
40
+ e.preventDefault();
41
+ setError('');
42
+
43
+ try {
44
+ if (editingId) {
45
+ await api.put(`/patients/${editingId}`, form);
46
+ } else {
47
+ await api.post('/patients', form);
48
+ }
49
+ setForm(emptyForm);
50
+ setEditingId(null);
51
+ setShowForm(false);
52
+ fetchPatients();
53
+ } catch (err) {
54
+ setError(err.response?.data?.message || 'Operation failed.');
55
+ }
56
+ };
57
+
58
+ const handleEdit = (patient) => {
59
+ setForm({
60
+ full_name: patient.full_name,
61
+ gender: patient.gender,
62
+ date_of_birth: patient.date_of_birth?.split('T')[0] || patient.date_of_birth,
63
+ phone_number: patient.phone_number,
64
+ address: patient.address,
65
+ });
66
+ setEditingId(patient.id);
67
+ setShowForm(true);
68
+ };
69
+
70
+ const handleDelete = async (id) => {
71
+ if (!window.confirm('Delete this patient record?')) return;
72
+ try {
73
+ await api.delete(`/patients/${id}`);
74
+ fetchPatients();
75
+ } catch (err) {
76
+ setError(err.response?.data?.message || 'Delete failed.');
77
+ }
78
+ };
79
+
80
+ const cancelForm = () => {
81
+ setForm(emptyForm);
82
+ setEditingId(null);
83
+ setShowForm(false);
84
+ };
85
+
86
+ return (
87
+ <div className="page">
88
+ <header className="page-header">
89
+ <div>
90
+ <h1>Patient Management</h1>
91
+ <p>Register and manage patient records</p>
92
+ </div>
93
+ <button
94
+ type="button"
95
+ className="btn btn-primary"
96
+ onClick={() => {
97
+ setShowForm(!showForm);
98
+ if (showForm) cancelForm();
99
+ }}
100
+ >
101
+ {showForm ? 'Close Form' : '+ Add Patient'}
102
+ </button>
103
+ </header>
104
+
105
+ {error && <div className="alert alert-error">{error}</div>}
106
+
107
+ {showForm && (
108
+ <div className="card form-card">
109
+ <h2>{editingId ? 'Update Patient' : 'New Patient'}</h2>
110
+ <form onSubmit={handleSubmit} className="form-grid">
111
+ <div className="form-group">
112
+ <label>Full Name</label>
113
+ <input name="full_name" value={form.full_name} onChange={handleChange} required />
114
+ </div>
115
+ <div className="form-group">
116
+ <label>Gender</label>
117
+ <select name="gender" value={form.gender} onChange={handleChange}>
118
+ <option value="Male">Male</option>
119
+ <option value="Female">Female</option>
120
+ <option value="Other">Other</option>
121
+ </select>
122
+ </div>
123
+ <div className="form-group">
124
+ <label>Date of Birth</label>
125
+ <input
126
+ name="date_of_birth"
127
+ type="date"
128
+ value={form.date_of_birth}
129
+ onChange={handleChange}
130
+ required
131
+ />
132
+ </div>
133
+ <div className="form-group">
134
+ <label>Phone Number</label>
135
+ <input name="phone_number" value={form.phone_number} onChange={handleChange} required />
136
+ </div>
137
+ <div className="form-group full-width">
138
+ <label>Address</label>
139
+ <textarea name="address" value={form.address} onChange={handleChange} rows={2} required />
140
+ </div>
141
+ <div className="form-actions full-width">
142
+ <button type="submit" className="btn btn-primary">
143
+ {editingId ? 'Update' : 'Save'} Patient
144
+ </button>
145
+ <button type="button" className="btn btn-outline" onClick={cancelForm}>
146
+ Cancel
147
+ </button>
148
+ </div>
149
+ </form>
150
+ </div>
151
+ )}
152
+
153
+ <div className="card">
154
+ {loading ? (
155
+ <p className="text-muted">Loading patients...</p>
156
+ ) : patients.length === 0 ? (
157
+ <p className="text-muted">No patients registered yet.</p>
158
+ ) : (
159
+ <div className="table-responsive">
160
+ <table>
161
+ <thead>
162
+ <tr>
163
+ <th>Name</th>
164
+ <th>Gender</th>
165
+ <th>Date of Birth</th>
166
+ <th>Phone</th>
167
+ <th>Address</th>
168
+ <th>Actions</th>
169
+ </tr>
170
+ </thead>
171
+ <tbody>
172
+ {patients.map((p) => (
173
+ <tr key={p.id}>
174
+ <td>{p.full_name}</td>
175
+ <td>{p.gender}</td>
176
+ <td>{p.date_of_birth?.split('T')[0]}</td>
177
+ <td>{p.phone_number}</td>
178
+ <td>{p.address}</td>
179
+ <td className="actions">
180
+ <button type="button" className="btn btn-sm btn-outline" onClick={() => handleEdit(p)}>
181
+ Edit
182
+ </button>
183
+ <button type="button" className="btn btn-sm btn-danger" onClick={() => handleDelete(p.id)}>
184
+ Delete
185
+ </button>
186
+ </td>
187
+ </tr>
188
+ ))}
189
+ </tbody>
190
+ </table>
191
+ </div>
192
+ )}
193
+ </div>
194
+ </div>
195
+ );
196
+ }
@@ -0,0 +1,98 @@
1
+ import { useState } from 'react';
2
+ import { Link, useNavigate } from 'react-router-dom';
3
+ import { useAuth } from '../context/AuthContext';
4
+
5
+ export default function Register() {
6
+ const [form, setForm] = useState({
7
+ full_name: '',
8
+ email: '',
9
+ password: '',
10
+ role: 'receptionist',
11
+ });
12
+ const [error, setError] = useState('');
13
+ const [success, setSuccess] = useState('');
14
+ const [loading, setLoading] = useState(false);
15
+ const { register } = useAuth();
16
+ const navigate = useNavigate();
17
+
18
+ const handleChange = (e) => {
19
+ setForm({ ...form, [e.target.name]: e.target.value });
20
+ };
21
+
22
+ const handleSubmit = async (e) => {
23
+ e.preventDefault();
24
+ setError('');
25
+ setSuccess('');
26
+ setLoading(true);
27
+ try {
28
+ await register(form);
29
+ setSuccess('Registration successful! You can now login.');
30
+ setTimeout(() => navigate('/login'), 2000);
31
+ } catch (err) {
32
+ setError(err.response?.data?.message || 'Registration failed.');
33
+ } finally {
34
+ setLoading(false);
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div className="auth-page">
40
+ <div className="auth-card">
41
+ <div className="auth-header">
42
+ <h1>Create Account</h1>
43
+ <p>Register as Receptionist or Doctor</p>
44
+ </div>
45
+ <form onSubmit={handleSubmit}>
46
+ {error && <div className="alert alert-error">{error}</div>}
47
+ {success && <div className="alert alert-success">{success}</div>}
48
+ <div className="form-group">
49
+ <label htmlFor="full_name">Full Name</label>
50
+ <input
51
+ id="full_name"
52
+ name="full_name"
53
+ value={form.full_name}
54
+ onChange={handleChange}
55
+ required
56
+ />
57
+ </div>
58
+ <div className="form-group">
59
+ <label htmlFor="email">Email</label>
60
+ <input
61
+ id="email"
62
+ name="email"
63
+ type="email"
64
+ value={form.email}
65
+ onChange={handleChange}
66
+ required
67
+ />
68
+ </div>
69
+ <div className="form-group">
70
+ <label htmlFor="password">Password</label>
71
+ <input
72
+ id="password"
73
+ name="password"
74
+ type="password"
75
+ value={form.password}
76
+ onChange={handleChange}
77
+ minLength={6}
78
+ required
79
+ />
80
+ </div>
81
+ <div className="form-group">
82
+ <label htmlFor="role">Role</label>
83
+ <select id="role" name="role" value={form.role} onChange={handleChange}>
84
+ <option value="receptionist">Receptionist</option>
85
+ <option value="doctor">Doctor</option>
86
+ </select>
87
+ </div>
88
+ <button type="submit" className="btn btn-primary btn-block" disabled={loading}>
89
+ {loading ? 'Registering...' : 'Register'}
90
+ </button>
91
+ </form>
92
+ <p className="auth-footer">
93
+ Already have an account? <Link to="/login">Login here</Link>
94
+ </p>
95
+ </div>
96
+ </div>
97
+ );
98
+ }
@@ -0,0 +1,170 @@
1
+ import { useState } from 'react';
2
+ import api from '../services/api';
3
+
4
+ export default function Reports() {
5
+ const [startDate, setStartDate] = useState('');
6
+ const [endDate, setEndDate] = useState('');
7
+ const [report, setReport] = useState(null);
8
+ const [error, setError] = useState('');
9
+ const [loading, setLoading] = useState(false);
10
+
11
+ const handleGenerate = async (e) => {
12
+ e.preventDefault();
13
+ setError('');
14
+ setReport(null);
15
+
16
+ if (!startDate || !endDate) {
17
+ setError('Please select both start and end dates.');
18
+ return;
19
+ }
20
+
21
+ if (startDate > endDate) {
22
+ setError('Start date must be before or equal to end date.');
23
+ return;
24
+ }
25
+
26
+ setLoading(true);
27
+ try {
28
+ const res = await api.get('/reports/summary', {
29
+ params: { start_date: startDate, end_date: endDate },
30
+ });
31
+ setReport(res.data);
32
+ } catch (err) {
33
+ setError(err.response?.data?.message || 'Failed to generate report.');
34
+ } finally {
35
+ setLoading(false);
36
+ }
37
+ };
38
+
39
+ return (
40
+ <div className="page">
41
+ <header className="page-header">
42
+ <div>
43
+ <h1>Report Generation</h1>
44
+ <p>Filter appointments and patients attended by date range</p>
45
+ </div>
46
+ </header>
47
+
48
+ <div className="card form-card">
49
+ <form onSubmit={handleGenerate} className="report-filter-form">
50
+ <div className="form-group">
51
+ <label htmlFor="start_date">Start Date</label>
52
+ <input
53
+ id="start_date"
54
+ type="date"
55
+ value={startDate}
56
+ onChange={(e) => setStartDate(e.target.value)}
57
+ required
58
+ />
59
+ </div>
60
+ <div className="form-group">
61
+ <label htmlFor="end_date">End Date</label>
62
+ <input
63
+ id="end_date"
64
+ type="date"
65
+ value={endDate}
66
+ onChange={(e) => setEndDate(e.target.value)}
67
+ required
68
+ />
69
+ </div>
70
+ <button type="submit" className="btn btn-primary" disabled={loading}>
71
+ {loading ? 'Generating...' : 'Generate Report'}
72
+ </button>
73
+ </form>
74
+ </div>
75
+
76
+ {error && <div className="alert alert-error">{error}</div>}
77
+
78
+ {report && (
79
+ <>
80
+ <div className="stats-grid">
81
+ <div className="stat-card highlight">
82
+ <span className="stat-label">Total Appointments</span>
83
+ <span className="stat-value">{report.total_appointments}</span>
84
+ </div>
85
+ <div className="stat-card">
86
+ <span className="stat-label">Completed</span>
87
+ <span className="stat-value">{report.total_completed}</span>
88
+ </div>
89
+ <div className="stat-card">
90
+ <span className="stat-label">Cancelled</span>
91
+ <span className="stat-value">{report.total_cancelled}</span>
92
+ </div>
93
+ <div className="stat-card">
94
+ <span className="stat-label">Patients Attended</span>
95
+ <span className="stat-value">{report.patients_attended?.length || 0}</span>
96
+ </div>
97
+ </div>
98
+
99
+ <div className="card">
100
+ <h2>
101
+ Patients Attended ({report.start_date} to {report.end_date})
102
+ </h2>
103
+ {report.patients_attended?.length === 0 ? (
104
+ <p className="text-muted">No patients attended in this period.</p>
105
+ ) : (
106
+ <div className="table-responsive">
107
+ <table>
108
+ <thead>
109
+ <tr>
110
+ <th>Name</th>
111
+ <th>Gender</th>
112
+ <th>Phone</th>
113
+ <th>Report Date</th>
114
+ </tr>
115
+ </thead>
116
+ <tbody>
117
+ {report.patients_attended.map((p) => (
118
+ <tr key={`${p.id}-${p.report_date}`}>
119
+ <td>{p.full_name}</td>
120
+ <td>{p.gender}</td>
121
+ <td>{p.phone_number}</td>
122
+ <td>{p.report_date?.split('T')[0]}</td>
123
+ </tr>
124
+ ))}
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ )}
129
+ </div>
130
+
131
+ <div className="card">
132
+ <h2>Appointments in Period</h2>
133
+ {report.appointments?.length === 0 ? (
134
+ <p className="text-muted">No appointments in this period.</p>
135
+ ) : (
136
+ <div className="table-responsive">
137
+ <table>
138
+ <thead>
139
+ <tr>
140
+ <th>Patient</th>
141
+ <th>Doctor</th>
142
+ <th>Date</th>
143
+ <th>Time</th>
144
+ <th>Status</th>
145
+ </tr>
146
+ </thead>
147
+ <tbody>
148
+ {report.appointments.map((a) => (
149
+ <tr key={a.id}>
150
+ <td>{a.patient_name}</td>
151
+ <td>{a.doctor_name}</td>
152
+ <td>{a.appointment_date?.split('T')[0]}</td>
153
+ <td>{a.appointment_time?.slice(0, 5)}</td>
154
+ <td>
155
+ <span className={`status-badge status-${a.status}`}>
156
+ {a.status}
157
+ </span>
158
+ </td>
159
+ </tr>
160
+ ))}
161
+ </tbody>
162
+ </table>
163
+ </div>
164
+ )}
165
+ </div>
166
+ </>
167
+ )}
168
+ </div>
169
+ );
170
+ }
@@ -0,0 +1,15 @@
1
+ import axios from 'axios';
2
+
3
+ const api = axios.create({
4
+ baseURL: process.env.REACT_APP_API_URL || '/api',
5
+ });
6
+
7
+ api.interceptors.request.use((config) => {
8
+ const token = localStorage.getItem('token');
9
+ if (token) {
10
+ config.headers.Authorization = `Bearer ${token}`;
11
+ }
12
+ return config;
13
+ });
14
+
15
+ export default api;
package/apps/config.js ADDED
@@ -0,0 +1,8 @@
1
+ export const apps = [
2
+ {
3
+ title: "HMSR - Hospital Management System Report",
4
+ value: "hmsr",
5
+ description: "Hospital management system by Steve Rogers",
6
+ folder: "HMSR",
7
+ },
8
+ ];
package/exclude.txt ADDED
@@ -0,0 +1 @@
1
+ node_modules
package/index.js ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { cpSync, existsSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { cwd } from "process";
5
+ import { fileURLToPath } from "url";
6
+ import { execSync } from "child_process";
7
+ import prompts from "prompts";
8
+ import kleur from "kleur";
9
+ import { apps } from "./apps/config.js";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ async function main() {
14
+ console.log(kleur.bold().cyan("\nšŸ›” Steve Rogers Installer\n"));
15
+
16
+ const choices = apps.map((app) => ({
17
+ title: `${app.title} ${kleur.gray(app.description)}`,
18
+ value: app.value,
19
+ }));
20
+
21
+ const { selected } = await prompts({
22
+ type: "multiselect",
23
+ name: "selected",
24
+ message: "Which apps do you want to install?",
25
+ choices,
26
+ hint: "- Space to select, Return to submit",
27
+ min: 1,
28
+ });
29
+
30
+ if (!selected || selected.length === 0) {
31
+ console.log(kleur.yellow("\nNo apps selected. Exiting.\n"));
32
+ process.exit(0);
33
+ }
34
+
35
+ for (const value of selected) {
36
+ const app = apps.find((a) => a.value === value);
37
+ const src = join(__dirname, "apps", app.folder);
38
+ const dest = join(cwd(), app.folder);
39
+
40
+ console.log(kleur.cyan(`\n→ Installing ${app.title}...`));
41
+
42
+ try {
43
+ cpSync(src, dest, { recursive: true });
44
+ console.log(kleur.gray(" Running npm install..."));
45
+ execSync("npm install", { cwd: dest, stdio: "inherit" });
46
+ console.log(kleur.green(` āœ“ ${app.title} ready at ./${app.folder}\n`));
47
+ } catch (err) {
48
+ console.log(kleur.red(` āœ— Failed: ${err.message}\n`));
49
+ }
50
+ }
51
+
52
+ console.log(kleur.bold().green("šŸŽ‰ Done!\n"));
53
+ }
54
+
55
+ main();
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "create-steve-rogers",
3
+ "version": "1.0.0",
4
+ "description": "Steve Rogers personal app installer",
5
+ "bin": {
6
+ "create-steve-rogers": "index.js"
7
+ },
8
+ "type": "module",
9
+ "dependencies": {
10
+ "kleur": "^4.1.5",
11
+ "prompts": "^2.4.2",
12
+ "unzipper": "^0.12.3"
13
+ }
14
+ }