create-jinmankn-app 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/bin/index.js +76 -0
- package/package.json +20 -0
- package/templates/blueprint/BLUEPRINT_REPRODUCTION_PROMPT.md +996 -0
- package/templates/blueprint/HOW_IT_WORKS.md +286 -0
- package/templates/blueprint/README.md +123 -0
- package/templates/blueprint/backend/config/db.js +12 -0
- package/templates/blueprint/backend/controllers/authController.js +90 -0
- package/templates/blueprint/backend/controllers/itemController.js +74 -0
- package/templates/blueprint/backend/middleware/auth.js +32 -0
- package/templates/blueprint/backend/middleware/errorHandler.js +23 -0
- package/templates/blueprint/backend/models/Item.js +26 -0
- package/templates/blueprint/backend/models/User.js +28 -0
- package/templates/blueprint/backend/package-lock.json +2190 -0
- package/templates/blueprint/backend/package.json +23 -0
- package/templates/blueprint/backend/routes/authRoutes.js +11 -0
- package/templates/blueprint/backend/routes/healthRoutes.js +9 -0
- package/templates/blueprint/backend/routes/itemRoutes.js +21 -0
- package/templates/blueprint/backend/server.js +29 -0
- package/templates/blueprint/frontend/.env.example +1 -0
- package/templates/blueprint/frontend/index.html +13 -0
- package/templates/blueprint/frontend/package-lock.json +2844 -0
- package/templates/blueprint/frontend/package.json +23 -0
- package/templates/blueprint/frontend/public/favicon.svg +4 -0
- package/templates/blueprint/frontend/src/App.jsx +78 -0
- package/templates/blueprint/frontend/src/assets/logo.svg +4 -0
- package/templates/blueprint/frontend/src/components/DashboardLayout.jsx +103 -0
- package/templates/blueprint/frontend/src/components/ProtectedRoute.jsx +18 -0
- package/templates/blueprint/frontend/src/index.css +1 -0
- package/templates/blueprint/frontend/src/main.jsx +13 -0
- package/templates/blueprint/frontend/src/pages/DashboardHome.jsx +74 -0
- package/templates/blueprint/frontend/src/pages/Items.jsx +243 -0
- package/templates/blueprint/frontend/src/pages/Login.jsx +101 -0
- package/templates/blueprint/frontend/src/pages/Profile.jsx +79 -0
- package/templates/blueprint/frontend/src/pages/Register.jsx +122 -0
- package/templates/blueprint/frontend/src/pages/Report.jsx +124 -0
- package/templates/blueprint/frontend/vite.config.js +10 -0
- package/templates/blueprint/package.json +13 -0
- package/templates/blueprint/scripts/pack-blueprint.ps1 +18 -0
- package/templates/chom/Backend/app.js +25 -0
- package/templates/chom/Backend/package-lock.json +1551 -0
- package/templates/chom/Backend/package.json +23 -0
- package/templates/chom/Backend/seedAdmin.js +21 -0
- package/templates/chom/Backend/src/controllers/payment.c.js +57 -0
- package/templates/chom/Backend/src/controllers/students.c.js +58 -0
- package/templates/chom/Backend/src/controllers/users.c.js +62 -0
- package/templates/chom/Backend/src/middleware/authentication.js +18 -0
- package/templates/chom/Backend/src/models/payment.m.js +13 -0
- package/templates/chom/Backend/src/models/students.m.js +10 -0
- package/templates/chom/Backend/src/models/users.m.js +11 -0
- package/templates/chom/Backend/src/routes/users.r.js +21 -0
- package/templates/chom/Frontend/README.md +16 -0
- package/templates/chom/Frontend/eslint.config.js +21 -0
- package/templates/chom/Frontend/index.html +13 -0
- package/templates/chom/Frontend/package-lock.json +3075 -0
- package/templates/chom/Frontend/package.json +31 -0
- package/templates/chom/Frontend/public/favicon.svg +1 -0
- package/templates/chom/Frontend/public/icons.svg +24 -0
- package/templates/chom/Frontend/src/App.css +189 -0
- package/templates/chom/Frontend/src/App.jsx +28 -0
- package/templates/chom/Frontend/src/api/api.jsx +27 -0
- package/templates/chom/Frontend/src/assets/hero.png +0 -0
- package/templates/chom/Frontend/src/assets/react.svg +1 -0
- package/templates/chom/Frontend/src/assets/vite.svg +1 -0
- package/templates/chom/Frontend/src/components/Navbar.jsx +21 -0
- package/templates/chom/Frontend/src/index.css +8 -0
- package/templates/chom/Frontend/src/main.jsx +10 -0
- package/templates/chom/Frontend/src/pages/Dashboard.jsx +21 -0
- package/templates/chom/Frontend/src/pages/Landing.jsx +39 -0
- package/templates/chom/Frontend/src/pages/Login.jsx +49 -0
- package/templates/chom/Frontend/src/pages/Overview.jsx +42 -0
- package/templates/chom/Frontend/src/pages/Register.jsx +76 -0
- package/templates/chom/Frontend/src/pages/Students.jsx +14 -0
- package/templates/chom/Frontend/vite.config.js +8 -0
- package/templates/chom/package.json +13 -0
- package/templates/hospital-faisal/backend/.env.example +9 -0
- package/templates/hospital-faisal/backend/config/db.js +96 -0
- package/templates/hospital-faisal/backend/controllers/appointmentController.js +164 -0
- package/templates/hospital-faisal/backend/controllers/authController.js +106 -0
- package/templates/hospital-faisal/backend/controllers/hospitalReportController.js +72 -0
- package/templates/hospital-faisal/backend/controllers/medicalReportController.js +105 -0
- package/templates/hospital-faisal/backend/controllers/patientController.js +98 -0
- package/templates/hospital-faisal/backend/database/schema.sql +47 -0
- package/templates/hospital-faisal/backend/middleware/auth.js +30 -0
- package/templates/hospital-faisal/backend/middleware/errorHandler.js +23 -0
- package/templates/hospital-faisal/backend/middleware/role.js +6 -0
- package/templates/hospital-faisal/backend/package-lock.json +2092 -0
- package/templates/hospital-faisal/backend/package.json +23 -0
- package/templates/hospital-faisal/backend/routes/appointmentRoutes.js +25 -0
- package/templates/hospital-faisal/backend/routes/authRoutes.js +12 -0
- package/templates/hospital-faisal/backend/routes/healthRoutes.js +9 -0
- package/templates/hospital-faisal/backend/routes/hospitalReportRoutes.js +10 -0
- package/templates/hospital-faisal/backend/routes/medicalReportRoutes.js +16 -0
- package/templates/hospital-faisal/backend/routes/patientRoutes.js +22 -0
- package/templates/hospital-faisal/backend/server.js +46 -0
- package/templates/hospital-faisal/frontend/.env.example +1 -0
- package/templates/hospital-faisal/frontend/index.html +10 -0
- package/templates/hospital-faisal/frontend/package-lock.json +2844 -0
- package/templates/hospital-faisal/frontend/package.json +23 -0
- package/templates/hospital-faisal/frontend/public/favicon.svg +4 -0
- package/templates/hospital-faisal/frontend/src/App.jsx +56 -0
- package/templates/hospital-faisal/frontend/src/api.js +20 -0
- package/templates/hospital-faisal/frontend/src/assets/logo.svg +4 -0
- package/templates/hospital-faisal/frontend/src/components/DashboardLayout.jsx +114 -0
- package/templates/hospital-faisal/frontend/src/components/ProtectedRoute.jsx +18 -0
- package/templates/hospital-faisal/frontend/src/components/RoleRoute.jsx +14 -0
- package/templates/hospital-faisal/frontend/src/index.css +1 -0
- package/templates/hospital-faisal/frontend/src/main.jsx +13 -0
- package/templates/hospital-faisal/frontend/src/pages/Appointments.jsx +305 -0
- package/templates/hospital-faisal/frontend/src/pages/DashboardHome.jsx +105 -0
- package/templates/hospital-faisal/frontend/src/pages/Login.jsx +98 -0
- package/templates/hospital-faisal/frontend/src/pages/MedicalReports.jsx +182 -0
- package/templates/hospital-faisal/frontend/src/pages/Patients.jsx +237 -0
- package/templates/hospital-faisal/frontend/src/pages/Profile.jsx +78 -0
- package/templates/hospital-faisal/frontend/src/pages/Register.jsx +133 -0
- package/templates/hospital-faisal/frontend/src/pages/Report.jsx +167 -0
- package/templates/hospital-faisal/frontend/vite.config.js +10 -0
- package/templates/hospital-faisal/package.json +13 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Link } from "react-router-dom";
|
|
3
|
+
import { useNavigate } from "react-router-dom";
|
|
4
|
+
import API from "../api/api";
|
|
5
|
+
|
|
6
|
+
const Register = () => {
|
|
7
|
+
const navigate = useNavigate()
|
|
8
|
+
|
|
9
|
+
const [form, setForm] = useState({
|
|
10
|
+
names: "",
|
|
11
|
+
email: "",
|
|
12
|
+
password: "",
|
|
13
|
+
role: ""
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const handleChange = (e) => {
|
|
17
|
+
setForm({
|
|
18
|
+
...form, [e.target.name]: e.target.value
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const handleSubmit = async (e) => {
|
|
24
|
+
e.preventDefault()
|
|
25
|
+
try {
|
|
26
|
+
const res = await API.post("/register", form)
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return alert(error)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
navigate("/dashboard")
|
|
32
|
+
}
|
|
33
|
+
return (
|
|
34
|
+
<>
|
|
35
|
+
<div className="w-full h-screen flex items-center justify-center">
|
|
36
|
+
<div className="w-1/4 h-8/12 border-2 border-cyan-500 rounded-2xl">
|
|
37
|
+
<div className="w-full flex items-center justify-center">
|
|
38
|
+
<p className="font-semibold text-2xl text-(--text-main)">Register</p>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="w-full h-5/7 flex flex-col items-center justify-evenly">
|
|
41
|
+
<input
|
|
42
|
+
type="text"
|
|
43
|
+
onChange={handleChange}
|
|
44
|
+
name="names"
|
|
45
|
+
placeholder="Username"
|
|
46
|
+
className="border border-(--bg-maj) text-(--text-maj) w-10/11 h-1/8 rounded-xl placeholder:text-(--text-maj)/40 text-sm pl-4 pr-3" />
|
|
47
|
+
<input
|
|
48
|
+
type="email"
|
|
49
|
+
onChange={handleChange}
|
|
50
|
+
name="email"
|
|
51
|
+
placeholder="Email"
|
|
52
|
+
className="border border-(--bg-maj) text-(--text-maj) w-10/11 h-1/8 rounded-xl placeholder:text-(--text-maj)/40 text-sm pl-4 pr-3" />
|
|
53
|
+
<input
|
|
54
|
+
type="password"
|
|
55
|
+
onChange={handleChange}
|
|
56
|
+
name="password"
|
|
57
|
+
placeholder="Password"
|
|
58
|
+
className="border border-(--bg-maj) text-(--text-maj) w-10/11 h-1/8 rounded-xl placeholder:text-(--text-maj)/40 text-sm pl-4 pr-3" />
|
|
59
|
+
<input
|
|
60
|
+
type="text"
|
|
61
|
+
onChange={handleChange}
|
|
62
|
+
name="role"
|
|
63
|
+
placeholder="Role"
|
|
64
|
+
className="border border-(--bg-maj) text-(--text-maj) w-10/11 h-1/8 rounded-xl placeholder:text-(--text-maj)/40 text-sm pl-4 pr-3" />
|
|
65
|
+
</div>
|
|
66
|
+
<div className="w-full h-1/8 flex justify-center">
|
|
67
|
+
<button onClick={handleSubmit} className="w-10/11 h-10 rounded bg-(--bg-maj)">Submit</button>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="w-full flex items-center justify-center"><p className="text-(--text-main) text-sm font-light">Already have an account? <Link to="/login" className="text-(--text-maj) hover:underline">Login</Link></p></div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export default Register
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const mysql = require('mysql2/promise');
|
|
2
|
+
|
|
3
|
+
let pool;
|
|
4
|
+
|
|
5
|
+
function getPool() {
|
|
6
|
+
if (!pool) {
|
|
7
|
+
pool = mysql.createPool({
|
|
8
|
+
host: process.env.DB_HOST || 'localhost',
|
|
9
|
+
user: process.env.DB_USER || 'root',
|
|
10
|
+
password: process.env.DB_PASSWORD || '',
|
|
11
|
+
database: process.env.DB_NAME || 'kf_hospital',
|
|
12
|
+
waitForConnections: true,
|
|
13
|
+
connectionLimit: 10,
|
|
14
|
+
dateStrings: true,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return pool;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function initDatabase() {
|
|
21
|
+
const host = process.env.DB_HOST || 'localhost';
|
|
22
|
+
const user = process.env.DB_USER || 'root';
|
|
23
|
+
const password = process.env.DB_PASSWORD || '';
|
|
24
|
+
const dbName = process.env.DB_NAME || 'kf_hospital';
|
|
25
|
+
|
|
26
|
+
let bootstrap;
|
|
27
|
+
try {
|
|
28
|
+
bootstrap = await mysql.createConnection({ host, user, password });
|
|
29
|
+
await bootstrap.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
|
|
30
|
+
await bootstrap.query(`USE \`${dbName}\``);
|
|
31
|
+
|
|
32
|
+
await bootstrap.query(`
|
|
33
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
34
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
35
|
+
name VARCHAR(255) NOT NULL,
|
|
36
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
37
|
+
password VARCHAR(255) NOT NULL,
|
|
38
|
+
role ENUM('receptionist', 'doctor') NOT NULL,
|
|
39
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
await bootstrap.query(`
|
|
44
|
+
CREATE TABLE IF NOT EXISTS patients (
|
|
45
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
46
|
+
full_name VARCHAR(255) NOT NULL,
|
|
47
|
+
gender ENUM('Male', 'Female', 'Other') NOT NULL,
|
|
48
|
+
date_of_birth DATE NOT NULL,
|
|
49
|
+
phone VARCHAR(50) NOT NULL,
|
|
50
|
+
address TEXT NOT NULL,
|
|
51
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
52
|
+
)
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
await bootstrap.query(`
|
|
56
|
+
CREATE TABLE IF NOT EXISTS appointments (
|
|
57
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
58
|
+
patient_id INT NOT NULL,
|
|
59
|
+
doctor_id INT NOT NULL,
|
|
60
|
+
appointment_date DATE NOT NULL,
|
|
61
|
+
appointment_time TIME NOT NULL,
|
|
62
|
+
status ENUM('scheduled', 'completed', 'cancelled') NOT NULL DEFAULT 'scheduled',
|
|
63
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
+
FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE,
|
|
65
|
+
FOREIGN KEY (doctor_id) REFERENCES users(id) ON DELETE RESTRICT
|
|
66
|
+
)
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
await bootstrap.query(`
|
|
70
|
+
CREATE TABLE IF NOT EXISTS medical_reports (
|
|
71
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
72
|
+
appointment_id INT NOT NULL UNIQUE,
|
|
73
|
+
patient_id INT NOT NULL,
|
|
74
|
+
doctor_id INT NOT NULL,
|
|
75
|
+
diagnosis TEXT NOT NULL,
|
|
76
|
+
prescription TEXT NOT NULL,
|
|
77
|
+
report_date DATE NOT NULL,
|
|
78
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
79
|
+
FOREIGN KEY (appointment_id) REFERENCES appointments(id) ON DELETE CASCADE,
|
|
80
|
+
FOREIGN KEY (patient_id) REFERENCES patients(id) ON DELETE CASCADE,
|
|
81
|
+
FOREIGN KEY (doctor_id) REFERENCES users(id) ON DELETE RESTRICT
|
|
82
|
+
)
|
|
83
|
+
`);
|
|
84
|
+
|
|
85
|
+
await bootstrap.end();
|
|
86
|
+
getPool();
|
|
87
|
+
console.log('MySQL connected and schema ready');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (bootstrap) await bootstrap.end().catch(() => {});
|
|
90
|
+
const hint =
|
|
91
|
+
'Ensure MySQL is running and DB_USER/DB_PASSWORD in backend/.env are correct.';
|
|
92
|
+
throw new Error(`${err.message || err.code || 'Database connection failed'}. ${hint}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { getPool, initDatabase };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
const { getPool } = require('../config/db');
|
|
2
|
+
|
|
3
|
+
function formatAppointment(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
patientId: row.patient_id,
|
|
7
|
+
doctorId: row.doctor_id,
|
|
8
|
+
patientName: row.patient_name,
|
|
9
|
+
doctorName: row.doctor_name,
|
|
10
|
+
appointmentDate: row.appointment_date,
|
|
11
|
+
appointmentTime: row.appointment_time,
|
|
12
|
+
status: row.status,
|
|
13
|
+
createdAt: row.created_at,
|
|
14
|
+
hasReport: Boolean(row.has_report),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const appointmentSelect = `
|
|
19
|
+
SELECT a.*,
|
|
20
|
+
p.full_name AS patient_name,
|
|
21
|
+
u.name AS doctor_name,
|
|
22
|
+
(SELECT COUNT(*) FROM medical_reports mr WHERE mr.appointment_id = a.id) AS has_report
|
|
23
|
+
FROM appointments a
|
|
24
|
+
JOIN patients p ON p.id = a.patient_id
|
|
25
|
+
JOIN users u ON u.id = a.doctor_id
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
exports.getAppointments = async (req, res) => {
|
|
29
|
+
try {
|
|
30
|
+
const pool = getPool();
|
|
31
|
+
let query = `${appointmentSelect}`;
|
|
32
|
+
const params = [];
|
|
33
|
+
|
|
34
|
+
if (req.user.role === 'doctor') {
|
|
35
|
+
query += ' WHERE a.doctor_id = ?';
|
|
36
|
+
params.push(req.user.id);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
query += ' ORDER BY a.appointment_date DESC, a.appointment_time DESC';
|
|
40
|
+
|
|
41
|
+
const [rows] = await pool.query(query, params);
|
|
42
|
+
res.json({ appointments: rows.map(formatAppointment) });
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(500).json({ message: err.message });
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
exports.getAppointment = async (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const pool = getPool();
|
|
51
|
+
const [rows] = await pool.query(`${appointmentSelect} WHERE a.id = ?`, [req.params.id]);
|
|
52
|
+
|
|
53
|
+
if (!rows.length) {
|
|
54
|
+
return res.status(404).json({ message: 'Appointment not found' });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (req.user.role === 'doctor' && rows[0].doctor_id !== req.user.id) {
|
|
58
|
+
return res.status(403).json({ message: 'Access denied' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
res.json({ appointment: formatAppointment(rows[0]) });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
res.status(500).json({ message: err.message });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
exports.createAppointment = async (req, res) => {
|
|
68
|
+
try {
|
|
69
|
+
const { patientId, doctorId, appointmentDate, appointmentTime, status } = req.body;
|
|
70
|
+
|
|
71
|
+
if (!patientId || !doctorId || !appointmentDate || !appointmentTime) {
|
|
72
|
+
return res.status(400).json({ message: 'Patient, doctor, date, and time are required' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pool = getPool();
|
|
76
|
+
|
|
77
|
+
const [doctor] = await pool.query('SELECT id FROM users WHERE id = ? AND role = ?', [
|
|
78
|
+
doctorId,
|
|
79
|
+
'doctor',
|
|
80
|
+
]);
|
|
81
|
+
if (!doctor.length) {
|
|
82
|
+
return res.status(400).json({ message: 'Invalid doctor selected' });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const [result] = await pool.query(
|
|
86
|
+
`INSERT INTO appointments (patient_id, doctor_id, appointment_date, appointment_time, status)
|
|
87
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
88
|
+
[patientId, doctorId, appointmentDate, appointmentTime, status || 'scheduled'],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const [rows] = await pool.query(`${appointmentSelect} WHERE a.id = ?`, [result.insertId]);
|
|
92
|
+
res.status(201).json({ appointment: formatAppointment(rows[0]) });
|
|
93
|
+
} catch (err) {
|
|
94
|
+
res.status(500).json({ message: err.message });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
exports.updateAppointment = async (req, res) => {
|
|
99
|
+
try {
|
|
100
|
+
const { patientId, doctorId, appointmentDate, appointmentTime, status } = req.body;
|
|
101
|
+
const pool = getPool();
|
|
102
|
+
|
|
103
|
+
const [existing] = await pool.query('SELECT id FROM appointments WHERE id = ?', [req.params.id]);
|
|
104
|
+
if (!existing.length) {
|
|
105
|
+
return res.status(404).json({ message: 'Appointment not found' });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (doctorId) {
|
|
109
|
+
const [doctor] = await pool.query('SELECT id FROM users WHERE id = ? AND role = ?', [
|
|
110
|
+
doctorId,
|
|
111
|
+
'doctor',
|
|
112
|
+
]);
|
|
113
|
+
if (!doctor.length) {
|
|
114
|
+
return res.status(400).json({ message: 'Invalid doctor selected' });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
await pool.query(
|
|
119
|
+
`UPDATE appointments
|
|
120
|
+
SET patient_id = ?, doctor_id = ?, appointment_date = ?, appointment_time = ?, status = ?
|
|
121
|
+
WHERE id = ?`,
|
|
122
|
+
[patientId, doctorId, appointmentDate, appointmentTime, status, req.params.id],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const [rows] = await pool.query(`${appointmentSelect} WHERE a.id = ?`, [req.params.id]);
|
|
126
|
+
res.json({ appointment: formatAppointment(rows[0]) });
|
|
127
|
+
} catch (err) {
|
|
128
|
+
res.status(500).json({ message: err.message });
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
exports.cancelAppointment = async (req, res) => {
|
|
133
|
+
try {
|
|
134
|
+
const pool = getPool();
|
|
135
|
+
const [result] = await pool.query(
|
|
136
|
+
"UPDATE appointments SET status = 'cancelled' WHERE id = ?",
|
|
137
|
+
[req.params.id],
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (result.affectedRows === 0) {
|
|
141
|
+
return res.status(404).json({ message: 'Appointment not found' });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const [rows] = await pool.query(`${appointmentSelect} WHERE a.id = ?`, [req.params.id]);
|
|
145
|
+
res.json({ appointment: formatAppointment(rows[0]) });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
res.status(500).json({ message: err.message });
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
exports.deleteAppointment = async (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
const pool = getPool();
|
|
154
|
+
const [result] = await pool.query('DELETE FROM appointments WHERE id = ?', [req.params.id]);
|
|
155
|
+
|
|
156
|
+
if (result.affectedRows === 0) {
|
|
157
|
+
return res.status(404).json({ message: 'Appointment not found' });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
res.json({ message: 'Appointment deleted' });
|
|
161
|
+
} catch (err) {
|
|
162
|
+
res.status(500).json({ message: err.message });
|
|
163
|
+
}
|
|
164
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const bcrypt = require('bcrypt');
|
|
2
|
+
const jwt = require('jsonwebtoken');
|
|
3
|
+
const { getPool } = require('../config/db');
|
|
4
|
+
|
|
5
|
+
function createToken(userId) {
|
|
6
|
+
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatUser(user) {
|
|
10
|
+
return {
|
|
11
|
+
id: user.id,
|
|
12
|
+
name: user.name,
|
|
13
|
+
email: user.email,
|
|
14
|
+
role: user.role,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
exports.register = async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const { name, email, password, role } = req.body;
|
|
21
|
+
|
|
22
|
+
if (!name || !email || !password || !role) {
|
|
23
|
+
return res.status(400).json({ message: 'Name, email, password, and role are required' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!['receptionist', 'doctor'].includes(role)) {
|
|
27
|
+
return res.status(400).json({ message: 'Role must be receptionist or doctor' });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (password.length < 6) {
|
|
31
|
+
return res.status(400).json({ message: 'Password must be at least 6 characters' });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const pool = getPool();
|
|
35
|
+
const [existing] = await pool.query('SELECT id FROM users WHERE email = ?', [
|
|
36
|
+
email.toLowerCase(),
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
if (existing.length) {
|
|
40
|
+
return res.status(409).json({ message: 'Email already registered' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
44
|
+
const [result] = await pool.query(
|
|
45
|
+
'INSERT INTO users (name, email, password, role) VALUES (?, ?, ?, ?)',
|
|
46
|
+
[name, email.toLowerCase(), hashedPassword, role],
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const user = { id: result.insertId, name, email: email.toLowerCase(), role };
|
|
50
|
+
const token = createToken(user.id);
|
|
51
|
+
|
|
52
|
+
res.status(201).json({ token, user: formatUser(user) });
|
|
53
|
+
} catch (err) {
|
|
54
|
+
res.status(500).json({ message: err.message });
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
exports.login = async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const { email, password } = req.body;
|
|
61
|
+
|
|
62
|
+
if (!email || !password) {
|
|
63
|
+
return res.status(400).json({ message: 'Email and password are required' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const pool = getPool();
|
|
67
|
+
const [rows] = await pool.query('SELECT * FROM users WHERE email = ?', [email.toLowerCase()]);
|
|
68
|
+
|
|
69
|
+
if (!rows.length) {
|
|
70
|
+
return res.status(401).json({ message: 'Invalid email or password' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const user = rows[0];
|
|
74
|
+
const match = await bcrypt.compare(password, user.password);
|
|
75
|
+
|
|
76
|
+
if (!match) {
|
|
77
|
+
return res.status(401).json({ message: 'Invalid email or password' });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const token = createToken(user.id);
|
|
81
|
+
res.json({ token, user: formatUser(user) });
|
|
82
|
+
} catch (err) {
|
|
83
|
+
res.status(500).json({ message: err.message });
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
exports.getMe = async (req, res) => {
|
|
88
|
+
try {
|
|
89
|
+
res.json({ user: formatUser(req.user) });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
res.status(500).json({ message: err.message });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
exports.getDoctors = async (req, res) => {
|
|
96
|
+
try {
|
|
97
|
+
const pool = getPool();
|
|
98
|
+
const [doctors] = await pool.query(
|
|
99
|
+
'SELECT id, name, email FROM users WHERE role = ? ORDER BY name',
|
|
100
|
+
['doctor'],
|
|
101
|
+
);
|
|
102
|
+
res.json({ doctors });
|
|
103
|
+
} catch (err) {
|
|
104
|
+
res.status(500).json({ message: err.message });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { getPool } = require('../config/db');
|
|
2
|
+
|
|
3
|
+
exports.generateReport = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const { startDate, endDate } = req.query;
|
|
6
|
+
|
|
7
|
+
if (!startDate || !endDate) {
|
|
8
|
+
return res.status(400).json({ message: 'Start date and end date are required' });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const pool = getPool();
|
|
12
|
+
|
|
13
|
+
const [appointments] = await pool.query(
|
|
14
|
+
`SELECT a.id, a.appointment_date, a.appointment_time, a.status,
|
|
15
|
+
p.full_name AS patient_name,
|
|
16
|
+
u.name AS doctor_name
|
|
17
|
+
FROM appointments a
|
|
18
|
+
JOIN patients p ON p.id = a.patient_id
|
|
19
|
+
JOIN users u ON u.id = a.doctor_id
|
|
20
|
+
WHERE a.appointment_date BETWEEN ? AND ?
|
|
21
|
+
ORDER BY a.appointment_date, a.appointment_time`,
|
|
22
|
+
[startDate, endDate],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const [stats] = await pool.query(
|
|
26
|
+
`SELECT
|
|
27
|
+
COUNT(*) AS totalAppointments,
|
|
28
|
+
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS totalCompleted,
|
|
29
|
+
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS totalCancelled
|
|
30
|
+
FROM appointments
|
|
31
|
+
WHERE appointment_date BETWEEN ? AND ?`,
|
|
32
|
+
[startDate, endDate],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const [attended] = await pool.query(
|
|
36
|
+
`SELECT DISTINCT p.id, p.full_name AS patient_name, p.phone
|
|
37
|
+
FROM appointments a
|
|
38
|
+
JOIN patients p ON p.id = a.patient_id
|
|
39
|
+
WHERE a.status = 'completed' AND a.appointment_date BETWEEN ? AND ?
|
|
40
|
+
ORDER BY p.full_name`,
|
|
41
|
+
[startDate, endDate],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const [medicalReports] = await pool.query(
|
|
45
|
+
`SELECT mr.id, mr.diagnosis, mr.prescription, mr.report_date,
|
|
46
|
+
p.full_name AS patient_name,
|
|
47
|
+
u.name AS doctor_name
|
|
48
|
+
FROM medical_reports mr
|
|
49
|
+
JOIN patients p ON p.id = mr.patient_id
|
|
50
|
+
JOIN users u ON u.id = mr.doctor_id
|
|
51
|
+
WHERE mr.report_date BETWEEN ? AND ?
|
|
52
|
+
ORDER BY mr.report_date DESC`,
|
|
53
|
+
[startDate, endDate],
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
res.json({
|
|
57
|
+
startDate,
|
|
58
|
+
endDate,
|
|
59
|
+
summary: {
|
|
60
|
+
totalAppointments: Number(stats[0].totalAppointments),
|
|
61
|
+
totalCompleted: Number(stats[0].totalCompleted),
|
|
62
|
+
totalCancelled: Number(stats[0].totalCancelled),
|
|
63
|
+
patientsAttended: attended.length,
|
|
64
|
+
},
|
|
65
|
+
appointments,
|
|
66
|
+
patientsAttended: attended,
|
|
67
|
+
medicalReports,
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
res.status(500).json({ message: err.message });
|
|
71
|
+
}
|
|
72
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const { getPool } = require('../config/db');
|
|
2
|
+
|
|
3
|
+
function formatReport(row) {
|
|
4
|
+
return {
|
|
5
|
+
id: row.id,
|
|
6
|
+
appointmentId: row.appointment_id,
|
|
7
|
+
patientId: row.patient_id,
|
|
8
|
+
doctorId: row.doctor_id,
|
|
9
|
+
patientName: row.patient_name,
|
|
10
|
+
doctorName: row.doctor_name,
|
|
11
|
+
diagnosis: row.diagnosis,
|
|
12
|
+
prescription: row.prescription,
|
|
13
|
+
reportDate: row.report_date,
|
|
14
|
+
createdAt: row.created_at,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const reportSelect = `
|
|
19
|
+
SELECT mr.*,
|
|
20
|
+
p.full_name AS patient_name,
|
|
21
|
+
u.name AS doctor_name
|
|
22
|
+
FROM medical_reports mr
|
|
23
|
+
JOIN patients p ON p.id = mr.patient_id
|
|
24
|
+
JOIN users u ON u.id = mr.doctor_id
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
exports.getMedicalReports = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const pool = getPool();
|
|
30
|
+
let query = `${reportSelect}`;
|
|
31
|
+
const params = [];
|
|
32
|
+
|
|
33
|
+
if (req.user.role === 'doctor') {
|
|
34
|
+
query += ' WHERE mr.doctor_id = ?';
|
|
35
|
+
params.push(req.user.id);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
query += ' ORDER BY mr.report_date DESC';
|
|
39
|
+
|
|
40
|
+
const [rows] = await pool.query(query, params);
|
|
41
|
+
res.json({ reports: rows.map(formatReport) });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
res.status(500).json({ message: err.message });
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
exports.createMedicalReport = async (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const { appointmentId, diagnosis, prescription, reportDate } = req.body;
|
|
50
|
+
|
|
51
|
+
if (!appointmentId || !diagnosis || !prescription) {
|
|
52
|
+
return res.status(400).json({ message: 'Appointment, diagnosis, and prescription are required' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pool = getPool();
|
|
56
|
+
const [appointments] = await pool.query(
|
|
57
|
+
'SELECT * FROM appointments WHERE id = ?',
|
|
58
|
+
[appointmentId],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!appointments.length) {
|
|
62
|
+
return res.status(404).json({ message: 'Appointment not found' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const appointment = appointments[0];
|
|
66
|
+
|
|
67
|
+
if (req.user.role === 'doctor' && appointment.doctor_id !== req.user.id) {
|
|
68
|
+
return res.status(403).json({ message: 'You can only report on your assigned appointments' });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (appointment.status === 'cancelled') {
|
|
72
|
+
return res.status(400).json({ message: 'Cannot create report for cancelled appointment' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const [existingReport] = await pool.query(
|
|
76
|
+
'SELECT id FROM medical_reports WHERE appointment_id = ?',
|
|
77
|
+
[appointmentId],
|
|
78
|
+
);
|
|
79
|
+
if (existingReport.length) {
|
|
80
|
+
return res.status(409).json({ message: 'Medical report already exists for this appointment' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const finalReportDate = reportDate || new Date().toISOString().slice(0, 10);
|
|
84
|
+
|
|
85
|
+
const [result] = await pool.query(
|
|
86
|
+
`INSERT INTO medical_reports (appointment_id, patient_id, doctor_id, diagnosis, prescription, report_date)
|
|
87
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
88
|
+
[
|
|
89
|
+
appointmentId,
|
|
90
|
+
appointment.patient_id,
|
|
91
|
+
appointment.doctor_id,
|
|
92
|
+
diagnosis,
|
|
93
|
+
prescription,
|
|
94
|
+
finalReportDate,
|
|
95
|
+
],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await pool.query("UPDATE appointments SET status = 'completed' WHERE id = ?", [appointmentId]);
|
|
99
|
+
|
|
100
|
+
const [rows] = await pool.query(`${reportSelect} WHERE mr.id = ?`, [result.insertId]);
|
|
101
|
+
res.status(201).json({ report: formatReport(rows[0]) });
|
|
102
|
+
} catch (err) {
|
|
103
|
+
res.status(500).json({ message: err.message });
|
|
104
|
+
}
|
|
105
|
+
};
|