create-jinmankn-app 1.0.13 → 1.0.15
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/cookiejar.txt +4 -0
- 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 } +23 -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 } +24 -5
- package/templates/{index/frontend/src/components/ProtectedRoute.jsx → HRMS (reba kuri iyi bro)/frontend/src/components/ProtectedRoute.jsx } +2 -2
- package/templates/{index/frontend/src/main.jsx → HRMS (reba kuri iyi bro)/frontend/src/main.jsx } +4 -1
- package/templates/HRMS (reba kuri iyi bro)/frontend/src/pages/CreateUser.jsx +150 -0
- package/templates/{index/frontend/src/pages/DashboardHome.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/DashboardHome.jsx } +4 -4
- package/templates/{index/frontend/src/pages/Department.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Department.jsx } +47 -36
- package/templates/{index/frontend/src/pages/Employee.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Employee.jsx } +27 -16
- package/templates/{index/frontend/src/pages/Login.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Login.jsx } +7 -3
- package/templates/{index/frontend/src/pages/Position.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Position.jsx } +27 -16
- package/templates/{index/frontend/src/pages/Register.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Register.jsx } +10 -6
- package/templates/{index/frontend/src/pages/Reports.jsx → HRMS (reba kuri iyi bro)/frontend/src/pages/Reports.jsx } +2 -3
- package/templates/HRMS (reba kuri iyi bro)/frontend/vite.config.js +18 -0
- 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/frontend/vite.config.js +0 -7
- /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
|
@@ -0,0 +1,150 @@
|
|
|
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('/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('/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
|
+
'/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;
|
|
150
|
+
|
|
@@ -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('
|
|
14
|
-
axios.get('
|
|
15
|
-
axios.get('
|
|
12
|
+
axios.get('/api/departments/list', { withCredentials: true }),
|
|
13
|
+
axios.get('/api/employees/list', { withCredentials: true }),
|
|
14
|
+
axios.get('/api/positions/list', { withCredentials: true }),
|
|
16
15
|
])
|
|
17
16
|
.then(([deptRes, empRes, posRes]) => {
|
|
18
17
|
setDepartments(deptRes.data);
|
|
@@ -56,3 +55,4 @@ function DashboardHome() {
|
|
|
56
55
|
|
|
57
56
|
export default DashboardHome;
|
|
58
57
|
|
|
58
|
+
|
|
@@ -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('
|
|
13
|
+
const res = await axios.get('/api/departments/list', { withCredentials: true });
|
|
15
14
|
setDepartments(res.data);
|
|
16
15
|
} catch (err) {
|
|
17
16
|
console.error(err);
|
|
@@ -27,16 +26,16 @@ function Department() {
|
|
|
27
26
|
try {
|
|
28
27
|
if (editingId) {
|
|
29
28
|
await axios.put(
|
|
30
|
-
|
|
29
|
+
`/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
|
+
'/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(
|
|
59
|
+
await axios.delete(`/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,5 @@ function Department() {
|
|
|
147
157
|
}
|
|
148
158
|
|
|
149
159
|
export default Department;
|
|
160
|
+
|
|
150
161
|
|
|
@@ -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('
|
|
28
|
+
const res = await axios.get('/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('
|
|
37
|
+
const res = await axios.get('/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('
|
|
46
|
+
const res = await axios.get('/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(
|
|
94
|
+
await axios.put(`/api/employees/${editingId}`, payload, { withCredentials: true });
|
|
96
95
|
setMessage('Employee updated successfully');
|
|
97
96
|
} else {
|
|
98
|
-
await axios.post('
|
|
97
|
+
await axios.post('/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(
|
|
128
|
+
await axios.delete(`/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
|
))}
|
|
@@ -364,3 +374,4 @@ function Employee() {
|
|
|
364
374
|
|
|
365
375
|
export default Employee;
|
|
366
376
|
|
|
377
|
+
|
|
@@ -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
|
+
'/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,5 @@ function Login() {
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
export default Login;
|
|
84
|
+
|
|
81
85
|
|
|
@@ -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('
|
|
14
|
+
const res = await axios.get('/api/positions/list', { withCredentials: true });
|
|
16
15
|
setPositions(res.data);
|
|
17
16
|
} catch (err) {
|
|
18
17
|
console.error(err);
|
|
@@ -28,16 +27,16 @@ function Position() {
|
|
|
28
27
|
try {
|
|
29
28
|
if (editingId) {
|
|
30
29
|
await axios.put(
|
|
31
|
-
|
|
30
|
+
`/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
|
+
'/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(
|
|
63
|
+
await axios.delete(`/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
|
))}
|
|
@@ -167,3 +177,4 @@ function Position() {
|
|
|
167
177
|
|
|
168
178
|
export default Position;
|
|
169
179
|
|
|
180
|
+
|
|
@@ -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
|
+
'/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,5 @@ function Register() {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
export default Register;
|
|
105
|
+
|
|
102
106
|
|
|
@@ -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('
|
|
11
|
+
.get('/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,5 @@ function Reports() {
|
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
export default Reports;
|
|
92
|
+
|
|
94
93
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
// https://vite.dev/config/
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react(), tailwindcss()],
|
|
7
|
+
server: {
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:5006',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
secure: false,
|
|
13
|
+
cookieDomainRewrite: 'localhost',
|
|
14
|
+
cookiePathRewrite: '/',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
|
@@ -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
|
-
};
|