create-nodemin-app 1.0.16

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 (88) hide show
  1. package/bin/cli.js +82 -0
  2. package/package.json +25 -0
  3. package/templates/HRMS_Mongodb/README.md +331 -0
  4. package/templates/HRMS_Mongodb/backend/.env.example +6 -0
  5. package/templates/HRMS_Mongodb/backend/package-lock.json +1646 -0
  6. package/templates/HRMS_Mongodb/backend/package.json +26 -0
  7. package/templates/HRMS_Mongodb/backend/src/config/db.js +9 -0
  8. package/templates/HRMS_Mongodb/backend/src/controllers/authController.js +187 -0
  9. package/templates/HRMS_Mongodb/backend/src/controllers/departmentController.js +70 -0
  10. package/templates/HRMS_Mongodb/backend/src/controllers/employeeController.js +178 -0
  11. package/templates/HRMS_Mongodb/backend/src/controllers/positionController.js +66 -0
  12. package/templates/HRMS_Mongodb/backend/src/middleware/auth.js +57 -0
  13. package/templates/HRMS_Mongodb/backend/src/middleware/errorHandler.js +32 -0
  14. package/templates/HRMS_Mongodb/backend/src/middleware/restrictToAdmin.js +5 -0
  15. package/templates/HRMS_Mongodb/backend/src/middleware/validate.js +13 -0
  16. package/templates/HRMS_Mongodb/backend/src/models/Department.js +19 -0
  17. package/templates/HRMS_Mongodb/backend/src/models/Employee.js +81 -0
  18. package/templates/HRMS_Mongodb/backend/src/models/Position.js +19 -0
  19. package/templates/HRMS_Mongodb/backend/src/models/User.js +40 -0
  20. package/templates/HRMS_Mongodb/backend/src/routes/authRoutes.js +27 -0
  21. package/templates/HRMS_Mongodb/backend/src/routes/departmentRoutes.js +33 -0
  22. package/templates/HRMS_Mongodb/backend/src/routes/employeeRoutes.js +39 -0
  23. package/templates/HRMS_Mongodb/backend/src/routes/positionRoutes.js +32 -0
  24. package/templates/HRMS_Mongodb/backend/src/server.js +74 -0
  25. package/templates/HRMS_Mongodb/backend/src/utils/roles.js +5 -0
  26. package/templates/HRMS_Mongodb/backend/src/utils/seed.js +78 -0
  27. package/templates/HRMS_Mongodb/backend/src/validators/authValidator.js +61 -0
  28. package/templates/HRMS_Mongodb/backend/src/validators/departmentValidator.js +21 -0
  29. package/templates/HRMS_Mongodb/backend/src/validators/employeeValidator.js +27 -0
  30. package/templates/HRMS_Mongodb/backend/src/validators/positionValidator.js +26 -0
  31. package/templates/HRMS_Mongodb/frontend/index.html +19 -0
  32. package/templates/HRMS_Mongodb/frontend/package-lock.json +2812 -0
  33. package/templates/HRMS_Mongodb/frontend/package.json +25 -0
  34. package/templates/HRMS_Mongodb/frontend/public/favicon.svg +4 -0
  35. package/templates/HRMS_Mongodb/frontend/src/App.jsx +50 -0
  36. package/templates/HRMS_Mongodb/frontend/src/api/axios.js +54 -0
  37. package/templates/HRMS_Mongodb/frontend/src/components/ProtectedRoute.jsx +26 -0
  38. package/templates/HRMS_Mongodb/frontend/src/components/layout/DashboardLayout.jsx +16 -0
  39. package/templates/HRMS_Mongodb/frontend/src/components/layout/Sidebar.jsx +108 -0
  40. package/templates/HRMS_Mongodb/frontend/src/components/ui/Button.jsx +33 -0
  41. package/templates/HRMS_Mongodb/frontend/src/components/ui/Input.jsx +20 -0
  42. package/templates/HRMS_Mongodb/frontend/src/components/ui/Modal.jsx +48 -0
  43. package/templates/HRMS_Mongodb/frontend/src/components/ui/Select.jsx +27 -0
  44. package/templates/HRMS_Mongodb/frontend/src/context/AuthContext.jsx +97 -0
  45. package/templates/HRMS_Mongodb/frontend/src/index.css +34 -0
  46. package/templates/HRMS_Mongodb/frontend/src/main.jsx +16 -0
  47. package/templates/HRMS_Mongodb/frontend/src/pages/Dashboard.jsx +78 -0
  48. package/templates/HRMS_Mongodb/frontend/src/pages/Departments.jsx +144 -0
  49. package/templates/HRMS_Mongodb/frontend/src/pages/Employees.jsx +297 -0
  50. package/templates/HRMS_Mongodb/frontend/src/pages/LeaveReport.jsx +113 -0
  51. package/templates/HRMS_Mongodb/frontend/src/pages/Login.jsx +92 -0
  52. package/templates/HRMS_Mongodb/frontend/src/pages/Positions.jsx +157 -0
  53. package/templates/HRMS_Mongodb/frontend/src/pages/Register.jsx +93 -0
  54. package/templates/HRMS_Mongodb/frontend/src/pages/ResetPassword.jsx +135 -0
  55. package/templates/HRMS_Mongodb/frontend/src/utils/roles.js +1 -0
  56. package/templates/HRMS_Mongodb/frontend/src/utils/session.js +5 -0
  57. package/templates/HRMS_Mongodb/frontend/src/utils/validation.js +66 -0
  58. package/templates/HRMS_Mongodb/frontend/vite.config.js +16 -0
  59. package/templates/HRMS_Mysql/backend/db.js +13 -0
  60. package/templates/HRMS_Mysql/backend/package-lock.json +1614 -0
  61. package/templates/HRMS_Mysql/backend/package.json +21 -0
  62. package/templates/HRMS_Mysql/backend/server.js +421 -0
  63. package/templates/HRMS_Mysql/frontend/dist/assets/index-CtLtQf3_.js +75 -0
  64. package/templates/HRMS_Mysql/frontend/dist/assets/index-Dq1AXlEY.css +1 -0
  65. package/templates/HRMS_Mysql/frontend/dist/index.html +14 -0
  66. package/templates/HRMS_Mysql/frontend/dist/vite.svg +1 -0
  67. package/templates/HRMS_Mysql/frontend/index.html +13 -0
  68. package/templates/HRMS_Mysql/frontend/package-lock.json +2978 -0
  69. package/templates/HRMS_Mysql/frontend/package.json +25 -0
  70. package/templates/HRMS_Mysql/frontend/postcss.config.js +6 -0
  71. package/templates/HRMS_Mysql/frontend/public/vite.svg +1 -0
  72. package/templates/HRMS_Mysql/frontend/src/App.jsx +55 -0
  73. package/templates/HRMS_Mysql/frontend/src/api.js +11 -0
  74. package/templates/HRMS_Mysql/frontend/src/components/Layout.jsx +59 -0
  75. package/templates/HRMS_Mysql/frontend/src/index.css +7 -0
  76. package/templates/HRMS_Mysql/frontend/src/main.jsx +13 -0
  77. package/templates/HRMS_Mysql/frontend/src/pages/Dashboard.jsx +45 -0
  78. package/templates/HRMS_Mysql/frontend/src/pages/Departments.jsx +108 -0
  79. package/templates/HRMS_Mysql/frontend/src/pages/EmployeeStatusReport.jsx +72 -0
  80. package/templates/HRMS_Mysql/frontend/src/pages/Employees.jsx +252 -0
  81. package/templates/HRMS_Mysql/frontend/src/pages/ForgotPassword.jsx +66 -0
  82. package/templates/HRMS_Mysql/frontend/src/pages/Login.jsx +79 -0
  83. package/templates/HRMS_Mysql/frontend/src/pages/Positions.jsx +109 -0
  84. package/templates/HRMS_Mysql/frontend/src/pages/Register.jsx +95 -0
  85. package/templates/HRMS_Mysql/frontend/src/pages/Users.jsx +133 -0
  86. package/templates/HRMS_Mysql/frontend/tailwind.config.js +26 -0
  87. package/templates/HRMS_Mysql/frontend/vite.config.js +15 -0
  88. package/templates/HRMS_Mysql/hrms_schema.sql +57 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "hrms-backend",
