create-jinmankn-app 1.0.13 → 1.0.14
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/package.json +1 -1
- package/templates/HRMS (reba kuri iyi bro)/README.md +1 -0
- package/templates/{index/backend/controllers/authController.js → HRMS (reba kuri iyi bro)/backend/controllers/authController.js } +60 -10
- package/templates/HRMS (reba kuri iyi bro)/backend/middleware/auth.js +28 -0
- package/templates/{index/backend/models/User.js → HRMS (reba kuri iyi bro)/backend/models/User.js } +5 -0
- package/templates/{index/backend/package-lock.json → HRMS (reba kuri iyi bro)/backend/package-lock.json } +54 -13
- package/templates/{index/backend/package.json → HRMS (reba kuri iyi bro)/backend/package.json } +1 -0
- package/templates/HRMS (reba kuri iyi bro)/backend/routes/authRoutes.js +13 -0
- package/templates/{index/backend/routes/departmentRoutes.js → HRMS (reba kuri iyi bro)/backend/routes/departmentRoutes.js } +4 -4
- package/templates/{index/backend/routes/employeeRoutes.js → HRMS (reba kuri iyi bro)/backend/routes/employeeRoutes.js } +4 -4
- package/templates/{index/backend/routes/positionRoutes.js → HRMS (reba kuri iyi bro)/backend/routes/positionRoutes.js } +4 -4
- package/templates/{index/backend/routes/reportRoutes.js → HRMS (reba kuri iyi bro)/backend/routes/reportRoutes.js } +2 -2
- package/templates/{index/backend/server.js → HRMS (reba kuri iyi bro)/backend/server.js } +20 -1
- package/templates/{index/frontend/src/App.jsx → HRMS (reba kuri iyi bro)/frontend/src/App.jsx } +3 -1
- package/templates/{index/frontend/src/components/DashboardLayout.jsx → HRMS (reba kuri iyi bro)/frontend/src/components/DashboardLayout.jsx } +23 -5
- package/templates/{index/frontend/src/components/ProtectedRoute.jsx → HRMS (reba kuri iyi bro)/frontend/src/components/ProtectedRoute.jsx } +2 -2
- package/templates/HRMS (reba kuri iyi bro)/frontend/src/pages/CreateUser.jsx +149 -0
- package/templates/{index/frontend/src/pages/DashboardHome.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/DashboardHome.jsx } +3 -4
- package/templates/{index/frontend/src/pages/Department.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Department.jsx } +45 -35
- package/templates/{index/frontend/src/pages/Employee.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Employee.jsx } +26 -16
- package/templates/{index/frontend/src/pages/Login.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Login.jsx } +7 -4
- package/templates/{index/frontend/src/pages/Position.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Position.jsx } +24 -14
- package/templates/{index/frontend/src/pages/Register.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Register.jsx } +10 -7
- package/templates/{index/frontend/src/pages/Reports.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Reports.jsx } +2 -4
- package/templates/index/README.md +0 -34
- package/templates/index/backend/middleware/auth.js +0 -27
- package/templates/index/backend/routes/authRoutes.js +0 -11
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/config/db.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/departmentController.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/employeeController.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/positionController.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/reportController.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/models/Department.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/models/Employee.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/models/Position.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/backend/routes/protectedRoutes.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/README.md +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/eslint.config.js +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/index.html +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/package-lock.json +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/package.json +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/public/favicon.svg +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/src/App.css +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/src/assets/hero.png +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/src/index.css +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/src/main.jsx +0 -0
- /package/templates/{index → HRMS (reba kuri iyi bro)}/frontend/vite.config.js +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
umvax mbaye nshyizeho iyi reka mbanze nkore test numona i dakora fresh uze gusubiroho after 10min ndaba naya updatinze
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
const bcrypt = require('bcrypt');
|
|
2
|
-
const jwt = require('jsonwebtoken');
|
|
3
2
|
const User = require('../models/User');
|
|
4
|
-
const
|
|
3
|
+
const Employee = require('../models/Employee');
|
|
5
4
|
|
|
6
|
-
function createToken(userId) {
|
|
7
|
-
return jwt.sign({ userId }, JWT_SECRET, { expiresIn: '7d' });
|
|
8
|
-
}
|
|
9
|
-
//this function is formatting the user object to return the id, name, and email when
|
|
10
5
|
function formatUser(user) {
|
|
11
6
|
return {
|
|
12
7
|
id: user._id,
|
|
13
8
|
name: user.name,
|
|
14
9
|
email: user.email,
|
|
10
|
+
role: user.role || 'user',
|
|
11
|
+
employee: user.employee || null,
|
|
15
12
|
};
|
|
16
13
|
}
|
|
17
14
|
|
|
@@ -32,17 +29,57 @@ exports.register = async (req, res) => {
|
|
|
32
29
|
return res.status(409).json({ message: 'Email already registered' });
|
|
33
30
|
}
|
|
34
31
|
|
|
32
|
+
const userCount = await User.countDocuments();
|
|
33
|
+
if (userCount > 0) {
|
|
34
|
+
return res.status(403).json({ message: 'Registration closed. Ask an admin to create an account.' });
|
|
35
|
+
}
|
|
36
|
+
|
|
35
37
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
36
38
|
const user = await User.create({
|
|
37
39
|
name: name.trim(),
|
|
38
40
|
email: email.trim().toLowerCase(),
|
|
39
41
|
password: hashedPassword,
|
|
42
|
+
role: 'admin',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
req.session.userId = user._id;
|
|
46
|
+
|
|
47
|
+
res.status(201).json({
|
|
48
|
+
user: formatUser(user),
|
|
40
49
|
});
|
|
50
|
+
} catch (err) {
|
|
51
|
+
res.status(500).json({ message: err.message });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
exports.createUser = async (req, res) => {
|
|
56
|
+
try {
|
|
57
|
+
const { employeeId, password } = req.body;
|
|
58
|
+
|
|
59
|
+
if (!employeeId || !password) {
|
|
60
|
+
return res.status(400).json({ message: 'Employee and password are required' });
|
|
61
|
+
}
|
|
41
62
|
|
|
42
|
-
const
|
|
63
|
+
const employee = await Employee.findById(employeeId);
|
|
64
|
+
if (!employee) {
|
|
65
|
+
return res.status(404).json({ message: 'Employee not found' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const existing = await User.findOne({ email: employee.email });
|
|
69
|
+
if (existing) {
|
|
70
|
+
return res.status(409).json({ message: 'A user already exists for this employee' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
74
|
+
const user = await User.create({
|
|
75
|
+
name: `${employee.firstName} ${employee.lastName}`.trim(),
|
|
76
|
+
email: employee.email,
|
|
77
|
+
password: hashedPassword,
|
|
78
|
+
role: 'user',
|
|
79
|
+
employee: employee._id,
|
|
80
|
+
});
|
|
43
81
|
|
|
44
82
|
res.status(201).json({
|
|
45
|
-
token,
|
|
46
83
|
user: formatUser(user),
|
|
47
84
|
});
|
|
48
85
|
} catch (err) {
|
|
@@ -68,10 +105,9 @@ exports.login = async (req, res) => {
|
|
|
68
105
|
return res.status(401).json({ message: 'Invalid email or password' });
|
|
69
106
|
}
|
|
70
107
|
|
|
71
|
-
|
|
108
|
+
req.session.userId = user._id;
|
|
72
109
|
|
|
73
110
|
res.json({
|
|
74
|
-
token,
|
|
75
111
|
user: formatUser(user),
|
|
76
112
|
});
|
|
77
113
|
} catch (err) {
|
|
@@ -86,3 +122,17 @@ exports.getMe = async (req, res) => {
|
|
|
86
122
|
res.status(500).json({ message: err.message });
|
|
87
123
|
}
|
|
88
124
|
};
|
|
125
|
+
|
|
126
|
+
exports.logout = async (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
req.session.destroy((err) => {
|
|
129
|
+
if (err) {
|
|
130
|
+
return res.status(500).json({ message: 'Logout failed' });
|
|
131
|
+
}
|
|
132
|
+
res.clearCookie('connect.sid');
|
|
133
|
+
res.json({ message: 'Logged out successfully' });
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
res.status(500).json({ message: err.message });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
const User = require('../models/User');
|
|
3
|
+
|
|
4
|
+
exports.protect = async (req, res, next) => {
|
|
5
|
+
try {
|
|
6
|
+
if (!req.session || !req.session.userId) {
|
|
7
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const user = await User.findById(req.session.userId).select('-password');
|
|
11
|
+
|
|
12
|
+
if (!user) {
|
|
13
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
req.user = user;
|
|
17
|
+
next();
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return res.status(401).json({ message: 'Not authorized' });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
exports.admin = (req, res, next) => {
|
|
24
|
+
if (!req.user || req.user.role !== 'admin') {
|
|
25
|
+
return res.status(403).json({ message: 'Admin access required' });
|
|
26
|
+
}
|
|
27
|
+
next();
|
|
28
|
+
};
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"bcrypt": "^5.1.1",
|
|
13
13
|
"cors": "^2.8.5",
|
|
14
|
-
"dotenv": "^16.4.7",
|
|
15
14
|
"express": "^4.21.2",
|
|
15
|
+
"express-session": "^1.19.0",
|
|
16
16
|
"jsonwebtoken": "^9.0.2",
|
|
17
17
|
"mongoose": "^8.9.3"
|
|
18
18
|
},
|
|
@@ -458,18 +458,6 @@
|
|
|
458
458
|
"node": ">=8"
|
|
459
459
|
}
|
|
460
460
|
},
|
|
461
|
-
"node_modules/dotenv": {
|
|
462
|
-
"version": "16.6.1",
|
|
463
|
-
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
|
|
464
|
-
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
|
|
465
|
-
"license": "BSD-2-Clause",
|
|
466
|
-
"engines": {
|
|
467
|
-
"node": ">=12"
|
|
468
|
-
},
|
|
469
|
-
"funding": {
|
|
470
|
-
"url": "https://dotenvx.com"
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
461
|
"node_modules/dunder-proto": {
|
|
474
462
|
"version": "1.0.1",
|
|
475
463
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
@@ -605,6 +593,29 @@
|
|
|
605
593
|
"url": "https://opencollective.com/express"
|
|
606
594
|
}
|
|
607
595
|
},
|
|
596
|
+
"node_modules/express-session": {
|
|
597
|
+
"version": "1.19.0",
|
|
598
|
+
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz",
|
|
599
|
+
"integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==",
|
|
600
|
+
"license": "MIT",
|
|
601
|
+
"dependencies": {
|
|
602
|
+
"cookie": "~0.7.2",
|
|
603
|
+
"cookie-signature": "~1.0.7",
|
|
604
|
+
"debug": "~2.6.9",
|
|
605
|
+
"depd": "~2.0.0",
|
|
606
|
+
"on-headers": "~1.1.0",
|
|
607
|
+
"parseurl": "~1.3.3",
|
|
608
|
+
"safe-buffer": "~5.2.1",
|
|
609
|
+
"uid-safe": "~2.1.5"
|
|
610
|
+
},
|
|
611
|
+
"engines": {
|
|
612
|
+
"node": ">= 0.8.0"
|
|
613
|
+
},
|
|
614
|
+
"funding": {
|
|
615
|
+
"type": "opencollective",
|
|
616
|
+
"url": "https://opencollective.com/express"
|
|
617
|
+
}
|
|
618
|
+
},
|
|
608
619
|
"node_modules/fill-range": {
|
|
609
620
|
"version": "7.1.1",
|
|
610
621
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
@@ -1613,6 +1624,15 @@
|
|
|
1613
1624
|
"node": ">= 0.8"
|
|
1614
1625
|
}
|
|
1615
1626
|
},
|
|
1627
|
+
"node_modules/on-headers": {
|
|
1628
|
+
"version": "1.1.0",
|
|
1629
|
+
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
|
1630
|
+
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
|
1631
|
+
"license": "MIT",
|
|
1632
|
+
"engines": {
|
|
1633
|
+
"node": ">= 0.8"
|
|
1634
|
+
}
|
|
1635
|
+
},
|
|
1616
1636
|
"node_modules/once": {
|
|
1617
1637
|
"version": "1.4.0",
|
|
1618
1638
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
|
@@ -1703,6 +1723,15 @@
|
|
|
1703
1723
|
"url": "https://github.com/sponsors/ljharb"
|
|
1704
1724
|
}
|
|
1705
1725
|
},
|
|
1726
|
+
"node_modules/random-bytes": {
|
|
1727
|
+
"version": "1.0.0",
|
|
1728
|
+
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
|
1729
|
+
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
|
1730
|
+
"license": "MIT",
|
|
1731
|
+
"engines": {
|
|
1732
|
+
"node": ">= 0.8"
|
|
1733
|
+
}
|
|
1734
|
+
},
|
|
1706
1735
|
"node_modules/range-parser": {
|
|
1707
1736
|
"version": "1.2.1",
|
|
1708
1737
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
|
@@ -2103,6 +2132,18 @@
|
|
|
2103
2132
|
"node": ">= 0.6"
|
|
2104
2133
|
}
|
|
2105
2134
|
},
|
|
2135
|
+
"node_modules/uid-safe": {
|
|
2136
|
+
"version": "2.1.5",
|
|
2137
|
+
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
|
2138
|
+
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
|
2139
|
+
"license": "MIT",
|
|
2140
|
+
"dependencies": {
|
|
2141
|
+
"random-bytes": "~1.0.0"
|
|
2142
|
+
},
|
|
2143
|
+
"engines": {
|
|
2144
|
+
"node": ">= 0.8"
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2106
2147
|
"node_modules/undefsafe": {
|
|
2107
2148
|
"version": "2.0.5",
|
|
2108
2149
|
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { register, login, getMe, createUser, logout } = require('../controllers/authController');
|
|
3
|
+
const { protect, admin } = require('../middleware/auth');
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
router.post('/register', register);
|
|
8
|
+
router.post('/login', login);
|
|
9
|
+
router.post('/logout', protect, logout);
|
|
10
|
+
router.post('/users', protect, admin, createUser);
|
|
11
|
+
router.get('/me', protect, getMe);
|
|
12
|
+
|
|
13
|
+
module.exports = router;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const { protect } = require('../middleware/auth');
|
|
3
|
+
const { protect, admin } = require('../middleware/auth');
|
|
4
4
|
const {
|
|
5
5
|
addDepartment,
|
|
6
6
|
getDepartments,
|
|
@@ -9,9 +9,9 @@ const {
|
|
|
9
9
|
} = require('../controllers/departmentController');
|
|
10
10
|
|
|
11
11
|
router.use(protect);
|
|
12
|
-
router.post('/add', addDepartment);
|
|
12
|
+
router.post('/add', admin, addDepartment);
|
|
13
13
|
router.get('/list', getDepartments);
|
|
14
|
-
router.put('/:id', updateDepartment);
|
|
15
|
-
router.delete('/:id', deleteDepartment);
|
|
14
|
+
router.put('/:id', admin, updateDepartment);
|
|
15
|
+
router.delete('/:id', admin, deleteDepartment);
|
|
16
16
|
|
|
17
17
|
module.exports = router;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const { protect } = require('../middleware/auth');
|
|
3
|
+
const { protect, admin } = require('../middleware/auth');
|
|
4
4
|
const {
|
|
5
5
|
addEmployee,
|
|
6
6
|
getEmployees,
|
|
@@ -10,10 +10,10 @@ const {
|
|
|
10
10
|
} = require('../controllers/employeeController');
|
|
11
11
|
|
|
12
12
|
router.use(protect);
|
|
13
|
-
router.post('/add', addEmployee);
|
|
13
|
+
router.post('/add', admin, addEmployee);
|
|
14
14
|
router.get('/list', getEmployees);
|
|
15
15
|
router.get('/:id', getEmployee);
|
|
16
|
-
router.put('/:id', updateEmployee);
|
|
17
|
-
router.delete('/:id', deleteEmployee);
|
|
16
|
+
router.put('/:id', admin, updateEmployee);
|
|
17
|
+
router.delete('/:id', admin, deleteEmployee);
|
|
18
18
|
|
|
19
19
|
module.exports = router;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const router = express.Router();
|
|
3
|
-
const { protect } = require('../middleware/auth');
|
|
3
|
+
const { protect, admin } = require('../middleware/auth');
|
|
4
4
|
const {
|
|
5
5
|
addPosition,
|
|
6
6
|
getPositions,
|
|
@@ -9,9 +9,9 @@ const {
|
|
|
9
9
|
} = require('../controllers/positionController');
|
|
10
10
|
|
|
11
11
|
router.use(protect);
|
|
12
|
-
router.post('/add', addPosition);
|
|
12
|
+
router.post('/add', admin, addPosition);
|
|
13
13
|
router.get('/list', getPositions);
|
|
14
|
-
router.put('/:id', updatePosition);
|
|
15
|
-
router.delete('/:id', deletePosition);
|
|
14
|
+
router.put('/:id', admin, updatePosition);
|
|
15
|
+
router.delete('/:id', admin, deletePosition);
|
|
16
16
|
|
|
17
17
|
module.exports = router;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const { getOnLeaveByDepartment } = require('../controllers/reportController');
|
|
3
|
-
const { protect } = require('../middleware/auth');
|
|
3
|
+
const { protect, admin } = require('../middleware/auth');
|
|
4
4
|
|
|
5
5
|
const router = express.Router();
|
|
6
6
|
|
|
7
|
-
router.get('/on-leave-by-department', protect, getOnLeaveByDepartment);
|
|
7
|
+
router.get('/on-leave-by-department', protect, admin, getOnLeaveByDepartment);
|
|
8
8
|
|
|
9
9
|
module.exports = router;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const cors = require('cors');
|
|
3
|
+
const session = require('express-session');
|
|
3
4
|
|
|
4
5
|
const connectDB = require('./config/db');
|
|
5
6
|
|
|
@@ -12,8 +13,26 @@ const protectedRoutes = require('./routes/protectedRoutes');
|
|
|
12
13
|
|
|
13
14
|
const app = express();
|
|
14
15
|
|
|
15
|
-
app.use(
|
|
16
|
+
app.use(
|
|
17
|
+
cors({
|
|
18
|
+
origin: 'http://localhost:5173',
|
|
19
|
+
credentials: true,
|
|
20
|
+
})
|
|
21
|
+
);
|
|
16
22
|
app.use(express.json());
|
|
23
|
+
app.use(
|
|
24
|
+
session({
|
|
25
|
+
secret: 'change_this_to_a_long_random_secret',
|
|
26
|
+
resave: false,
|
|
27
|
+
saveUninitialized: false,
|
|
28
|
+
cookie: {
|
|
29
|
+
httpOnly: true,
|
|
30
|
+
secure: false,
|
|
31
|
+
sameSite: 'none',
|
|
32
|
+
maxAge: 1000 * 60 * 60 * 24,
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
);
|
|
17
36
|
connectDB();
|
|
18
37
|
|
|
19
38
|
app.use('/api/auth', authRoutes);
|
package/templates/{index/frontend/src/App.jsx → HRMS (reba kuri iyi bro)/frontend/src/App.jsx }
RENAMED
|
@@ -8,6 +8,7 @@ import Department from './pages/Department';
|
|
|
8
8
|
import Employee from './pages/Employee';
|
|
9
9
|
import Position from './pages/Position';
|
|
10
10
|
import Reports from './pages/Reports';
|
|
11
|
+
import CreateUser from './pages/CreateUser';
|
|
11
12
|
|
|
12
13
|
function App() {
|
|
13
14
|
return (
|
|
@@ -22,6 +23,7 @@ function App() {
|
|
|
22
23
|
<Route path="employee" element={<Employee />} />
|
|
23
24
|
<Route path="position" element={<Position />} />
|
|
24
25
|
<Route path="reports" element={<Reports />} />
|
|
26
|
+
<Route path="create-user" element={<CreateUser />} />
|
|
25
27
|
</Route>
|
|
26
28
|
|
|
27
29
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
@@ -32,4 +34,4 @@ function App() {
|
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export default App;
|
|
35
|
-
|
|
37
|
+
|
|
@@ -22,7 +22,7 @@ function DashboardLayout() {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
axios.get('http://localhost:5006/api/auth/me', {
|
|
25
|
-
|
|
25
|
+
withCredentials: true,
|
|
26
26
|
})
|
|
27
27
|
.then((response) => {
|
|
28
28
|
setUser(response.data.user);
|
|
@@ -35,12 +35,22 @@ function DashboardLayout() {
|
|
|
35
35
|
});
|
|
36
36
|
}, []);
|
|
37
37
|
|
|
38
|
-
const handleLogout = () => {
|
|
39
|
-
|
|
38
|
+
const handleLogout = async () => {
|
|
39
|
+
try {
|
|
40
|
+
await axios.post('http://localhost:5006/api/auth/logout', null, {
|
|
41
|
+
withCredentials: true,
|
|
42
|
+
});
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error('Logout failed', err.response?.data || err.message);
|
|
45
|
+
}
|
|
40
46
|
localStorage.removeItem('user');
|
|
41
47
|
navigate('/login');
|
|
42
48
|
};
|
|
43
49
|
|
|
50
|
+
const visibleNav = user?.role === 'employee'
|
|
51
|
+
? navItems.filter((i) => ['/dashboard/employee', '/dashboard/department', '/dashboard/position'].includes(i.to))
|
|
52
|
+
: navItems;
|
|
53
|
+
|
|
44
54
|
return (
|
|
45
55
|
<div className="min-h-screen bg-gray-100">
|
|
46
56
|
<aside className="fixed inset-y-0 left-0 flex w-56 flex-col bg-teal-900 text-white">
|
|
@@ -50,7 +60,7 @@ function DashboardLayout() {
|
|
|
50
60
|
</div>
|
|
51
61
|
|
|
52
62
|
<nav className="flex-1 space-y-1 overflow-y-auto px-2 py-4">
|
|
53
|
-
{
|
|
63
|
+
{visibleNav.map((item) => (
|
|
54
64
|
<NavLink
|
|
55
65
|
key={item.to}
|
|
56
66
|
to={item.to}
|
|
@@ -60,6 +70,14 @@ function DashboardLayout() {
|
|
|
60
70
|
{item.label}
|
|
61
71
|
</NavLink>
|
|
62
72
|
))}
|
|
73
|
+
{user?.role === 'admin' && (
|
|
74
|
+
<NavLink
|
|
75
|
+
to="/dashboard/create-user"
|
|
76
|
+
className={({ isActive }) => `nav-link ${isActive ? 'nav-link-active' : ''}`}
|
|
77
|
+
>
|
|
78
|
+
Create User
|
|
79
|
+
</NavLink>
|
|
80
|
+
)}
|
|
63
81
|
</nav>
|
|
64
82
|
|
|
65
83
|
<div className="border-t border-teal-800 px-4 py-4">
|
|
@@ -87,4 +105,4 @@ function DashboardLayout() {
|
|
|
87
105
|
}
|
|
88
106
|
|
|
89
107
|
export default DashboardLayout;
|
|
90
|
-
|
|
108
|
+
|
|
@@ -2,8 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { Navigate } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
export default function ProtectedRoute({ children }) {
|
|
5
|
-
const
|
|
6
|
-
if (!
|
|
5
|
+
const user = localStorage.getItem('user');
|
|
6
|
+
if (!user) return <Navigate to="/login" replace />;
|
|
7
7
|
return children;
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function CreateUser() {
|
|
5
|
+
const [employees, setEmployees] = useState([]);
|
|
6
|
+
const [selectedEmployeeId, setSelectedEmployeeId] = useState('');
|
|
7
|
+
const [password, setPassword] = useState('');
|
|
8
|
+
const [message, setMessage] = useState('');
|
|
9
|
+
const [error, setError] = useState('');
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [user, setUser] = useState(null);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const storedUser = localStorage.getItem('user');
|
|
15
|
+
if (storedUser) {
|
|
16
|
+
setUser(JSON.parse(storedUser));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
axios
|
|
20
|
+
.get('http://localhost:5006/api/auth/me', {
|
|
21
|
+
withCredentials: true,
|
|
22
|
+
})
|
|
23
|
+
.then((response) => {
|
|
24
|
+
setUser(response.data.user);
|
|
25
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
26
|
+
})
|
|
27
|
+
.catch((err) => {
|
|
28
|
+
setError(err.response?.data?.message || 'Unable to verify admin rights');
|
|
29
|
+
});
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!user || user.role !== 'admin') return;
|
|
34
|
+
|
|
35
|
+
axios
|
|
36
|
+
.get('http://localhost:5006/api/employees/list', {
|
|
37
|
+
withCredentials: true,
|
|
38
|
+
})
|
|
39
|
+
.then((response) => setEmployees(response.data))
|
|
40
|
+
.catch((err) => setError(err.response?.data?.message || 'Could not load employees'));
|
|
41
|
+
}, [user]);
|
|
42
|
+
|
|
43
|
+
const handleSubmit = async (e) => {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
setError('');
|
|
46
|
+
setMessage('');
|
|
47
|
+
|
|
48
|
+
if (!selectedEmployeeId) {
|
|
49
|
+
setError('Please select an existing employee.');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (password.length < 6) {
|
|
54
|
+
setError('Password must be at least 6 characters long.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setLoading(true);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await axios.post(
|
|
62
|
+
'http://localhost:5006/api/auth/users',
|
|
63
|
+
{
|
|
64
|
+
employeeId: selectedEmployeeId,
|
|
65
|
+
password,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
withCredentials: true,
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
setMessage(`User account created for ${response.data.user.name}.`);
|
|
73
|
+
setSelectedEmployeeId('');
|
|
74
|
+
setPassword('');
|
|
75
|
+
} catch (err) {
|
|
76
|
+
setError(err.response?.data?.message || 'Failed to create user account');
|
|
77
|
+
console.error(err.response?.data || err.message);
|
|
78
|
+
} finally {
|
|
79
|
+
setLoading(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (!user) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="rounded-xl bg-white p-6 shadow-sm">
|
|
86
|
+
<h1 className="mb-4 text-2xl font-bold">Create User</h1>
|
|
87
|
+
<p className="text-sm text-gray-600">Loading your account...</p>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (user.role !== 'admin') {
|
|
93
|
+
return (
|
|
94
|
+
<div className="rounded-xl bg-white p-6 shadow-sm">
|
|
95
|
+
<h1 className="mb-4 text-2xl font-bold">Access Denied</h1>
|
|
96
|
+
<p className="text-sm text-gray-600">Only admins can create dashboard users.</p>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className="rounded-xl bg-white p-6 shadow-sm">
|
|
103
|
+
<h1 className="mb-4 text-2xl font-bold">Create Dashboard User</h1>
|
|
104
|
+
<p className="mb-4 text-sm text-gray-600">
|
|
105
|
+
Choose an existing employee and assign a password. New users will be created with the <strong>user</strong> role.
|
|
106
|
+
</p>
|
|
107
|
+
|
|
108
|
+
{message && <p className="alert-success mb-4">{message}</p>}
|
|
109
|
+
{error && <p className="alert-error mb-4">{error}</p>}
|
|
110
|
+
|
|
111
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
112
|
+
<div>
|
|
113
|
+
<label className="mb-1 block text-sm font-medium">Employee</label>
|
|
114
|
+
<select
|
|
115
|
+
value={selectedEmployeeId}
|
|
116
|
+
onChange={(e) => setSelectedEmployeeId(e.target.value)}
|
|
117
|
+
className="input-field w-full"
|
|
118
|
+
>
|
|
119
|
+
<option value="">Select an employee</option>
|
|
120
|
+
{employees.map((employee) => (
|
|
121
|
+
<option key={employee._id} value={employee._id}>
|
|
122
|
+
{employee.firstName} {employee.lastName} — {employee.email}
|
|
123
|
+
</option>
|
|
124
|
+
))}
|
|
125
|
+
</select>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<div>
|
|
129
|
+
<label className="mb-1 block text-sm font-medium">Password</label>
|
|
130
|
+
<input
|
|
131
|
+
type="password"
|
|
132
|
+
required
|
|
133
|
+
minLength={6}
|
|
134
|
+
placeholder="Enter a password for this user"
|
|
135
|
+
value={password}
|
|
136
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
137
|
+
className="input-field w-full"
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<button type="submit" disabled={loading} className="btn-primary w-full">
|
|
142
|
+
{loading ? 'Creating user...' : 'Create User'}
|
|
143
|
+
</button>
|
|
144
|
+
</form>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default CreateUser;
|
|
@@ -8,11 +8,10 @@ function DashboardHome() {
|
|
|
8
8
|
const [positions, setPositions] = useState([]);
|
|
9
9
|
|
|
10
10
|
useEffect(() => {
|
|
11
|
-
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
12
11
|
Promise.all([
|
|
13
|
-
axios.get('http://localhost:5006/api/departments/list', {
|
|
14
|
-
axios.get('http://localhost:5006/api/employees/list', {
|
|
15
|
-
axios.get('http://localhost:5006/api/positions/list', {
|
|
12
|
+
axios.get('http://localhost:5006/api/departments/list', { withCredentials: true }),
|
|
13
|
+
axios.get('http://localhost:5006/api/employees/list', { withCredentials: true }),
|
|
14
|
+
axios.get('http://localhost:5006/api/positions/list', { withCredentials: true }),
|
|
16
15
|
])
|
|
17
16
|
.then(([deptRes, empRes, posRes]) => {
|
|
18
17
|
setDepartments(deptRes.data);
|
|
@@ -6,12 +6,11 @@ function Department() {
|
|
|
6
6
|
const [departmentName, setDepartmentName] = useState('');
|
|
7
7
|
const [editingId, setEditingId] = useState(null);
|
|
8
8
|
const [message, setMessage] = useState('');
|
|
9
|
-
|
|
10
|
-
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
9
|
+
const currentUser = JSON.parse(localStorage.getItem('user') || 'null');
|
|
11
10
|
|
|
12
11
|
const getDepartments = async () => {
|
|
13
12
|
try {
|
|
14
|
-
const res = await axios.get('http://localhost:5006/api/departments/list', {
|
|
13
|
+
const res = await axios.get('http://localhost:5006/api/departments/list', { withCredentials: true });
|
|
15
14
|
setDepartments(res.data);
|
|
16
15
|
} catch (err) {
|
|
17
16
|
console.error(err);
|
|
@@ -29,14 +28,14 @@ function Department() {
|
|
|
29
28
|
await axios.put(
|
|
30
29
|
`http://localhost:5006/api/departments/${editingId}`,
|
|
31
30
|
{ departmentName },
|
|
32
|
-
{
|
|
31
|
+
{ withCredentials: true }
|
|
33
32
|
);
|
|
34
33
|
setMessage('Department updated successfully');
|
|
35
34
|
} else {
|
|
36
35
|
await axios.post(
|
|
37
36
|
'http://localhost:5006/api/departments/add',
|
|
38
37
|
{ departmentName },
|
|
39
|
-
{
|
|
38
|
+
{ withCredentials: true }
|
|
40
39
|
);
|
|
41
40
|
setMessage('Department added successfully');
|
|
42
41
|
}
|
|
@@ -57,7 +56,7 @@ function Department() {
|
|
|
57
56
|
const handleDelete = async (id) => {
|
|
58
57
|
if (!window.confirm('Delete this department?')) return;
|
|
59
58
|
try {
|
|
60
|
-
await axios.delete(`http://localhost:5006/api/departments/${id}`, {
|
|
59
|
+
await axios.delete(`http://localhost:5006/api/departments/${id}`, { withCredentials: true });
|
|
61
60
|
setMessage('Department deleted successfully');
|
|
62
61
|
if (editingId === id) {
|
|
63
62
|
setEditingId(null);
|
|
@@ -85,30 +84,37 @@ function Department() {
|
|
|
85
84
|
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
86
85
|
)}
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{editingId && (
|
|
106
|
-
<button type="button" className="btn-secondary" onClick={handleCancel}>
|
|
107
|
-
Cancel
|
|
87
|
+
{currentUser?.role === 'admin' ? (
|
|
88
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
89
|
+
<h2 className="font-semibold">{editingId ? 'Edit Department' : 'Add Department'}</h2>
|
|
90
|
+
<div>
|
|
91
|
+
<label className="mb-1 block text-sm">Department Name</label>
|
|
92
|
+
<input
|
|
93
|
+
type="text"
|
|
94
|
+
value={departmentName}
|
|
95
|
+
onChange={(e) => setDepartmentName(e.target.value)}
|
|
96
|
+
className="input-field"
|
|
97
|
+
placeholder="e.g. Human Resources"
|
|
98
|
+
required
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="flex items-center gap-3">
|
|
102
|
+
<button type="submit" className="btn-primary">
|
|
103
|
+
{editingId ? 'Update Department' : 'Add Department'}
|
|
108
104
|
</button>
|
|
109
|
-
|
|
105
|
+
{editingId && (
|
|
106
|
+
<button type="button" className="btn-secondary" onClick={handleCancel}>
|
|
107
|
+
Cancel
|
|
108
|
+
</button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</form>
|
|
112
|
+
) : (
|
|
113
|
+
<div className="mb-6 box">
|
|
114
|
+
<h2 className="font-semibold">Departments (read-only)</h2>
|
|
115
|
+
<p className="text-sm text-gray-600">You have read-only access. Contact an admin for changes.</p>
|
|
110
116
|
</div>
|
|
111
|
-
|
|
117
|
+
)}
|
|
112
118
|
|
|
113
119
|
<div className="box">
|
|
114
120
|
<h2 className="mb-3 font-semibold">Department List ({departments.length})</h2>
|
|
@@ -128,12 +134,16 @@ function Department() {
|
|
|
128
134
|
<tr key={dept._id}>
|
|
129
135
|
<td>{dept.departmentName}</td>
|
|
130
136
|
<td className="space-x-2">
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
{currentUser?.role === 'admin' && (
|
|
138
|
+
<>
|
|
139
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(dept)}>
|
|
140
|
+
Edit
|
|
141
|
+
</button>
|
|
142
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(dept._id)}>
|
|
143
|
+
Delete
|
|
144
|
+
</button>
|
|
145
|
+
</>
|
|
146
|
+
)}
|
|
137
147
|
</td>
|
|
138
148
|
</tr>
|
|
139
149
|
))}
|
|
@@ -147,4 +157,4 @@ function Department() {
|
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
export default Department;
|
|
150
|
-
|
|
160
|
+
|
|
@@ -21,12 +21,11 @@ function Employee() {
|
|
|
21
21
|
const [status, setStatus] = useState('on mission');
|
|
22
22
|
const [department, setDepartment] = useState('');
|
|
23
23
|
const [message, setMessage] = useState('');
|
|
24
|
-
|
|
25
|
-
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
24
|
+
const currentUser = JSON.parse(localStorage.getItem('user') || 'null');
|
|
26
25
|
|
|
27
26
|
const getEmployees = async () => {
|
|
28
27
|
try {
|
|
29
|
-
const res = await axios.get('http://localhost:5006/api/employees/list', {
|
|
28
|
+
const res = await axios.get('http://localhost:5006/api/employees/list', { withCredentials: true });
|
|
30
29
|
setEmployees(res.data);
|
|
31
30
|
} catch (err) {
|
|
32
31
|
console.error(err);
|
|
@@ -35,7 +34,7 @@ function Employee() {
|
|
|
35
34
|
|
|
36
35
|
const getDepartments = async () => {
|
|
37
36
|
try {
|
|
38
|
-
const res = await axios.get('http://localhost:5006/api/departments/list', {
|
|
37
|
+
const res = await axios.get('http://localhost:5006/api/departments/list', { withCredentials: true });
|
|
39
38
|
setDepartments(res.data);
|
|
40
39
|
} catch (err) {
|
|
41
40
|
console.error(err);
|
|
@@ -44,7 +43,7 @@ function Employee() {
|
|
|
44
43
|
|
|
45
44
|
const getPositions = async () => {
|
|
46
45
|
try {
|
|
47
|
-
const res = await axios.get('http://localhost:5006/api/positions/list', {
|
|
46
|
+
const res = await axios.get('http://localhost:5006/api/positions/list', { withCredentials: true });
|
|
48
47
|
setPositions(res.data);
|
|
49
48
|
} catch (err) {
|
|
50
49
|
console.error(err);
|
|
@@ -92,10 +91,10 @@ function Employee() {
|
|
|
92
91
|
};
|
|
93
92
|
|
|
94
93
|
if (editingId) {
|
|
95
|
-
await axios.put(`http://localhost:5006/api/employees/${editingId}`, payload, {
|
|
94
|
+
await axios.put(`http://localhost:5006/api/employees/${editingId}`, payload, { withCredentials: true });
|
|
96
95
|
setMessage('Employee updated successfully');
|
|
97
96
|
} else {
|
|
98
|
-
await axios.post('http://localhost:5006/api/employees/add', payload, {
|
|
97
|
+
await axios.post('http://localhost:5006/api/employees/add', payload, { withCredentials: true });
|
|
99
98
|
setMessage('Employee added successfully');
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -126,7 +125,7 @@ function Employee() {
|
|
|
126
125
|
const handleDelete = async (id) => {
|
|
127
126
|
if (!window.confirm('Delete this employee?')) return;
|
|
128
127
|
try {
|
|
129
|
-
await axios.delete(`http://localhost:5006/api/employees/${id}`, {
|
|
128
|
+
await axios.delete(`http://localhost:5006/api/employees/${id}`, { withCredentials: true });
|
|
130
129
|
setMessage('Employee deleted successfully');
|
|
131
130
|
if (editingId === id) {
|
|
132
131
|
resetForm();
|
|
@@ -152,7 +151,8 @@ function Employee() {
|
|
|
152
151
|
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
153
152
|
)}
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
{currentUser?.role === 'admin' ? (
|
|
155
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
156
156
|
<h2 className="font-semibold">{editingId ? 'Edit Employee' : 'Add Employee'}</h2>
|
|
157
157
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
158
158
|
<div>
|
|
@@ -304,7 +304,13 @@ function Employee() {
|
|
|
304
304
|
</button>
|
|
305
305
|
)}
|
|
306
306
|
</div>
|
|
307
|
-
|
|
307
|
+
</form>
|
|
308
|
+
) : (
|
|
309
|
+
<div className="mb-6 box">
|
|
310
|
+
<h2 className="font-semibold">Employees (read-only)</h2>
|
|
311
|
+
<p className="text-sm text-gray-600">You have read-only access. Contact an admin for changes.</p>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
308
314
|
|
|
309
315
|
<div className="box">
|
|
310
316
|
<h2 className="mb-3 font-semibold">Employee List ({employees.length})</h2>
|
|
@@ -344,12 +350,16 @@ function Employee() {
|
|
|
344
350
|
<td>{emp.position?.posName || 'N/A'}</td>
|
|
345
351
|
<td className="capitalize">{emp.status}</td>
|
|
346
352
|
<td className="space-x-2">
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
+
{currentUser?.role === 'admin' && (
|
|
354
|
+
<>
|
|
355
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(emp)}>
|
|
356
|
+
Edit
|
|
357
|
+
</button>
|
|
358
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(emp._id)}>
|
|
359
|
+
Delete
|
|
360
|
+
</button>
|
|
361
|
+
</>
|
|
362
|
+
)}
|
|
353
363
|
</td>
|
|
354
364
|
</tr>
|
|
355
365
|
))}
|
|
@@ -17,8 +17,11 @@ function Login() {
|
|
|
17
17
|
setLoading(true);
|
|
18
18
|
|
|
19
19
|
try {
|
|
20
|
-
const response = await axios.post(
|
|
21
|
-
|
|
20
|
+
const response = await axios.post(
|
|
21
|
+
'http://localhost:5006/api/auth/login',
|
|
22
|
+
{ email, password },
|
|
23
|
+
{ withCredentials: true }
|
|
24
|
+
);
|
|
22
25
|
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
23
26
|
setMessage('Login successful');
|
|
24
27
|
navigate('/dashboard');
|
|
@@ -70,7 +73,7 @@ function Login() {
|
|
|
70
73
|
</form>
|
|
71
74
|
|
|
72
75
|
<p className="mt-4 text-center text-sm text-gray-600">
|
|
73
|
-
|
|
76
|
+
If you do not have an account, ask your administrator to create one.
|
|
74
77
|
</p>
|
|
75
78
|
</div>
|
|
76
79
|
</div>
|
|
@@ -78,4 +81,4 @@ function Login() {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
export default Login;
|
|
81
|
-
|
|
84
|
+
|
|
@@ -7,12 +7,11 @@ function Position() {
|
|
|
7
7
|
const [requiredQualification, setRequiredQualification] = useState('');
|
|
8
8
|
const [editingId, setEditingId] = useState(null);
|
|
9
9
|
const [message, setMessage] = useState('');
|
|
10
|
-
|
|
11
|
-
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
10
|
+
const currentUser = JSON.parse(localStorage.getItem('user') || 'null');
|
|
12
11
|
|
|
13
12
|
const getPositions = async () => {
|
|
14
13
|
try {
|
|
15
|
-
const res = await axios.get('http://localhost:5006/api/positions/list', {
|
|
14
|
+
const res = await axios.get('http://localhost:5006/api/positions/list', { withCredentials: true });
|
|
16
15
|
setPositions(res.data);
|
|
17
16
|
} catch (err) {
|
|
18
17
|
console.error(err);
|
|
@@ -30,14 +29,14 @@ function Position() {
|
|
|
30
29
|
await axios.put(
|
|
31
30
|
`http://localhost:5006/api/positions/${editingId}`,
|
|
32
31
|
{ posName, requiredQualification },
|
|
33
|
-
{
|
|
32
|
+
{ withCredentials: true }
|
|
34
33
|
);
|
|
35
34
|
setMessage('Position updated successfully');
|
|
36
35
|
} else {
|
|
37
36
|
await axios.post(
|
|
38
37
|
'http://localhost:5006/api/positions/add',
|
|
39
38
|
{ posName, requiredQualification },
|
|
40
|
-
{
|
|
39
|
+
{ withCredentials: true }
|
|
41
40
|
);
|
|
42
41
|
setMessage('Position added successfully');
|
|
43
42
|
}
|
|
@@ -61,7 +60,7 @@ function Position() {
|
|
|
61
60
|
const handleDelete = async (id) => {
|
|
62
61
|
if (!window.confirm('Delete this position?')) return;
|
|
63
62
|
try {
|
|
64
|
-
await axios.delete(`http://localhost:5006/api/positions/${id}`, {
|
|
63
|
+
await axios.delete(`http://localhost:5006/api/positions/${id}`, { withCredentials: true });
|
|
65
64
|
setMessage('Position deleted successfully');
|
|
66
65
|
if (editingId === id) {
|
|
67
66
|
setEditingId(null);
|
|
@@ -91,7 +90,8 @@ function Position() {
|
|
|
91
90
|
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
92
91
|
)}
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
{currentUser?.role === 'admin' ? (
|
|
94
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
95
95
|
<h2 className="font-semibold">{editingId ? 'Edit Position' : 'Add Position'}</h2>
|
|
96
96
|
<div>
|
|
97
97
|
<label className="mb-1 block text-sm">Position Name</label>
|
|
@@ -125,7 +125,13 @@ function Position() {
|
|
|
125
125
|
</button>
|
|
126
126
|
)}
|
|
127
127
|
</div>
|
|
128
|
-
|
|
128
|
+
</form>
|
|
129
|
+
) : (
|
|
130
|
+
<div className="mb-6 box">
|
|
131
|
+
<h2 className="font-semibold">Positions (read-only)</h2>
|
|
132
|
+
<p className="text-sm text-gray-600">You have read-only access. Contact an admin for changes.</p>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
129
135
|
|
|
130
136
|
<div className="box">
|
|
131
137
|
<h2 className="mb-3 font-semibold">Position List ({positions.length})</h2>
|
|
@@ -147,12 +153,16 @@ function Position() {
|
|
|
147
153
|
<td>{pos.posName}</td>
|
|
148
154
|
<td>{pos.requiredQualification}</td>
|
|
149
155
|
<td className="space-x-2">
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
{currentUser?.role === 'admin' && (
|
|
157
|
+
<>
|
|
158
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(pos)}>
|
|
159
|
+
Edit
|
|
160
|
+
</button>
|
|
161
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(pos._id)}>
|
|
162
|
+
Delete
|
|
163
|
+
</button>
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
156
166
|
</td>
|
|
157
167
|
</tr>
|
|
158
168
|
))}
|
|
@@ -18,12 +18,15 @@ function Register() {
|
|
|
18
18
|
setLoading(true);
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
const response = await axios.post(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
const response = await axios.post(
|
|
22
|
+
'http://localhost:5006/api/auth/register',
|
|
23
|
+
{
|
|
24
|
+
name,
|
|
25
|
+
email,
|
|
26
|
+
password,
|
|
27
|
+
},
|
|
28
|
+
{ withCredentials: true }
|
|
29
|
+
);
|
|
27
30
|
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
28
31
|
setMessage('Account created successfully');
|
|
29
32
|
navigate('/dashboard');
|
|
@@ -99,4 +102,4 @@ function Register() {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
export default Register;
|
|
102
|
-
|
|
105
|
+
|
|
@@ -6,11 +6,9 @@ function Reports() {
|
|
|
6
6
|
const [loading, setLoading] = useState(true);
|
|
7
7
|
const [error, setError] = useState('');
|
|
8
8
|
|
|
9
|
-
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
10
|
-
|
|
11
9
|
useEffect(() => {
|
|
12
10
|
axios
|
|
13
|
-
.get('http://localhost:5006/api/reports/on-leave-by-department', {
|
|
11
|
+
.get('http://localhost:5006/api/reports/on-leave-by-department', { withCredentials: true })
|
|
14
12
|
.then((res) => setReport(res.data))
|
|
15
13
|
.catch((err) => setError(err.response?.data?.message || 'Failed to load report'))
|
|
16
14
|
.finally(() => setLoading(false));
|
|
@@ -91,4 +89,4 @@ function Reports() {
|
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
export default Reports;
|
|
94
|
-
|
|
92
|
+
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
Problems the new system is fixing
|
|
2
|
-
|
|
3
|
-
Data duplication
|
|
4
|
-
Slow manual processes
|
|
5
|
-
Poor record tracking
|
|
6
|
-
Data inconsistency
|
|
7
|
-
Delayed reporting
|
|
8
|
-
Difficulty retrieving information
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
What the new system will do
|
|
12
|
-
|
|
13
|
-
Manage employee records
|
|
14
|
-
Record purchases
|
|
15
|
-
Record sales
|
|
16
|
-
Store data centrally
|
|
17
|
-
Retrieve information quickly
|
|
18
|
-
Generate accurate reports
|
|
19
|
-
Support management decision-making
|
|
20
|
-
Simple summary
|
|
21
|
-
|
|
22
|
-
The web-based system will replace the manual system by centralizing employee, purchase, and sales records, reducing duplication, improving data consistency, speeding up operations, and producing accurate reports.
|
|
23
|
-
|
|
24
|
-
//middleware
|
|
25
|
-
Middleware in the MERN stack is a function that runs between a client's request and the server's response.
|
|
26
|
-
|
|
27
|
-
It is used to:
|
|
28
|
-
|
|
29
|
-
Check authentication (JWT tokens)
|
|
30
|
-
Validate data
|
|
31
|
-
Handle errors
|
|
32
|
-
Log requests
|
|
33
|
-
Modify request/response objects
|
|
34
|
-
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const jwt = require('jsonwebtoken');
|
|
3
|
-
const User = require('../models/User');
|
|
4
|
-
const JWT_SECRET = 'change_this_to_a_long_random_secret';
|
|
5
|
-
|
|
6
|
-
exports.protect = async (req, res, next) => {
|
|
7
|
-
try {
|
|
8
|
-
const header = req.headers.authorization;
|
|
9
|
-
|
|
10
|
-
if (!header || !header.startsWith('Bearer ')) {
|
|
11
|
-
return res.status(401).json({ message: 'Not authorized' });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const token = header.split(' ')[1];
|
|
15
|
-
const decoded = jwt.verify(token, JWT_SECRET);
|
|
16
|
-
const user = await User.findById(decoded.userId).select('-password');
|
|
17
|
-
|
|
18
|
-
if (!user) {
|
|
19
|
-
return res.status(401).json({ message: 'Not authorized' });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
req.user = user;
|
|
23
|
-
next();
|
|
24
|
-
} catch (err) {
|
|
25
|
-
return res.status(401).json({ message: 'Not authorized' });
|
|
26
|
-
}
|
|
27
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const express = require('express');
|
|
2
|
-
const { register, login, getMe } = require('../controllers/authController');
|
|
3
|
-
const { protect } = require('../middleware/auth');
|
|
4
|
-
|
|
5
|
-
const router = express.Router();
|
|
6
|
-
|
|
7
|
-
router.post('/register', register);
|
|
8
|
-
router.post('/login', login);
|
|
9
|
-
router.get('/me', protect, getMe);
|
|
10
|
-
|
|
11
|
-
module.exports = router;
|
|
File without changes
|
/package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/departmentController.js
RENAMED
|
File without changes
|
/package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/employeeController.js
RENAMED
|
File without changes
|
/package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/positionController.js
RENAMED
|
File without changes
|
/package/templates/{index → HRMS (reba kuri iyi bro)}/backend/controllers/reportController.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|