create-jinmankn-app 1.0.11 → 1.0.13
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/EPMS(Employee Payroll Managment System)/backend/routes/protectedRoutes.js +10 -0
- package/templates/EPMS(Employee Payroll Managment System)/backend/server.js +2 -0
- package/templates/EPMS(Employee Payroll Managment System)/frontend/src/App.jsx +2 -1
- package/templates/EPMS(Employee Payroll Managment System)/frontend/src/components/ProtectedRoute.jsx +8 -0
- package/templates/EPMS(Employee Payroll Managment System)/package-lock.json +13 -0
- package/templates/Faisal Hospital Managment System (FHMS) mysql/backend/routes/secureRoutes.js +14 -0
- package/templates/Faisal Hospital Managment System (FHMS) mysql/backend/server.js +2 -0
- package/templates/Faisal Hospital Managment System (FHMS) mysql/frontend/src/components/ProtectedRoute.jsx +2 -3
- package/templates/Faisal Hospital Managment System (FHMS) mysql/frontend/src/utils/auth.js +13 -0
- package/templates/LMS(Library Managment System)/backend/controllers/authController.js +32 -0
- package/templates/LMS(Library Managment System)/backend/routes/authRoutes.js +2 -1
- package/templates/LMS(Library Managment System)/backend/routes/protectedRoutes.js +19 -0
- package/templates/LMS(Library Managment System)/backend/server.js +2 -0
- package/templates/LMS(Library Managment System)/frontend/src/App.jsx +74 -15
- package/templates/LMS(Library Managment System)/frontend/src/ProtectedRoute.jsx +13 -0
- package/templates/LMS(Library Managment System)/frontend/src/pages/Login.jsx +4 -1
- package/templates/LMS(Library Managment System)/frontend/src/pages/Register.jsx +87 -0
- package/templates/SCMS(Supply Chain Managment System)/backend/routes/protectedRoutes.js +10 -0
- package/templates/SCMS(Supply Chain Managment System)/backend/server.js +2 -0
- package/templates/SCMS(Supply Chain Managment System)/frontend/src/App.jsx +2 -1
- package/templates/SCMS(Supply Chain Managment System)/frontend/src/components/ProtectedRoute.jsx +30 -0
- package/templates/SFM(School Fee Managment)/backend/middleware/protectedRoutes.js +10 -0
- package/templates/SFM(School Fee Managment)/frontend/src/App.jsx +7 -5
- package/templates/SFM(School Fee Managment)/frontend/src/components/ProtectedRoute.jsx +13 -0
- package/templates/SIMS(Stock Inventory Managment System)/backend/controllers/protectedController.js +14 -0
- package/templates/SIMS(Stock Inventory Managment System)/backend/routes/protectedRoutes.js +10 -0
- package/templates/SIMS(Stock Inventory Managment System)/backend/server.js +2 -1
- package/templates/SIMS(Stock Inventory Managment System)/frontend/src/App.jsx +57 -7
- package/templates/SIMS(Stock Inventory Managment System)/frontend/src/components/ProtectedRoute.jsx +8 -0
- package/templates/SMS(Stock Managment System)/backend-project/routes/protectedRoutes.js +18 -0
- package/templates/SMS(Stock Managment System)/backend-project/server.js +3 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/App.jsx +36 -7
- package/templates/SMS(Stock Managment System)/frontend-project/src/components/AppLayout.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/components/ProtectedRoute.jsx +13 -0
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Login.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Product.jsx +211 -78
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Profile.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Register.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Reports.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Sales.jsx +6 -6
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Warehouse.jsx +150 -44
- package/templates/SMS(Stock Managment System)/package-lock.json +13 -0
- package/templates/SRMS(Sales Record Management System)/backend/routes/protectedRoutes.js +13 -0
- package/templates/SRMS(Sales Record Management System)/backend/server.js +2 -0
- package/templates/SRMS(Sales Record Management System)/frontend/src/App.jsx +2 -1
- package/templates/SRMS(Sales Record Management System)/frontend/src/components/ProtectedRoute.jsx +22 -0
- package/templates/index/README.md +34 -0
- package/templates/index/backend/config/db.js +12 -0
- package/templates/index/backend/controllers/authController.js +88 -0
- package/templates/index/backend/controllers/departmentController.js +74 -0
- package/templates/index/backend/controllers/employeeController.js +204 -0
- package/templates/index/backend/controllers/positionController.js +78 -0
- package/templates/index/backend/controllers/reportController.js +45 -0
- package/templates/index/backend/middleware/auth.js +27 -0
- package/templates/index/backend/models/Department.js +15 -0
- package/templates/index/backend/models/Employee.js +73 -0
- package/templates/index/backend/models/Position.js +20 -0
- package/templates/index/backend/models/User.js +32 -0
- package/templates/index/backend/package-lock.json +2190 -0
- package/templates/index/backend/package.json +22 -0
- package/templates/index/backend/routes/authRoutes.js +11 -0
- package/templates/index/backend/routes/departmentRoutes.js +17 -0
- package/templates/index/backend/routes/employeeRoutes.js +19 -0
- package/templates/index/backend/routes/positionRoutes.js +17 -0
- package/templates/index/backend/routes/protectedRoutes.js +10 -0
- package/templates/index/backend/routes/reportRoutes.js +9 -0
- package/templates/index/backend/server.js +30 -0
- package/templates/index/frontend/README.md +16 -0
- package/templates/index/frontend/eslint.config.js +21 -0
- package/templates/index/frontend/index.html +13 -0
- package/templates/index/frontend/package-lock.json +3095 -0
- package/templates/index/frontend/package.json +31 -0
- package/templates/index/frontend/public/favicon.svg +1 -0
- package/templates/index/frontend/src/App.css +0 -0
- package/templates/index/frontend/src/App.jsx +35 -0
- package/templates/index/frontend/src/assets/hero.png +0 -0
- package/templates/index/frontend/src/components/DashboardLayout.jsx +90 -0
- package/templates/index/frontend/src/components/ProtectedRoute.jsx +9 -0
- package/templates/index/frontend/src/index.css +59 -0
- package/templates/index/frontend/src/main.jsx +11 -0
- package/templates/index/frontend/src/pages/DashboardHome.jsx +58 -0
- package/templates/index/frontend/src/pages/Department.jsx +150 -0
- package/templates/index/frontend/src/pages/Employee.jsx +366 -0
- package/templates/index/frontend/src/pages/Login.jsx +81 -0
- package/templates/index/frontend/src/pages/Position.jsx +169 -0
- package/templates/index/frontend/src/pages/Register.jsx +102 -0
- package/templates/index/frontend/src/pages/Reports.jsx +94 -0
- package/templates/index/frontend/vite.config.js +7 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
const EMPLOYEE_STATUSES = ['on leave', 'left', 'blacklisted', 'deceased', 'on mission'];
|
|
5
|
+
|
|
6
|
+
function Employee() {
|
|
7
|
+
const [employees, setEmployees] = useState([]);
|
|
8
|
+
const [departments, setDepartments] = useState([]);
|
|
9
|
+
const [positions, setPositions] = useState([]);
|
|
10
|
+
const [editingId, setEditingId] = useState(null);
|
|
11
|
+
const [userName, setUserName] = useState('');
|
|
12
|
+
const [firstName, setFirstName] = useState('');
|
|
13
|
+
const [lastName, setLastName] = useState('');
|
|
14
|
+
const [email, setEmail] = useState('');
|
|
15
|
+
const [address, setAddress] = useState('');
|
|
16
|
+
const [position, setPosition] = useState('');
|
|
17
|
+
const [telephone, setTelephone] = useState('');
|
|
18
|
+
const [gender, setGender] = useState('Male');
|
|
19
|
+
const [dateOfBirth, setDateOfBirth] = useState('');
|
|
20
|
+
const [hireDate, setHireDate] = useState('');
|
|
21
|
+
const [status, setStatus] = useState('on mission');
|
|
22
|
+
const [department, setDepartment] = useState('');
|
|
23
|
+
const [message, setMessage] = useState('');
|
|
24
|
+
|
|
25
|
+
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
26
|
+
|
|
27
|
+
const getEmployees = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const res = await axios.get('http://localhost:5006/api/employees/list', { headers });
|
|
30
|
+
setEmployees(res.data);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(err);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getDepartments = async () => {
|
|
37
|
+
try {
|
|
38
|
+
const res = await axios.get('http://localhost:5006/api/departments/list', { headers });
|
|
39
|
+
setDepartments(res.data);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(err);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getPositions = async () => {
|
|
46
|
+
try {
|
|
47
|
+
const res = await axios.get('http://localhost:5006/api/positions/list', { headers });
|
|
48
|
+
setPositions(res.data);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error(err);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
getEmployees();
|
|
56
|
+
getDepartments();
|
|
57
|
+
getPositions();
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const resetForm = () => {
|
|
61
|
+
setEditingId(null);
|
|
62
|
+
setUserName('');
|
|
63
|
+
setFirstName('');
|
|
64
|
+
setLastName('');
|
|
65
|
+
setEmail('');
|
|
66
|
+
setAddress('');
|
|
67
|
+
setPosition('');
|
|
68
|
+
setTelephone('');
|
|
69
|
+
setGender('Male');
|
|
70
|
+
setDateOfBirth('');
|
|
71
|
+
setHireDate('');
|
|
72
|
+
setStatus('on mission');
|
|
73
|
+
setDepartment('');
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleSubmit = async (e) => {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
try {
|
|
79
|
+
const payload = {
|
|
80
|
+
userName,
|
|
81
|
+
firstName,
|
|
82
|
+
lastName,
|
|
83
|
+
email,
|
|
84
|
+
gender,
|
|
85
|
+
dateOfBirth,
|
|
86
|
+
telephone,
|
|
87
|
+
address,
|
|
88
|
+
hireDate,
|
|
89
|
+
status,
|
|
90
|
+
department,
|
|
91
|
+
position,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (editingId) {
|
|
95
|
+
await axios.put(`http://localhost:5006/api/employees/${editingId}`, payload, { headers });
|
|
96
|
+
setMessage('Employee updated successfully');
|
|
97
|
+
} else {
|
|
98
|
+
await axios.post('http://localhost:5006/api/employees/add', payload, { headers });
|
|
99
|
+
setMessage('Employee added successfully');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
resetForm();
|
|
103
|
+
getEmployees();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
setMessage(err.response?.data?.message || 'Failed to save employee');
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleEdit = (emp) => {
|
|
110
|
+
setEditingId(emp._id);
|
|
111
|
+
setUserName(emp.userName || '');
|
|
112
|
+
setFirstName(emp.firstName);
|
|
113
|
+
setLastName(emp.lastName);
|
|
114
|
+
setEmail(emp.email);
|
|
115
|
+
setAddress(emp.address);
|
|
116
|
+
setPosition(emp.position?._id || '');
|
|
117
|
+
setTelephone(emp.telephone);
|
|
118
|
+
setGender(emp.gender);
|
|
119
|
+
setDateOfBirth(emp.dateOfBirth ? emp.dateOfBirth.split('T')[0] : '');
|
|
120
|
+
setHireDate(emp.hireDate ? emp.hireDate.split('T')[0] : '');
|
|
121
|
+
setStatus(EMPLOYEE_STATUSES.includes(emp.status) ? emp.status : 'on mission');
|
|
122
|
+
setDepartment(emp.department?._id || '');
|
|
123
|
+
setMessage('');
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const handleDelete = async (id) => {
|
|
127
|
+
if (!window.confirm('Delete this employee?')) return;
|
|
128
|
+
try {
|
|
129
|
+
await axios.delete(`http://localhost:5006/api/employees/${id}`, { headers });
|
|
130
|
+
setMessage('Employee deleted successfully');
|
|
131
|
+
if (editingId === id) {
|
|
132
|
+
resetForm();
|
|
133
|
+
}
|
|
134
|
+
getEmployees();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
setMessage(err.response?.data?.message || 'Failed to delete employee');
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const handleCancel = () => {
|
|
141
|
+
resetForm();
|
|
142
|
+
setMessage('');
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const isSuccess = message.includes('successfully');
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div>
|
|
149
|
+
<h1 className="mb-6 text-2xl font-bold">Employee Management</h1>
|
|
150
|
+
|
|
151
|
+
{message && (
|
|
152
|
+
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
156
|
+
<h2 className="font-semibold">{editingId ? 'Edit Employee' : 'Add Employee'}</h2>
|
|
157
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
158
|
+
<div>
|
|
159
|
+
<label className="mb-1 block text-sm">Username (optional)</label>
|
|
160
|
+
<input
|
|
161
|
+
type="text"
|
|
162
|
+
value={userName}
|
|
163
|
+
onChange={(e) => setUserName(e.target.value)}
|
|
164
|
+
className="input-field"
|
|
165
|
+
placeholder="Optional user handle"
|
|
166
|
+
/>
|
|
167
|
+
</div>
|
|
168
|
+
<div>
|
|
169
|
+
<label className="mb-1 block text-sm">First Name</label>
|
|
170
|
+
<input
|
|
171
|
+
type="text"
|
|
172
|
+
value={firstName}
|
|
173
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
174
|
+
className="input-field"
|
|
175
|
+
placeholder="Enter first name"
|
|
176
|
+
required
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<div>
|
|
180
|
+
<label className="mb-1 block text-sm">Last Name</label>
|
|
181
|
+
<input
|
|
182
|
+
type="text"
|
|
183
|
+
value={lastName}
|
|
184
|
+
onChange={(e) => setLastName(e.target.value)}
|
|
185
|
+
className="input-field"
|
|
186
|
+
placeholder="Enter last name"
|
|
187
|
+
required
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<label className="mb-1 block text-sm">Email</label>
|
|
192
|
+
<input
|
|
193
|
+
type="email"
|
|
194
|
+
value={email}
|
|
195
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
196
|
+
className="input-field"
|
|
197
|
+
placeholder="Enter employee email"
|
|
198
|
+
required
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
<div>
|
|
202
|
+
<label className="mb-1 block text-sm">Telephone</label>
|
|
203
|
+
<input
|
|
204
|
+
type="tel"
|
|
205
|
+
value={telephone}
|
|
206
|
+
onChange={(e) => setTelephone(e.target.value)}
|
|
207
|
+
className="input-field"
|
|
208
|
+
placeholder="Enter phone number"
|
|
209
|
+
required
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
<div>
|
|
213
|
+
<label className="mb-1 block text-sm">Gender</label>
|
|
214
|
+
<select
|
|
215
|
+
value={gender}
|
|
216
|
+
onChange={(e) => setGender(e.target.value)}
|
|
217
|
+
className="input-field"
|
|
218
|
+
>
|
|
219
|
+
<option value="Male">Male</option>
|
|
220
|
+
<option value="Female">Female</option>
|
|
221
|
+
<option value="Other">Other</option>
|
|
222
|
+
</select>
|
|
223
|
+
</div>
|
|
224
|
+
<div>
|
|
225
|
+
<label className="mb-1 block text-sm">Position</label>
|
|
226
|
+
<select
|
|
227
|
+
value={position}
|
|
228
|
+
onChange={(e) => setPosition(e.target.value)}
|
|
229
|
+
className="input-field"
|
|
230
|
+
required
|
|
231
|
+
>
|
|
232
|
+
<option value="">Select position</option>
|
|
233
|
+
{positions.map((pos) => (
|
|
234
|
+
<option key={pos._id} value={pos._id}>{pos.posName}</option>
|
|
235
|
+
))}
|
|
236
|
+
</select>
|
|
237
|
+
</div>
|
|
238
|
+
<div>
|
|
239
|
+
<label className="mb-1 block text-sm">Status</label>
|
|
240
|
+
<select
|
|
241
|
+
value={status}
|
|
242
|
+
onChange={(e) => setStatus(e.target.value)}
|
|
243
|
+
className="input-field"
|
|
244
|
+
required
|
|
245
|
+
>
|
|
246
|
+
{EMPLOYEE_STATUSES.map((s) => (
|
|
247
|
+
<option key={s} value={s}>{s}</option>
|
|
248
|
+
))}
|
|
249
|
+
</select>
|
|
250
|
+
</div>
|
|
251
|
+
<div>
|
|
252
|
+
<label className="mb-1 block text-sm">Date of Birth</label>
|
|
253
|
+
<input
|
|
254
|
+
type="date"
|
|
255
|
+
value={dateOfBirth}
|
|
256
|
+
onChange={(e) => setDateOfBirth(e.target.value)}
|
|
257
|
+
className="input-field"
|
|
258
|
+
required
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
<div>
|
|
262
|
+
<label className="mb-1 block text-sm">Hire Date</label>
|
|
263
|
+
<input
|
|
264
|
+
type="date"
|
|
265
|
+
value={hireDate}
|
|
266
|
+
onChange={(e) => setHireDate(e.target.value)}
|
|
267
|
+
className="input-field"
|
|
268
|
+
required
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
<div className="sm:col-span-2">
|
|
272
|
+
<label className="mb-1 block text-sm">Address</label>
|
|
273
|
+
<input
|
|
274
|
+
type="text"
|
|
275
|
+
value={address}
|
|
276
|
+
onChange={(e) => setAddress(e.target.value)}
|
|
277
|
+
className="input-field"
|
|
278
|
+
placeholder="Enter employee address"
|
|
279
|
+
required
|
|
280
|
+
/>
|
|
281
|
+
</div>
|
|
282
|
+
<div>
|
|
283
|
+
<label className="mb-1 block text-sm">Department</label>
|
|
284
|
+
<select
|
|
285
|
+
value={department}
|
|
286
|
+
onChange={(e) => setDepartment(e.target.value)}
|
|
287
|
+
className="input-field"
|
|
288
|
+
required
|
|
289
|
+
>
|
|
290
|
+
<option value="">Select department</option>
|
|
291
|
+
{departments.map((dept) => (
|
|
292
|
+
<option key={dept._id} value={dept._id}>{dept.departmentName}</option>
|
|
293
|
+
))}
|
|
294
|
+
</select>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
<div className="flex items-center gap-3">
|
|
298
|
+
<button type="submit" className="btn-primary">
|
|
299
|
+
{editingId ? 'Update Employee' : 'Add Employee'}
|
|
300
|
+
</button>
|
|
301
|
+
{editingId && (
|
|
302
|
+
<button type="button" className="btn-secondary" onClick={handleCancel}>
|
|
303
|
+
Cancel
|
|
304
|
+
</button>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
</form>
|
|
308
|
+
|
|
309
|
+
<div className="box">
|
|
310
|
+
<h2 className="mb-3 font-semibold">Employee List ({employees.length})</h2>
|
|
311
|
+
{employees.length === 0 ? (
|
|
312
|
+
<p className="text-gray-600">No employees yet.</p>
|
|
313
|
+
) : (
|
|
314
|
+
<div className="overflow-x-auto">
|
|
315
|
+
<table className="data-table">
|
|
316
|
+
<thead>
|
|
317
|
+
<tr>
|
|
318
|
+
<th>Username</th>
|
|
319
|
+
<th>Name</th>
|
|
320
|
+
<th>Email</th>
|
|
321
|
+
<th>Telephone</th>
|
|
322
|
+
<th>Address</th>
|
|
323
|
+
<th>Gender</th>
|
|
324
|
+
<th>DOB</th>
|
|
325
|
+
<th>Hire Date</th>
|
|
326
|
+
<th>Department</th>
|
|
327
|
+
<th>Position</th>
|
|
328
|
+
<th>Status</th>
|
|
329
|
+
<th>Actions</th>
|
|
330
|
+
</tr>
|
|
331
|
+
</thead>
|
|
332
|
+
<tbody>
|
|
333
|
+
{employees.map((emp) => (
|
|
334
|
+
<tr key={emp._id}>
|
|
335
|
+
<td>{emp.userName || 'N/A'}</td>
|
|
336
|
+
<td>{emp.firstName} {emp.lastName}</td>
|
|
337
|
+
<td>{emp.email}</td>
|
|
338
|
+
<td>{emp.telephone}</td>
|
|
339
|
+
<td>{emp.address}</td>
|
|
340
|
+
<td>{emp.gender}</td>
|
|
341
|
+
<td>{emp.dateOfBirth ? emp.dateOfBirth.split('T')[0] : 'N/A'}</td>
|
|
342
|
+
<td>{emp.hireDate ? emp.hireDate.split('T')[0] : 'N/A'}</td>
|
|
343
|
+
<td>{emp.department?.departmentName || 'N/A'}</td>
|
|
344
|
+
<td>{emp.position?.posName || 'N/A'}</td>
|
|
345
|
+
<td className="capitalize">{emp.status}</td>
|
|
346
|
+
<td className="space-x-2">
|
|
347
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(emp)}>
|
|
348
|
+
Edit
|
|
349
|
+
</button>
|
|
350
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(emp._id)}>
|
|
351
|
+
Delete
|
|
352
|
+
</button>
|
|
353
|
+
</td>
|
|
354
|
+
</tr>
|
|
355
|
+
))}
|
|
356
|
+
</tbody>
|
|
357
|
+
</table>
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export default Employee;
|
|
366
|
+
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { Link, useNavigate } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
function Login() {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const [email, setEmail] = useState('');
|
|
8
|
+
const [password, setPassword] = useState('');
|
|
9
|
+
const [error, setError] = useState('');
|
|
10
|
+
const [message, setMessage] = useState('');
|
|
11
|
+
const [loading, setLoading] = useState(false);
|
|
12
|
+
|
|
13
|
+
const handleSubmit = async (e) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
setError('');
|
|
16
|
+
setMessage('');
|
|
17
|
+
setLoading(true);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await axios.post('http://localhost:5006/api/auth/login', { email, password });
|
|
21
|
+
localStorage.setItem('token', response.data.token);
|
|
22
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
23
|
+
setMessage('Login successful');
|
|
24
|
+
navigate('/dashboard');
|
|
25
|
+
} catch (err) {
|
|
26
|
+
setError(err.response?.data?.message || 'Login failed');
|
|
27
|
+
console.error('Server Error:', err.response?.data || err.message);
|
|
28
|
+
} finally {
|
|
29
|
+
setLoading(false);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex min-h-screen items-center justify-center bg-gray-100 px-4">
|
|
35
|
+
<div className="w-full max-w-md box p-6">
|
|
36
|
+
<h1 className="mb-1 text-2xl font-bold text-teal-900">DAB HRMS</h1>
|
|
37
|
+
<p className="mb-6 text-sm text-gray-600">Sign in to your account</p>
|
|
38
|
+
|
|
39
|
+
{message && <p className="alert-success">{message}</p>}
|
|
40
|
+
{error && <p className="alert-error">{error}</p>}
|
|
41
|
+
|
|
42
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
43
|
+
<div>
|
|
44
|
+
<label className="mb-1 block text-sm font-medium">Email</label>
|
|
45
|
+
<input
|
|
46
|
+
type="email"
|
|
47
|
+
required
|
|
48
|
+
placeholder="Enter your email address"
|
|
49
|
+
value={email}
|
|
50
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
51
|
+
className="input-field"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div>
|
|
56
|
+
<label className="mb-1 block text-sm font-medium">Password</label>
|
|
57
|
+
<input
|
|
58
|
+
type="password"
|
|
59
|
+
required
|
|
60
|
+
placeholder="Enter your password"
|
|
61
|
+
value={password}
|
|
62
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
63
|
+
className="input-field"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<button type="submit" disabled={loading} className="btn-primary w-full">
|
|
68
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
69
|
+
</button>
|
|
70
|
+
</form>
|
|
71
|
+
|
|
72
|
+
<p className="mt-4 text-center text-sm text-gray-600">
|
|
73
|
+
No account? <Link to="/register" className="text-blue-600 hover:underline">Register</Link>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default Login;
|
|
81
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Position() {
|
|
5
|
+
const [positions, setPositions] = useState([]);
|
|
6
|
+
const [posName, setPosName] = useState('');
|
|
7
|
+
const [requiredQualification, setRequiredQualification] = useState('');
|
|
8
|
+
const [editingId, setEditingId] = useState(null);
|
|
9
|
+
const [message, setMessage] = useState('');
|
|
10
|
+
|
|
11
|
+
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
12
|
+
|
|
13
|
+
const getPositions = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const res = await axios.get('http://localhost:5006/api/positions/list', { headers });
|
|
16
|
+
setPositions(res.data);
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(err);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
getPositions();
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const handleSubmit = async (e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
try {
|
|
29
|
+
if (editingId) {
|
|
30
|
+
await axios.put(
|
|
31
|
+
`http://localhost:5006/api/positions/${editingId}`,
|
|
32
|
+
{ posName, requiredQualification },
|
|
33
|
+
{ headers }
|
|
34
|
+
);
|
|
35
|
+
setMessage('Position updated successfully');
|
|
36
|
+
} else {
|
|
37
|
+
await axios.post(
|
|
38
|
+
'http://localhost:5006/api/positions/add',
|
|
39
|
+
{ posName, requiredQualification },
|
|
40
|
+
{ headers }
|
|
41
|
+
);
|
|
42
|
+
setMessage('Position added successfully');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setPosName('');
|
|
46
|
+
setRequiredQualification('');
|
|
47
|
+
setEditingId(null);
|
|
48
|
+
getPositions();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
setMessage(err.response?.data?.message || 'Failed to save position');
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleEdit = (position) => {
|
|
55
|
+
setPosName(position.posName);
|
|
56
|
+
setRequiredQualification(position.requiredQualification);
|
|
57
|
+
setEditingId(position._id);
|
|
58
|
+
setMessage('');
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handleDelete = async (id) => {
|
|
62
|
+
if (!window.confirm('Delete this position?')) return;
|
|
63
|
+
try {
|
|
64
|
+
await axios.delete(`http://localhost:5006/api/positions/${id}`, { headers });
|
|
65
|
+
setMessage('Position deleted successfully');
|
|
66
|
+
if (editingId === id) {
|
|
67
|
+
setEditingId(null);
|
|
68
|
+
setPosName('');
|
|
69
|
+
setRequiredQualification('');
|
|
70
|
+
}
|
|
71
|
+
getPositions();
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setMessage(err.response?.data?.message || 'Failed to delete position');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleCancel = () => {
|
|
78
|
+
setEditingId(null);
|
|
79
|
+
setPosName('');
|
|
80
|
+
setRequiredQualification('');
|
|
81
|
+
setMessage('');
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const isSuccess = message.includes('successfully');
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div>
|
|
88
|
+
<h1 className="mb-6 text-2xl font-bold">Position Management</h1>
|
|
89
|
+
|
|
90
|
+
{message && (
|
|
91
|
+
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
95
|
+
<h2 className="font-semibold">{editingId ? 'Edit Position' : 'Add Position'}</h2>
|
|
96
|
+
<div>
|
|
97
|
+
<label className="mb-1 block text-sm">Position Name</label>
|
|
98
|
+
<input
|
|
99
|
+
type="text"
|
|
100
|
+
value={posName}
|
|
101
|
+
onChange={(e) => setPosName(e.target.value)}
|
|
102
|
+
className="input-field"
|
|
103
|
+
placeholder="e.g. Software Engineer"
|
|
104
|
+
required
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
<div>
|
|
108
|
+
<label className="mb-1 block text-sm">Required Qualification</label>
|
|
109
|
+
<input
|
|
110
|
+
type="text"
|
|
111
|
+
value={requiredQualification}
|
|
112
|
+
onChange={(e) => setRequiredQualification(e.target.value)}
|
|
113
|
+
className="input-field"
|
|
114
|
+
placeholder="e.g. Bachelor’s in Computer Science"
|
|
115
|
+
required
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
<div className="flex items-center gap-3">
|
|
119
|
+
<button type="submit" className="btn-primary">
|
|
120
|
+
{editingId ? 'Update Position' : 'Add Position'}
|
|
121
|
+
</button>
|
|
122
|
+
{editingId && (
|
|
123
|
+
<button type="button" className="btn-secondary" onClick={handleCancel}>
|
|
124
|
+
Cancel
|
|
125
|
+
</button>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</form>
|
|
129
|
+
|
|
130
|
+
<div className="box">
|
|
131
|
+
<h2 className="mb-3 font-semibold">Position List ({positions.length})</h2>
|
|
132
|
+
{positions.length === 0 ? (
|
|
133
|
+
<p className="text-gray-600">No positions yet.</p>
|
|
134
|
+
) : (
|
|
135
|
+
<div className="overflow-x-auto">
|
|
136
|
+
<table className="data-table">
|
|
137
|
+
<thead>
|
|
138
|
+
<tr>
|
|
139
|
+
<th>Position</th>
|
|
140
|
+
<th>Qualification</th>
|
|
141
|
+
<th>Actions</th>
|
|
142
|
+
</tr>
|
|
143
|
+
</thead>
|
|
144
|
+
<tbody>
|
|
145
|
+
{positions.map((pos) => (
|
|
146
|
+
<tr key={pos._id}>
|
|
147
|
+
<td>{pos.posName}</td>
|
|
148
|
+
<td>{pos.requiredQualification}</td>
|
|
149
|
+
<td className="space-x-2">
|
|
150
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(pos)}>
|
|
151
|
+
Edit
|
|
152
|
+
</button>
|
|
153
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(pos._id)}>
|
|
154
|
+
Delete
|
|
155
|
+
</button>
|
|
156
|
+
</td>
|
|
157
|
+
</tr>
|
|
158
|
+
))}
|
|
159
|
+
</tbody>
|
|
160
|
+
</table>
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default Position;
|
|
169
|
+
|