alpe-temp 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/backend-project/package-lock.json +131 -0
  2. package/backend-project/package.json +3 -1
  3. package/backend-project/src/app.js +33 -55
  4. package/backend-project/src/config/app.config.js +1 -49
  5. package/backend-project/src/config/env.js +2 -10
  6. package/backend-project/src/middleware/auth.middleware.js +3 -26
  7. package/backend-project/src/modules/auth/auth.controller.js +15 -19
  8. package/backend-project/src/modules/auth/auth.routes.js +4 -8
  9. package/backend-project/src/modules/auth/auth.service.js +9 -31
  10. package/backend-project/src/modules/auth/user.model.js +10 -33
  11. package/backend-project/src/modules/department/department.controller.js +0 -4
  12. package/backend-project/src/modules/department/department.model.js +1 -4
  13. package/backend-project/src/modules/department/department.routes.js +0 -1
  14. package/backend-project/src/modules/department/department.service.js +1 -9
  15. package/backend-project/src/modules/employee/employee.controller.js +2 -10
  16. package/backend-project/src/modules/employee/employee.model.js +15 -9
  17. package/backend-project/src/modules/employee/employee.routes.js +4 -6
  18. package/backend-project/src/modules/employee/employee.service.js +20 -5
  19. package/backend-project/src/modules/position/position.controller.js +50 -0
  20. package/backend-project/src/modules/position/position.model.js +8 -0
  21. package/backend-project/src/modules/position/position.routes.js +14 -0
  22. package/backend-project/src/modules/position/position.service.js +21 -0
  23. package/backend-project/src/modules/reports/reports.controller.js +16 -28
  24. package/backend-project/src/modules/reports/reports.routes.js +2 -2
  25. package/backend-project/src/seed.js +69 -15
  26. package/backend-project/src/utils/token.js +1 -27
  27. package/frontend-project/dist/assets/index-BXwcQ8Za.css +1 -0
  28. package/frontend-project/dist/assets/index-Bo0aORq7.js +20 -0
  29. package/frontend-project/dist/index.html +3 -3
  30. package/frontend-project/index.html +1 -1
  31. package/frontend-project/src/Auth/Login.jsx +15 -25
  32. package/frontend-project/src/Auth/Register.jsx +92 -183
  33. package/frontend-project/src/Intro.jsx +4 -9
  34. package/frontend-project/src/LayOut.jsx +10 -23
  35. package/frontend-project/src/api/ApiClient.js +19 -60
  36. package/frontend-project/src/layouts/BottomNav.jsx +22 -105
  37. package/frontend-project/src/layouts/TopNav.jsx +19 -98
  38. package/frontend-project/src/layouts/useShell.js +30 -44
  39. package/frontend-project/src/main.jsx +2 -3
  40. package/frontend-project/src/pages/Department.jsx +21 -58
  41. package/frontend-project/src/pages/Employee.jsx +131 -113
  42. package/frontend-project/src/pages/Home.jsx +36 -36
  43. package/frontend-project/src/pages/Position.jsx +161 -0
  44. package/frontend-project/src/pages/Reports.jsx +81 -68
  45. package/package.json +4 -2
  46. package/server-test-err.txt +0 -0
  47. package/server-test-out.txt +0 -0
  48. package/backend-project/src/modules/_example/example.controller.js +0 -82
  49. package/backend-project/src/modules/_example/example.model.js +0 -47
  50. package/backend-project/src/modules/_example/example.routes.js +0 -43
  51. package/backend-project/src/modules/_example/example.service.js +0 -58
  52. package/backend-project/src/modules/excel/excel.controller.js +0 -61
  53. package/backend-project/src/modules/excel/excel.routes.js +0 -13
  54. package/backend-project/src/modules/excel/excel.service.js +0 -303
  55. package/backend-project/src/modules/salary/salary.controller.js +0 -70
  56. package/backend-project/src/modules/salary/salary.model.js +0 -23
  57. package/backend-project/src/modules/salary/salary.routes.js +0 -16
  58. package/backend-project/src/modules/salary/salary.service.js +0 -44
  59. package/frontend-project/dist/assets/index-B08ICGra.js +0 -20
  60. package/frontend-project/dist/assets/index-D_cqT2Z6.css +0 -1