3
+ "version": "1.0.0",
4
+ "description": "HRMS Backend API",
5
+ "main": "server.js",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "dev": "nodemon server.js"
9
+ },
10
+ "dependencies": {
11
+ "axios": "^1.6.0",
12
+ "bcryptjs": "^2.4.3",
13
+ "cors": "^2.8.5",
14
+ "express": "^4.18.2",
15
+ "express-session": "^1.17.3",
16
+ "mysql2": "^3.6.0"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.0.1"
20
+ }
21
+ }
@@ -0,0 +1,421 @@
1
+ const express = require('express');
2
+ const session = require('express-session');
3
+ const cors = require('cors');
4
+ const path = require('path');
5
+ const bcrypt = require('bcryptjs');
6
+ const db = require('./db');
7
+
8
+ const app = express();
9
+ const PORT = process.env.PORT || 5000;
10
+
11
+ app.use(cors({
12
+ origin: 'http://localhost:5173',
13
+ credentials: true
14
+ }));
15
+ app.use(express.json());
16
+ app.use(express.urlencoded({ extended: true }));
17
+
18
+ app.use(session({
19
+ secret: 'hrms_secret_key_2024',
20
+ resave: false,
21
+ saveUninitialized: false,
22
+ cookie: {
23
+ secure: false,
24
+ maxAge: 24 * 60 * 60 * 1000
25
+ }
26
+ }));
27
+
28
+ function requireAuth(req, res, next) {
29
+ if (!req.session.userId) {
30
+ return res.status(401).json({ error: 'Unauthorized' });
31
+ }
32
+ next();
33
+ }
34
+
35
+ // ─── AUTH ROUTES ───
36
+
37
+ app.post('/api/register', async (req, res) => {
38
+ try {
39
+ const { firstName, lastName, email, username, password } = req.body;
40
+ if (!firstName || !lastName || !email || !username || !password) {
41
+ return res.status(400).json({ error: 'All fields are required' });
42
+ }
43
+ const [existing] = await db.query('SELECT UserID FROM Users WHERE UserName = ?', [username]);
44
+ if (existing.length > 0) {
45
+ return res.status(400).json({ error: 'Username already exists' });
46
+ }
47
+ const [emailExists] = await db.query('SELECT EmpID FROM Employee WHERE EmpEmail = ?', [email]);
48
+ if (emailExists.length > 0) {
49
+ return res.status(400).json({ error: 'Email already in use' });
50
+ }
51
+ const [empResult] = await db.query(
52
+ 'INSERT INTO Employee (EmpFirstName, EmpLastName, EmpEmail, EmpStatus) VALUES (?, ?, ?, ?)',
53
+ [firstName, lastName, email, 'on leave']
54
+ );
55
+ const hashed = await bcrypt.hash(password, 10);
56
+ const [userResult] = await db.query(
57
+ 'INSERT INTO Users (EmpID, UserName, Password) VALUES (?, ?, ?)',
58
+ [empResult.insertId, username, hashed]
59
+ );
60
+ req.session.userId = userResult.insertId;
61
+ req.session.userName = username;
62
+ req.session.empName = firstName;
63
+ res.json({
64
+ success: true,
65
+ user: { id: userResult.insertId, username, name: firstName }
66
+ });
67
+ } catch (err) {
68
+ res.status(500).json({ error: err.message });
69
+ }
70
+ });
71
+
72
+ app.post('/api/login', async (req, res) => {
73
+ try {
74
+ const { username, password } = req.body;
75
+ const [rows] = await db.query('SELECT u.*, e.EmpFirstName, e.EmpEmail FROM Users u JOIN Employee e ON u.EmpID = e.EmpID WHERE u.UserName = ?', [username]);
76
+ if (rows.length === 0) {
77
+ return res.status(401).json({ error: 'Invalid credentials' });
78
+ }
79
+ const user = rows[0];
80
+ const valid = await bcrypt.compare(password, user.Password);
81
+ if (!valid) {
82
+ return res.status(401).json({ error: 'Invalid credentials' });
83
+ }
84
+ req.session.userId = user.UserID;
85
+ req.session.userName = user.UserName;
86
+ req.session.empName = user.EmpFirstName;
87
+ res.json({ success: true, user: { id: user.UserID, username: user.UserName, name: user.EmpFirstName } });
88
+ } catch (err) {
89
+ res.status(500).json({ error: err.message });
90
+ }
91
+ });
92
+
93
+ app.post('/api/logout', (req, res) => {
94
+ req.session.destroy();
95
+ res.json({ success: true });
96
+ });
97
+
98
+ app.get('/api/me', requireAuth, async (req, res) => {
99
+ try {
100
+ const [rows] = await db.query(
101
+ 'SELECT u.UserID, u.UserName, e.EmpFirstName, e.EmpLastName, e.EmpEmail FROM Users u JOIN Employee e ON u.EmpID = e.EmpID WHERE u.UserID = ?',
102
+ [req.session.userId]
103
+ );
104
+ if (rows.length === 0) return res.status(404).json({ error: 'User not found' });
105
+ res.json(rows[0]);
106
+ } catch (err) {
107
+ res.status(500).json({ error: err.message });
108
+ }
109
+ });
110
+
111
+ app.post('/api/forgot-password', async (req, res) => {
112
+ try {
113
+ const { email } = req.body;
114
+ const [rows] = await db.query(
115
+ 'SELECT u.UserID, u.UserName, e.EmpEmail FROM Users u JOIN Employee e ON u.EmpID = e.EmpID WHERE e.EmpEmail = ?',
116
+ [email]
117
+ );
118
+ if (rows.length === 0) {
119
+ return res.status(404).json({ error: 'Email not found' });
120
+ }
121
+ const user = rows[0];
122
+ const tempPassword = Math.random().toString(36).slice(-8);
123
+ const hashed = await bcrypt.hash(tempPassword, 10);
124
+ await db.query('UPDATE Users SET Password = ? WHERE UserID = ?', [hashed, user.UserID]);
125
+ res.json({
126
+ success: true,
127
+ message: `Your temporary password has been sent to ${email}`,
128
+ tempPassword: tempPassword
129
+ });
130
+ } catch (err) {
131
+ res.status(500).json({ error: err.message });
132
+ }
133
+ });
134
+
135
+ app.post('/api/reset-password', requireAuth, async (req, res) => {
136
+ try {
137
+ const { currentPassword, newPassword } = req.body;
138
+ const [rows] = await db.query('SELECT Password FROM Users WHERE UserID = ?', [req.session.userId]);
139
+ if (rows.length === 0) return res.status(404).json({ error: 'User not found' });
140
+ const valid = await bcrypt.compare(currentPassword, rows[0].Password);
141
+ if (!valid) return res.status(401).json({ error: 'Current password is incorrect' });
142
+ const hashed = await bcrypt.hash(newPassword, 10);
143
+ await db.query('UPDATE Users SET Password = ? WHERE UserID = ?', [hashed, req.session.userId]);
144
+ res.json({ success: true, message: 'Password updated successfully' });
145
+ } catch (err) {
146
+ res.status(500).json({ error: err.message });
147
+ }
148
+ });
149
+
150
+ // ─── DEPARTMENT CRUD ───
151
+
152
+ app.get('/api/departments', requireAuth, async (req, res) => {
153
+ try {
154
+ const [rows] = await db.query('SELECT * FROM Department ORDER BY DepartName');
155
+ res.json(rows);
156
+ } catch (err) {
157
+ res.status(500).json({ error: err.message });
158
+ }
159
+ });
160
+
161
+ app.get('/api/departments/:id', requireAuth, async (req, res) => {
162
+ try {
163
+ const [rows] = await db.query('SELECT * FROM Department WHERE DepartID = ?', [req.params.id]);
164
+ if (rows.length === 0) return res.status(404).json({ error: 'Department not found' });
165
+ res.json(rows[0]);
166
+ } catch (err) {
167
+ res.status(500).json({ error: err.message });
168
+ }
169
+ });
170
+
171
+ app.post('/api/departments', requireAuth, async (req, res) => {
172
+ try {
173
+ const { DepartName } = req.body;
174
+ const [result] = await db.query('INSERT INTO Department (DepartName) VALUES (?)', [DepartName]);
175
+ res.json({ success: true, id: result.insertId });
176
+ } catch (err) {
177
+ res.status(500).json({ error: err.message });
178
+ }
179
+ });
180
+
181
+ app.put('/api/departments/:id', requireAuth, async (req, res) => {
182
+ try {
183
+ const { DepartName } = req.body;
184
+ await db.query('UPDATE Department SET DepartName = ? WHERE DepartID = ?', [DepartName, req.params.id]);
185
+ res.json({ success: true });
186
+ } catch (err) {
187
+ res.status(500).json({ error: err.message });
188
+ }
189
+ });
190
+
191
+ app.delete('/api/departments/:id', requireAuth, async (req, res) => {
192
+ try {
193
+ await db.query('DELETE FROM Department WHERE DepartID = ?', [req.params.id]);
194
+ res.json({ success: true });
195
+ } catch (err) {
196
+ res.status(500).json({ error: err.message });
197
+ }
198
+ });
199
+
200
+ // ─── POSITION CRUD ───
201
+
202
+ app.get('/api/positions', requireAuth, async (req, res) => {
203
+ try {
204
+ const [rows] = await db.query('SELECT * FROM Positionemp ORDER BY PosName');
205
+ res.json(rows);
206
+ } catch (err) {
207
+ res.status(500).json({ error: err.message });
208
+ }
209
+ });
210
+
211
+ app.get('/api/positions/:id', requireAuth, async (req, res) => {
212
+ try {
213
+ const [rows] = await db.query('SELECT * FROM Positionemp WHERE PosID = ?', [req.params.id]);
214
+ if (rows.length === 0) return res.status(404).json({ error: 'Position not found' });
215
+ res.json(rows[0]);
216
+ } catch (err) {
217
+ res.status(500).json({ error: err.message });
218
+ }
219
+ });
220
+
221
+ app.post('/api/positions', requireAuth, async (req, res) => {
222
+ try {
223
+ const { PosName, RequiredQualification } = req.body;
224
+ const [result] = await db.query('INSERT INTO Positionemp (PosName, RequiredQualification) VALUES (?, ?)', [PosName, RequiredQualification]);
225
+ res.json({ success: true, id: result.insertId });
226
+ } catch (err) {
227
+ res.status(500).json({ error: err.message });
228
+ }
229
+ });
230
+
231
+ app.put('/api/positions/:id', requireAuth, async (req, res) => {
232
+ try {
233
+ const { PosName, RequiredQualification } = req.body;
234
+ await db.query('UPDATE Positionemp SET PosName = ?, RequiredQualification = ? WHERE PosID = ?', [PosName, RequiredQualification, req.params.id]);
235
+ res.json({ success: true });
236
+ } catch (err) {
237
+ res.status(500).json({ error: err.message });
238
+ }
239
+ });
240
+
241
+ app.delete('/api/positions/:id', requireAuth, async (req, res) => {
242
+ try {
243
+ await db.query('DELETE FROM Positionemp WHERE PosID = ?', [req.params.id]);
244
+ res.json({ success: true });
245
+ } catch (err) {
246
+ res.status(500).json({ error: err.message });
247
+ }
248
+ });
249
+
250
+ // ─── EMPLOYEE CRUD ───
251
+
252
+ app.get('/api/employees', requireAuth, async (req, res) => {
253
+ try {
254
+ const { search } = req.query;
255
+ let query = `
256
+ SELECT e.*, d.DepartName, p.PosName
257
+ FROM Employee e
258
+ LEFT JOIN Department d ON e.DepartID = d.DepartID
259
+ LEFT JOIN Positionemp p ON e.PosID = p.PosID
260
+ `;
261
+ let params = [];
262
+ if (search) {
263
+ query += ' WHERE e.EmpFirstName LIKE ? OR e.EmpLastName LIKE ? OR e.EmpEmail LIKE ? OR e.EmpTelephone LIKE ?';
264
+ const s = `%${search}%`;
265
+ params = [s, s, s, s];
266
+ }
267
+ query += ' ORDER BY e.EmpLastName, e.EmpFirstName';
268
+ const [rows] = await db.query(query, params);
269
+ res.json(rows);
270
+ } catch (err) {
271
+ res.status(500).json({ error: err.message });
272
+ }
273
+ });
274
+
275
+ app.get('/api/employees/:id', requireAuth, async (req, res) => {
276
+ try {
277
+ const [rows] = await db.query(`
278
+ SELECT e.*, d.DepartName, p.PosName
279
+ FROM Employee e
280
+ LEFT JOIN Department d ON e.DepartID = d.DepartID
281
+ LEFT JOIN Positionemp p ON e.PosID = p.PosID
282
+ WHERE e.EmpID = ?
283
+ `, [req.params.id]);
284
+ if (rows.length === 0) return res.status(404).json({ error: 'Employee not found' });
285
+ res.json(rows[0]);
286
+ } catch (err) {
287
+ res.status(500).json({ error: err.message });
288
+ }
289
+ });
290
+
291
+ app.post('/api/employees', requireAuth, async (req, res) => {
292
+ try {
293
+ const { EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth, EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus, DepartID, PosID } = req.body;
294
+ const [result] = await db.query(
295
+ 'INSERT INTO Employee (EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth, EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus, DepartID, PosID) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
296
+ [EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth, EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus || 'on leave', DepartID || null, PosID || null]
297
+ );
298
+ res.json({ success: true, id: result.insertId });
299
+ } catch (err) {
300
+ res.status(500).json({ error: err.message });
301
+ }
302
+ });
303
+
304
+ app.put('/api/employees/:id', requireAuth, async (req, res) => {
305
+ try {
306
+ const { EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth, EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus, DepartID, PosID } = req.body;
307
+ await db.query(
308
+ 'UPDATE Employee SET EmpFirstName=?, EmpLastName=?, EmpGender=?, EmpDateOfBirth=?, EmpEmail=?, EmpTelephone=?, EmpAddress=?, EmpHireDate=?, EmpStatus=?, DepartID=?, PosID=? WHERE EmpID=?',
309
+ [EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth, EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus, DepartID || null, PosID || null, req.params.id]
310
+ );
311
+ res.json({ success: true });
312
+ } catch (err) {
313
+ res.status(500).json({ error: err.message });
314
+ }
315
+ });
316
+
317
+ app.delete('/api/employees/:id', requireAuth, async (req, res) => {
318
+ try {
319
+ await db.query('DELETE FROM Employee WHERE EmpID = ?', [req.params.id]);
320
+ res.json({ success: true });
321
+ } catch (err) {
322
+ res.status(500).json({ error: err.message });
323
+ }
324
+ });
325
+
326
+ // ─── USERS CRUD ───
327
+
328
+ app.get('/api/users', requireAuth, async (req, res) => {
329
+ try {
330
+ const [rows] = await db.query(`
331
+ SELECT u.UserID, u.UserName, u.EmpID, e.EmpFirstName, e.EmpLastName, e.EmpEmail
332
+ FROM Users u
333
+ JOIN Employee e ON u.EmpID = e.EmpID
334
+ ORDER BY u.UserName
335
+ `);
336
+ res.json(rows);
337
+ } catch (err) {
338
+ res.status(500).json({ error: err.message });
339
+ }
340
+ });
341
+
342
+ app.get('/api/users/:id', requireAuth, async (req, res) => {
343
+ try {
344
+ const [rows] = await db.query(`
345
+ SELECT u.UserID, u.UserName, u.EmpID, e.EmpFirstName, e.EmpLastName, e.EmpEmail
346
+ FROM Users u
347
+ JOIN Employee e ON u.EmpID = e.EmpID
348
+ WHERE u.UserID = ?
349
+ `, [req.params.id]);
350
+ if (rows.length === 0) return res.status(404).json({ error: 'User not found' });
351
+ res.json(rows[0]);
352
+ } catch (err) {
353
+ res.status(500).json({ error: err.message });
354
+ }
355
+ });
356
+
357
+ app.post('/api/users', requireAuth, async (req, res) => {
358
+ try {
359
+ const { EmpID, UserName, Password } = req.body;
360
+ const hashed = await bcrypt.hash(Password, 10);
361
+ const [result] = await db.query('INSERT INTO Users (EmpID, UserName, Password) VALUES (?, ?, ?)', [EmpID, UserName, hashed]);
362
+ res.json({ success: true, id: result.insertId });
363
+ } catch (err) {
364
+ res.status(500).json({ error: err.message });
365
+ }
366
+ });
367
+
368
+ app.put('/api/users/:id', requireAuth, async (req, res) => {
369
+ try {
370
+ const { UserName, Password } = req.body;
371
+ if (Password) {
372
+ const hashed = await bcrypt.hash(Password, 10);
373
+ await db.query('UPDATE Users SET UserName = ?, Password = ? WHERE UserID = ?', [UserName, hashed, req.params.id]);
374
+ } else {
375
+ await db.query('UPDATE Users SET UserName = ? WHERE UserID = ?', [UserName, req.params.id]);
376
+ }
377
+ res.json({ success: true });
378
+ } catch (err) {
379
+ res.status(500).json({ error: err.message });
380
+ }
381
+ });
382
+
383
+ app.delete('/api/users/:id', requireAuth, async (req, res) => {
384
+ try {
385
+ await db.query('DELETE FROM Users WHERE UserID = ?', [req.params.id]);
386
+ res.json({ success: true });
387
+ } catch (err) {
388
+ res.status(500).json({ error: err.message });
389
+ }
390
+ });
391
+
392
+ // ─── REPORTS ───
393
+
394
+ app.get('/api/reports/employees-on-leave', requireAuth, async (req, res) => {
395
+ try {
396
+ const [rows] = await db.query(`
397
+ SELECT e.*, d.DepartName, p.PosName
398
+ FROM Employee e
399
+ LEFT JOIN Department d ON e.DepartID = d.DepartID
400
+ LEFT JOIN Positionemp p ON e.PosID = p.PosID
401
+ WHERE e.EmpStatus = 'on leave'
402
+ ORDER BY d.DepartName, e.EmpLastName, e.EmpFirstName
403
+ `);
404
+ const departments = {};
405
+ for (const emp of rows) {
406
+ const dept = emp.DepartName || 'No Department';
407
+ if (!departments[dept]) departments[dept] = [];
408
+ departments[dept].push(emp);
409
+ }
410
+ res.json({
411
+ total: rows.length,
412
+ departments
413
+ });
414
+ } catch (err) {
415
+ res.status(500).json({ error: err.message });
416
+ }
417
+ });
418
+
419
+ app.listen(PORT, () => {
420
+ console.log(`HRMS Backend running on port ${PORT}`);
421
+ });