@@ -1,78 +1,77 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
- import { Plus, Download, Pencil, Trash2 } from 'lucide-react';
2
+ import { Plus, Pencil, Trash2, Search } from 'lucide-react';
3
3
  import Table from '../components/Table';
4
4
  import Modal from '../components/Modal';
5
5
  import Button from '../components/Button';
6
6
  import FormField from '../components/FormField';
7
7
  import { useToast } from '../components/Toast';
8
- import { employeeApi, departmentApi, excelApi } from '../api/ApiClient';
8
+ import { employeeApi, departmentApi, positionApi } from '../api/ApiClient';
9
+
10
+ const STATUS_OPTIONS = [
11
+ { value: 'on mission', label: 'On Mission' },
12
+ { value: 'on leave', label: 'On Leave' },
13
+ { value: 'left', label: 'Left' },
14
+ { value: 'blacklisted', label: 'Blacklisted' },
15
+ { value: 'deceased', label: 'Deceased' },
16
+ ];
9
17
 
10
18
  const formatDate = (iso) => {
11
19
  if (!iso) return '—';
12
20
  try { return new Date(iso).toISOString().split('T')[0]; } catch { return '—'; }
13
21
  };
14
22
 
23
+ function StatusBadge({ status }) {
24
+ const colors = {
25
+ 'on leave': 'bg-yellow-100 text-yellow-800',
26
+ 'on mission': 'bg-blue-100 text-blue-800',
27
+ left: 'bg-gray-100 text-gray-800',
28
+ blacklisted: 'bg-red-100 text-red-800',
29
+ deceased: 'bg-gray-200 text-gray-600',
30
+ };
31
+ return <span className={`px-2 py-0.5 text-[11px] font-semibold ${colors[status] || 'bg-gray-100 text-gray-800'}`}>{status}</span>;
32
+ }
33
+
15
34
  export default function Employee() {
16
35
  const toast = useToast();
17
36
  const [employees, setEmployees] = useState([]);
18
37
  const [departments, setDepartments] = useState([]);
38
+ const [positions, setPositions] = useState([]);
19
39
  const [loading, setLoading] = useState(true);
20
- const [exporting, setExporting] = useState(false);
40
+ const [search, setSearch] = useState('');
21
41
  const [createOpen, setCreateOpen] = useState(false);
22
42
  const [editTarget, setEditTarget] = useState(null);
23
43
  const [deleteTarget, setDeleteTarget] = useState(null);
24
44
 
25
- const loadAll = useCallback(async () => {
45
+ const loadAll = useCallback(async (searchTerm) => {
26
46
  setLoading(true);
27
47
  try {
28
- const [empRes, deptRes] = await Promise.allSettled([
29
- employeeApi.list(),
48
+ const [empRes, deptRes, posRes] = await Promise.allSettled([
49
+ employeeApi.list(searchTerm || undefined),
30
50
  departmentApi.list(),
51
+ positionApi.list(),
31
52
  ]);
32
53
  if (empRes.status === 'fulfilled') setEmployees(empRes.value?.data?.employees ?? []);
33
54
  if (deptRes.status === 'fulfilled') setDepartments(deptRes.value?.data?.departments ?? []);
34
- } catch (err) {
35
- toast.error('Failed to load data');
36
- } finally {
37
- setLoading(false);
38
- }
55
+ if (posRes.status === 'fulfilled') setPositions(posRes.value?.data?.positions ?? []);
56
+ } catch { toast.error('Failed to load data'); }
57
+ finally { setLoading(false); }
39
58
  }, []);
40
59
 
41
60
  useEffect(() => { loadAll(); }, [loadAll]);
42
61
 
43
- const handleExport = async () => {
44
- setExporting(true);
45
- try {
46
- try { await excelApi.exportUsers(); toast.success('Report downloaded'); }
47
- catch {
48
- const rows = employees.map((e) => ({
49
- 'Emp #': e.employeeNumber,
50
- 'First Name': e.firstName,
51
- 'Last Name': e.lastName,
52
- Position: e.position,
53
- Gender: e.gender,
54
- 'Hired Date': formatDate(e.hiredDate),
55
- Department: e.department?.departmentName ?? '',
56
- }));
57
- await excelApi.exportCustom('Employees', rows, 'employees-report');
58
- toast.success('Report downloaded');
59
- }
60
- } catch (err) {
61
- toast.error(err.message ?? 'Export failed');
62
- } finally {
63
- setExporting(false);
64
- }
65
- };
62
+ const handleSearch = () => loadAll(search);
63
+ const handleClearSearch = () => { setSearch(''); loadAll(); };
66
64
 
67
65
  const columns = [
68
- { key: 'employeeNumber', label: 'Emp #' },
69
- { key: 'firstName', label: 'First Name' },
70
- { key: 'lastName', label: 'Last Name' },
71
- { key: 'position', label: 'Position' },
72
- { key: 'gender', label: 'Gender' },
73
- { key: 'telephone', label: 'Phone' },
74
- { key: 'hiredDate', label: 'Hired', render: (v) => formatDate(v) },
75
- { key: 'department', label: 'Department', render: (v) => v?.departmentName ?? '—' },
66
+ { key: 'empFirstName', label: 'First Name' },
67
+ { key: 'empLastName', label: 'Last Name' },
68
+ { key: 'empGender', label: 'Gender' },
69
+ { key: 'empEmail', label: 'Email' },
70
+ { key: 'empTelephone', label: 'Phone' },
71
+ { key: 'empHireDate', label: 'Hired', render: (v) => formatDate(v) },
72
+ { key: 'empStatus', label: 'Status', render: (v) => <StatusBadge status={v} /> },
73
+ { key: 'department', label: 'Dept', render: (v) => v?.departName ?? '—' },
74
+ { key: 'position', label: 'Position', render: (v) => v?.posName ?? '—' },
76
75
  {
77
76
  key: 'actions', label: '', align: 'right',
78
77
  render: (_, row) => (
@@ -91,37 +90,48 @@ export default function Employee() {
91
90
  <h1 className="text-[18px] font-bold text-gray-800">Employees</h1>
92
91
  <p className="text-[13px] text-gray-400 mt-0.5">{employees.length} total records</p>
93
92
  </div>
94
- <div className="flex ">
95
- <Button size="sm" variant='dark' icon={<Plus className="w-3.5 h-3.5" />} onClick={() => setCreateOpen(true)}>Add Employee</Button>
93
+ <Button size="sm" variant='dark' icon={<Plus className="w-3.5 h-3.5" />} onClick={() => setCreateOpen(true)}>Add Employee</Button>
94
+ </div>
95
+ <div className="flex items-center gap-2">
96
+ <div className="relative w-72">
97
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
98
+ <input
99
+ type="text" value={search} onChange={(e) => setSearch(e.target.value)}
100
+ placeholder="Search employees..." onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
101
+ className="w-full pl-9 pr-3 py-2 text-[13px] border border-gray-200 rounded-md focus:outline-none focus:ring-1 focus:ring-[#008A75]"
102
+ />
96
103
  </div>
104
+ <Button size="sm" onClick={handleSearch}>Search</Button>
105
+ {search && <Button size="sm" variant="ghost" onClick={handleClearSearch}>Clear</Button>}
97
106
  </div>
98
107
  <div className="bg-white border border-gray-200 p-5">
99
108
  <Table columns={columns} data={employees} rowKey="_id" loading={loading} emptyMessage="No employees yet." maxHeight="480px" />
100
109
  </div>
101
- {createOpen && <CreateModal departments={departments} onClose={() => setCreateOpen(false)} onCreated={() => { setCreateOpen(false); loadAll(); }} />}
102
- {editTarget && <EditModal employee={editTarget} departments={departments} onClose={() => setEditTarget(null)} onUpdated={() => { setEditTarget(null); loadAll(); }} />}
110
+ {createOpen && <CreateModal departments={departments} positions={positions} onClose={() => setCreateOpen(false)} onCreated={() => { setCreateOpen(false); loadAll(); }} />}
111
+ {editTarget && <EditModal employee={editTarget} departments={departments} positions={positions} onClose={() => setEditTarget(null)} onUpdated={() => { setEditTarget(null); loadAll(); }} />}
103
112
  {deleteTarget && <DeleteModal employee={deleteTarget} onClose={() => setDeleteTarget(null)} onDeleted={() => { setDeleteTarget(null); loadAll(); }} />}
104
113
  </div>
105
114
  );
106
115
  }
107
116
 
108
- function CreateModal({ departments, onClose, onCreated }) {
117
+ function CreateModal({ departments, positions, onClose, onCreated }) {
109
118
  const toast = useToast();
110
119
  const now = new Date().toISOString().split('T')[0];
111
- const [form, setForm] = useState({ employeeNumber: '', firstName: '', lastName: '', position: '', address: '', telephone: '', gender: '', hiredDate: now, department: '' });
120
+ const [form, setForm] = useState({
121
+ empFirstName: '', empLastName: '', empGender: '', empDateOfBirth: '',
122
+ empEmail: '', empTelephone: '', empAddress: '', empHireDate: now,
123
+ empStatus: 'on mission', department: '', position: '',
124
+ });
112
125
  const [loading, setLoading] = useState(false);
113
126
  const [errors, setErrors] = useState({});
114
-
115
127
  const set = (field) => (val) => setForm((f) => ({ ...f, [field]: val }));
116
128
 
117
129
  const validate = () => {
118
130
  const e = {};
119
- if (!form.employeeNumber) e.employeeNumber = 'Required';
120
- if (!form.firstName) e.firstName = 'Required';
121
- if (!form.lastName) e.lastName = 'Required';
122
- if (!form.position) e.position = 'Required';
123
- if (!form.gender) e.gender = 'Required';
124
- if (!form.hiredDate) e.hiredDate = 'Required';
131
+ if (!form.empFirstName) e.empFirstName = 'Required';
132
+ if (!form.empLastName) e.empLastName = 'Required';
133
+ if (!form.empGender) e.empGender = 'Required';
134
+ if (!form.empHireDate) e.empHireDate = 'Required';
125
135
  if (!form.department) e.department = 'Required';
126
136
  setErrors(e);
127
137
  return Object.keys(e).length === 0;
@@ -132,51 +142,53 @@ function CreateModal({ departments, onClose, onCreated }) {
132
142
  setLoading(true);
133
143
  try {
134
144
  await employeeApi.create(form);
135
- toast.success('Employee created successfully');
145
+ toast.success('Employee created');
136
146
  onCreated();
137
- } catch (err) {
138
- toast.error(err.message ?? 'Failed to create employee');
139
- } finally {
140
- setLoading(false);
141
- }
147
+ } catch (err) { toast.error(err.message ?? 'Failed to create employee'); }
148
+ finally { setLoading(false); }
142
149
  };
143
150
 
144
- const deptOptions = departments.map((d) => ({ value: d._id, label: `${d.departmentCode} - ${d.departmentName}` }));
151
+ const deptOptions = departments.map((d) => ({ value: d._id, label: d.departName }));
152
+ const posOptions = positions.map((p) => ({ value: p._id, label: p.posName }));
145
153
 
146
154
  return (
147
- <Modal open title="Add Employee" size="lg" onClose={onClose}>
155
+ <Modal open title="Add Employee" size="2xl" onClose={onClose}>
148
156
  <div className="grid grid-cols-2 gap-3">
149
- <FormField label="Employee #" required error={errors.employeeNumber}>
150
- <FormField.Input value={form.employeeNumber} onChange={set('employeeNumber')} placeholder="e.g. EMP001" />
151
- </FormField>
152
- <FormField label="First Name" required error={errors.firstName}>
153
- <FormField.Input value={form.firstName} onChange={set('firstName')} placeholder="Jane" />
157
+ <FormField label="First Name" required error={errors.empFirstName}>
158
+ <FormField.Input value={form.empFirstName} onChange={set('empFirstName')} placeholder="First name" />
154
159
  </FormField>
155
- <FormField label="Last Name" required error={errors.lastName}>
156
- <FormField.Input value={form.lastName} onChange={set('lastName')} placeholder="Doe" />
160
+ <FormField label="Last Name" required error={errors.empLastName}>
161
+ <FormField.Input value={form.empLastName} onChange={set('empLastName')} placeholder="Last name" />
157
162
  </FormField>
158
- <FormField label="Position" required error={errors.position}>
159
- <FormField.Select value={form.position} onChange={set('position')} placeholder="Select position" options={[
160
- { value: 'Admin', label: 'Admin' }, { value: 'Employee', label: 'Employee' }, { value: 'Manager', label: 'Manager' }, { value: 'Mechanic', label: 'Mechanic' }, { value: 'Cashier', label: 'Cashier' },
163
+ <FormField label="Gender" required error={errors.empGender}>
164
+ <FormField.Select value={form.empGender} onChange={set('empGender')} placeholder="Select gender" options={[
165
+ { value: 'male', label: 'Male' }, { value: 'female', label: 'Female' },
161
166
  ]} />
162
167
  </FormField>
168
+ <FormField label="Date of Birth">
169
+ <FormField.Input type="date" value={form.empDateOfBirth} onChange={set('empDateOfBirth')} />
170
+ </FormField>
171
+ <FormField label="Email">
172
+ <FormField.Input type="email" value={form.empEmail} onChange={set('empEmail')} placeholder="employee@company.com" />
173
+ </FormField>
174
+ <FormField label="Telephone">
175
+ <FormField.Input type="tel" value={form.empTelephone} onChange={set('empTelephone')} placeholder="07xxxxxxxx" />
176
+ </FormField>
163
177
  <FormField label="Department" required error={errors.department}>
164
178
  <FormField.Select value={form.department} onChange={set('department')} placeholder="Select department" options={deptOptions} />
165
179
  </FormField>
166
- <FormField label="Gender" required error={errors.gender}>
167
- <FormField.Select value={form.gender} onChange={set('gender')} placeholder="Select gender" options={[
168
- { value: 'male', label: 'Male' }, { value: 'female', label: 'Female' },
169
- ]} />
180
+ <FormField label="Position">
181
+ <FormField.Select value={form.position} onChange={set('position')} placeholder="Select position" options={posOptions} />
170
182
  </FormField>
171
- <FormField label="Hired Date" required error={errors.hiredDate}>
172
- <FormField.Input type="date" value={form.hiredDate} onChange={set('hiredDate')} />
183
+ <FormField label="Hire Date" required error={errors.empHireDate}>
184
+ <FormField.Input type="date" value={form.empHireDate} onChange={set('empHireDate')} />
173
185
  </FormField>
174
- <FormField label="Phone Number">
175
- <FormField.Input type="tel" value={form.telephone} onChange={set('telephone')} placeholder="07xxxxxxxx" />
186
+ <FormField label="Status">
187
+ <FormField.Select value={form.empStatus} onChange={set('empStatus')} options={STATUS_OPTIONS} />
176
188
  </FormField>
177
189
  <div className="col-span-2">
178
190
  <FormField label="Address">
179
- <FormField.Textarea value={form.address} onChange={set('address')} placeholder="Kigali, Rwanda" />
191
+ <FormField.Textarea value={form.empAddress} onChange={set('empAddress')} placeholder="Kigali, Rwanda" />
180
192
  </FormField>
181
193
  </div>
182
194
  </div>
@@ -188,22 +200,25 @@ function CreateModal({ departments, onClose, onCreated }) {
188
200
  );
189
201
  }
190
202
 
191
- function EditModal({ employee, departments, onClose, onUpdated }) {
203
+ function EditModal({ employee, departments, positions, onClose, onUpdated }) {
192
204
  const toast = useToast();
193
205
  const [form, setForm] = useState({
194
- employeeNumber: employee.employeeNumber || '',
195
- firstName: employee.firstName || '',
196
- lastName: employee.lastName || '',
197
- position: employee.position || '',
198
- address: employee.address || '',
199
- telephone: employee.telephone || '',
200
- gender: employee.gender || '',
201
- hiredDate: employee.hiredDate ? employee.hiredDate.split('T')[0] : '',
206
+ empFirstName: employee.empFirstName || '',
207
+ empLastName: employee.empLastName || '',
208
+ empGender: employee.empGender || '',
209
+ empDateOfBirth: employee.empDateOfBirth ? employee.empDateOfBirth.split('T')[0] : '',
210
+ empEmail: employee.empEmail || '',
211
+ empTelephone: employee.empTelephone || '',
212
+ empAddress: employee.empAddress || '',
213
+ empHireDate: employee.empHireDate ? employee.empHireDate.split('T')[0] : '',
214
+ empStatus: employee.empStatus || 'on mission',
202
215
  department: employee.department?._id || '',
216
+ position: employee.position?._id || '',
203
217
  });
204
218
  const [loading, setLoading] = useState(false);
205
219
  const set = (f) => (v) => setForm((p) => ({ ...p, [f]: v }));
206
- const deptOptions = departments.map((d) => ({ value: d._id, label: `${d.departmentCode} - ${d.departmentName}` }));
220
+ const deptOptions = departments.map((d) => ({ value: d._id, label: d.departName }));
221
+ const posOptions = positions.map((p) => ({ value: p._id, label: p.posName }));
207
222
 
208
223
  const handleSave = async () => {
209
224
  setLoading(true);
@@ -211,28 +226,34 @@ function EditModal({ employee, departments, onClose, onUpdated }) {
211
226
  await employeeApi.update(employee._id, form);
212
227
  toast.success('Employee updated');
213
228
  onUpdated();
214
- } catch (err) {
215
- toast.error(err.message ?? 'Update failed');
216
- } finally {
217
- setLoading(false);
218
- }
229
+ } catch (err) { toast.error(err.message ?? 'Update failed'); }
230
+ finally { setLoading(false); }
219
231
  };
220
232
 
221
233
  return (
222
- <Modal open title={`Edit — ${employee.firstName} ${employee.lastName}`} size="lg" onClose={onClose}>
234
+ <Modal open title={`Edit — ${employee.empFirstName} ${employee.empLastName}`} size="2xl" onClose={onClose}>
223
235
  <div className="grid grid-cols-2 gap-3">
224
- <FormField label="Employee #"><FormField.Input value={form.employeeNumber} onChange={set('employeeNumber')} /></FormField>
225
- <FormField label="First Name"><FormField.Input value={form.firstName} onChange={set('firstName')} /></FormField>
226
- <FormField label="Last Name"><FormField.Input value={form.lastName} onChange={set('lastName')} /></FormField>
227
- <FormField label="Position"><FormField.Input value={form.position} onChange={set('position')} /></FormField>
236
+ <FormField label="First Name"><FormField.Input value={form.empFirstName} onChange={set('empFirstName')} /></FormField>
237
+ <FormField label="Last Name"><FormField.Input value={form.empLastName} onChange={set('empLastName')} /></FormField>
238
+ <FormField label="Gender">
239
+ <FormField.Select value={form.empGender} onChange={set('empGender')} options={[{ value: 'male', label: 'Male' }, { value: 'female', label: 'Female' }]} />
240
+ </FormField>
241
+ <FormField label="Date of Birth"><FormField.Input type="date" value={form.empDateOfBirth} onChange={set('empDateOfBirth')} /></FormField>
242
+ <FormField label="Email"><FormField.Input type="email" value={form.empEmail} onChange={set('empEmail')} /></FormField>
243
+ <FormField label="Telephone"><FormField.Input value={form.empTelephone} onChange={set('empTelephone')} /></FormField>
228
244
  <FormField label="Department">
229
245
  <FormField.Select value={form.department} onChange={set('department')} options={deptOptions} placeholder="Select department" />
230
246
  </FormField>
231
- <FormField label="Gender">
232
- <FormField.Select value={form.gender} onChange={set('gender')} options={[{ value: 'male', label: 'Male' }, { value: 'female', label: 'Female' }]} placeholder="Select gender" />
247
+ <FormField label="Position">
248
+ <FormField.Select value={form.position} onChange={set('position')} options={posOptions} placeholder="Select position" />
249
+ </FormField>
250
+ <FormField label="Hire Date"><FormField.Input type="date" value={form.empHireDate} onChange={set('empHireDate')} /></FormField>
251
+ <FormField label="Status">
252
+ <FormField.Select value={form.empStatus} onChange={set('empStatus')} options={STATUS_OPTIONS} />
233
253
  </FormField>
234
- <FormField label="Hired Date"><FormField.Input type="date" value={form.hiredDate} onChange={set('hiredDate')} /></FormField>
235
- <FormField label="Phone"><FormField.Input value={form.telephone} onChange={set('telephone')} /></FormField>
254
+ <div className="col-span-2">
255
+ <FormField label="Address"><FormField.Textarea value={form.empAddress} onChange={set('empAddress')} /></FormField>
256
+ </div>
236
257
  </div>
237
258
  <Modal.Footer>
238
259
  <Button variant="outline" onClick={onClose}>Cancel</Button>
@@ -250,20 +271,17 @@ function DeleteModal({ employee, onClose, onDeleted }) {
250
271
  setLoading(true);
251
272
  try {
252
273
  await employeeApi.remove(employee._id);
253
- toast.success(`${employee.firstName} ${employee.lastName} removed`);
274
+ toast.success(`${employee.empFirstName} ${employee.empLastName} removed`);
254
275
  onDeleted();
255
- } catch (err) {
256
- toast.error(err.message ?? 'Delete failed');
257
- } finally {
258
- setLoading(false);
259
- }
276
+ } catch (err) { toast.error(err.message ?? 'Delete failed'); }
277
+ finally { setLoading(false); }
260
278
  };
261
279
 
262
280
  return (
263
281
  <Modal open title="Confirm Deletion" size="sm" onClose={onClose}>
264
282
  <p className="text-[13px] text-gray-600 leading-relaxed">
265
283
  Are you sure you want to permanently delete{' '}
266
- <span className="font-semibold text-gray-800">{employee.firstName} {employee.lastName}</span>?
284
+ <span className="font-semibold text-gray-800">{employee.empFirstName} {employee.empLastName}</span>?
267
285
  </p>
268
286
  <Modal.Footer>
269
287
  <Button variant="outline" onClick={onClose}>Cancel</Button>
@@ -1,56 +1,58 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { StatCard } from '../components/Card';
3
3
  import Table from '../components/Table';
4
- import { employeeApi, salaryApi, departmentApi } from '../api/ApiClient';
4
+ import { employeeApi, departmentApi, positionApi } from '../api/ApiClient';
5
5
 
6
6
  const TABLE_COLUMNS = [
7
- { key: 'employeeNumber', label: 'Emp #' },
8
- { key: 'firstName', label: 'First Name' },
9
- { key: 'lastName', label: 'Last Name' },
10
- { key: 'position', label: 'Position', render: (v) => <Badge>{v}</Badge> },
11
- { key: 'gender', label: 'Gender' },
12
- { key: 'hiredDate', label: 'Hired', render: (v) => formatDate(v) },
13
- { key: 'department', label: 'Department', render: (v) => v?.departmentName ?? '—' },
7
+ { key: 'empFirstName', label: 'First Name' },
8
+ { key: 'empLastName', label: 'Last Name' },
9
+ { key: 'empGender', label: 'Gender' },
10
+ { key: 'empStatus', label: 'Status', render: (v) => <Badge status={v}>{v}</Badge> },
11
+ { key: 'empEmail', label: 'Email' },
12
+ {
13
+ key: 'department',
14
+ label: 'Department',
15
+ render: (v) => v?.departName ?? '\u2014',
16
+ },
14
17
  ];
15
18
 
16
- const formatDate = (iso) => {
17
- if (!iso) return '—';
18
- try { return new Date(iso).toISOString().split('T')[0]; } catch { return '—'; }
19
- };
20
-
21
- function Badge({ children }) {
19
+ function Badge({ status, children }) {
20
+ const colors = {
21
+ 'on leave': 'bg-yellow-100 text-yellow-800',
22
+ 'on mission': 'bg-blue-100 text-blue-800',
23
+ left: 'bg-gray-100 text-gray-800',
24
+ blacklisted: 'bg-red-100 text-red-800',
25
+ deceased: 'bg-gray-200 text-gray-600',
26
+ };
22
27
  return (
23
- <span className="px-2 py-0.5 bg-[#E6F7F5] text-[#008A75] text-[11px] font-semibold">
28
+ <span className={`px-2 py-0.5 text-[11px] font-semibold ${colors[status] || 'bg-gray-100 text-gray-800'}`}>
24
29
  {children}
25
30
  </span>
26
31
  );
27
32
  }
28
33
 
29
34
  export default function Home() {
30
- const user = (() => { try { return JSON.parse(localStorage.getItem('user')); } catch { return {}; } })();
31
35
  const [employees, setEmployees] = useState([]);
32
36
  const [totalCount, setTotalCount] = useState(null);
33
- const [avgSalary, setAvgSalary] = useState(null);
34
37
  const [deptCount, setDeptCount] = useState(null);
38
+ const [posCount, setPosCount] = useState(null);
35
39
  const [loading, setLoading] = useState(true);
36
40
 
37
41
  useEffect(() => {
38
42
  const load = async () => {
39
43
  setLoading(true);
40
44
  try {
41
- const [empRes, countRes, salaryRes, deptRes] = await Promise.allSettled([
42
- employeeApi.list(),
43
- employeeApi.count(),
44
- salaryApi.average(),
45
- departmentApi.list(),
45
+ const [empRes, deptRes, posRes] = await Promise.allSettled([
46
+ employeeApi.list(), departmentApi.list(), positionApi.list(),
46
47
  ]);
47
- if (empRes.status === 'fulfilled') setEmployees(empRes.value?.data?.employees ?? []);
48
- if (countRes.status === 'fulfilled') setTotalCount(countRes.value?.data?.count ?? 0);
49
- if (salaryRes.status === 'fulfilled') setAvgSalary(salaryRes.value?.data?.averageSalary ?? 0);
48
+ if (empRes.status === 'fulfilled') {
49
+ const data = empRes.value?.data?.employees ?? [];
50
+ setEmployees(data);
51
+ setTotalCount(data.length);
52
+ }
50
53
  if (deptRes.status === 'fulfilled') setDeptCount(deptRes.value?.data?.departments?.length ?? 0);
51
- } finally {
52
- setLoading(false);
53
- }
54
+ if (posRes.status === 'fulfilled') setPosCount(posRes.value?.data?.positions?.length ?? 0);
55
+ } finally { setLoading(false); }
54
56
  };
55
57
  load();
56
58
  }, []);
@@ -59,16 +61,14 @@ export default function Home() {
59
61
 
60
62
  return (
61
63
  <div className="space-y-6">
62
- <div className="flex justify-between">
63
- <h1 className="text-[18px] font-bold text-gray-800">
64
- Welcome back, <span className="text-[#008A75]">{user?.name ?? 'Admin'}</span>
65
- </h1>
66
- </div>
64
+ <h1 className="text-[18px] font-bold text-gray-800">
65
+ DAB Enterprise LTD — HRMS Dashboard
66
+ </h1>
67
67
  <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
68
68
  <StatCard label="Total Employees" value={fmt(totalCount)} loading={loading && totalCount == null} className="text-white bg-[#008A75]" />
69
- <StatCard label="Avg. Net Salary" value={avgSalary != null ? `${Math.round(avgSalary).toLocaleString()} RWF` : null} loading={loading && avgSalary == null} className="text-zinc-900 bg-zinc-50" />
70
- <StatCard label="Departments" value={fmt(deptCount)} loading={loading && deptCount == null} className="text-white bg-[#008A75]" />
71
- <StatCard label="Active Today" value={fmt(totalCount)} loading={loading && totalCount == null} className="text-zinc-900 bg-zinc-50" />
69
+ <StatCard label="Departments" value={fmt(deptCount)} loading={loading && deptCount == null} className="text-zinc-900 bg-zinc-50" />
70
+ <StatCard label="Positions" value={fmt(posCount)} loading={loading && posCount == null} className="text-white bg-[#008A75]" />
71
+ <StatCard label="On Leave" value={fmt(employees.filter(e => e.empStatus === 'on leave').length)} loading={loading && totalCount == null} className="text-zinc-900 bg-zinc-50" />
72
72
  </div>
73
73
  <div className="bg-white border border-gray-200 p-5">
74
74
  <h2 className="text-[13px] font-semibold text-gray-700 mb-4">Recent Employees</h2>