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
@@ -0,0 +1,161 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import { Plus, Pencil, Trash2 } from 'lucide-react';
3
+ import Table from '../components/Table';
4
+ import Modal from '../components/Modal';
5
+ import Button from '../components/Button';
6
+ import FormField from '../components/FormField';
7
+ import { useToast } from '../components/Toast';
8
+ import { positionApi } from '../api/ApiClient';
9
+
10
+ export default function Position() {
11
+ const toast = useToast();
12
+ const [positions, setPositions] = useState([]);
13
+ const [loading, setLoading] = useState(true);
14
+ const [createOpen, setCreateOpen] = useState(false);
15
+ const [editTarget, setEditTarget] = useState(null);
16
+ const [deleteTarget, setDeleteTarget] = useState(null);
17
+
18
+ const loadAll = useCallback(async () => {
19
+ setLoading(true);
20
+ try {
21
+ const res = await positionApi.list();
22
+ setPositions(res?.data?.positions ?? []);
23
+ } catch { toast.error('Failed to load positions'); }
24
+ finally { setLoading(false); }
25
+ }, []);
26
+
27
+ useEffect(() => { loadAll(); }, [loadAll]);
28
+
29
+ const columns = [
30
+ { key: 'posName', label: 'Position Name' },
31
+ { key: 'requiredQualification', label: 'Required Qualification' },
32
+ {
33
+ key: 'actions', label: '', align: 'right',
34
+ render: (_, row) => (
35
+ <div className="flex items-center justify-end gap-1.5">
36
+ <Button size="xs" variant="outline" icon={<Pencil className="w-3 h-3" />} onClick={() => setEditTarget(row)}>Edit</Button>
37
+ <Button size="xs" variant="danger" icon={<Trash2 className="w-3 h-3" />} onClick={() => setDeleteTarget(row)}>Delete</Button>
38
+ </div>
39
+ ),
40
+ },
41
+ ];
42
+
43
+ return (
44
+ <div className="space-y-5">
45
+ <div className="flex items-start justify-between">
46
+ <div>
47
+ <h1 className="text-[18px] font-bold text-gray-800">Positions</h1>
48
+ <p className="text-[13px] text-gray-400 mt-0.5">{positions.length} positions</p>
49
+ </div>
50
+ <Button size="sm" icon={<Plus className="w-3.5 h-3.5" />} onClick={() => setCreateOpen(true)}>Add Position</Button>
51
+ </div>
52
+ <div className="bg-white border border-gray-200 p-5">
53
+ <Table columns={columns} data={positions} rowKey="_id" loading={loading} emptyMessage="No positions yet." maxHeight="480px" />
54
+ </div>
55
+ {createOpen && <CreateModal onClose={() => setCreateOpen(false)} onCreated={() => { setCreateOpen(false); loadAll(); }} />}
56
+ {editTarget && <EditModal position={editTarget} onClose={() => setEditTarget(null)} onUpdated={() => { setEditTarget(null); loadAll(); }} />}
57
+ {deleteTarget && <DeleteModal position={deleteTarget} onClose={() => setDeleteTarget(null)} onDeleted={() => { setDeleteTarget(null); loadAll(); }} />}
58
+ </div>
59
+ );
60
+ }
61
+
62
+ function CreateModal({ onClose, onCreated }) {
63
+ const toast = useToast();
64
+ const [form, setForm] = useState({ posName: '', requiredQualification: '' });
65
+ const [loading, setLoading] = useState(false);
66
+ const [errors, setErrors] = useState({});
67
+ const set = (f) => (v) => setForm((p) => ({ ...p, [f]: v }));
68
+
69
+ const validate = () => {
70
+ const e = {};
71
+ if (!form.posName) e.posName = 'Required';
72
+ setErrors(e);
73
+ return Object.keys(e).length === 0;
74
+ };
75
+
76
+ const handleSubmit = async () => {
77
+ if (!validate()) return;
78
+ setLoading(true);
79
+ try {
80
+ await positionApi.create(form);
81
+ toast.success('Position created');
82
+ onCreated();
83
+ } catch (err) { toast.error(err.message ?? 'Failed to create position'); }
84
+ finally { setLoading(false); }
85
+ };
86
+
87
+ return (
88
+ <Modal open title="Add Position" size="sm" onClose={onClose}>
89
+ <div className="space-y-3">
90
+ <FormField label="Position Name" required error={errors.posName}>
91
+ <FormField.Input value={form.posName} onChange={set('posName')} placeholder="e.g. Sales Representative" />
92
+ </FormField>
93
+ <FormField label="Required Qualification">
94
+ <FormField.Input value={form.requiredQualification} onChange={set('requiredQualification')} placeholder="e.g. Bachelor in Business" />
95
+ </FormField>
96
+ </div>
97
+ <Modal.Footer>
98
+ <Button variant="outline" onClick={onClose}>Cancel</Button>
99
+ <Button loading={loading} onClick={handleSubmit}>Create Position</Button>
100
+ </Modal.Footer>
101
+ </Modal>
102
+ );
103
+ }
104
+
105
+ function EditModal({ position, onClose, onUpdated }) {
106
+ const toast = useToast();
107
+ const [form, setForm] = useState({
108
+ posName: position.posName || '',
109
+ requiredQualification: position.requiredQualification || '',
110
+ });
111
+ const [loading, setLoading] = useState(false);
112
+ const set = (f) => (v) => setForm((p) => ({ ...p, [f]: v }));
113
+
114
+ const handleSave = async () => {
115
+ setLoading(true);
116
+ try {
117
+ await positionApi.update(position._id, form);
118
+ toast.success('Position updated');
119
+ onUpdated();
120
+ } catch (err) { toast.error(err.message ?? 'Update failed'); }
121
+ finally { setLoading(false); }
122
+ };
123
+
124
+ return (
125
+ <Modal open title={`Edit — ${position.posName}`} size="sm" onClose={onClose}>
126
+ <div className="space-y-3">
127
+ <FormField label="Position Name"><FormField.Input value={form.posName} onChange={set('posName')} /></FormField>
128
+ <FormField label="Required Qualification"><FormField.Input value={form.requiredQualification} onChange={set('requiredQualification')} /></FormField>
129
+ </div>
130
+ <Modal.Footer>
131
+ <Button variant="outline" onClick={onClose}>Cancel</Button>
132
+ <Button loading={loading} onClick={handleSave}>Save Changes</Button>
133
+ </Modal.Footer>
134
+ </Modal>
135
+ );
136
+ }
137
+
138
+ function DeleteModal({ position, onClose, onDeleted }) {
139
+ const toast = useToast();
140
+ const [loading, setLoading] = useState(false);
141
+
142
+ const handleDelete = async () => {
143
+ setLoading(true);
144
+ try {
145
+ await positionApi.remove(position._id);
146
+ toast.success(`${position.posName} removed`);
147
+ onDeleted();
148
+ } catch (err) { toast.error(err.message ?? 'Delete failed'); }
149
+ finally { setLoading(false); }
150
+ };
151
+
152
+ return (
153
+ <Modal open title="Confirm Deletion" size="sm" onClose={onClose}>
154
+ <p className="text-[13px] text-gray-600">Delete <strong>{position.posName}</strong>? This cannot be undone.</p>
155
+ <Modal.Footer>
156
+ <Button variant="outline" onClick={onClose}>Cancel</Button>
157
+ <Button variant="danger" loading={loading} icon={<Trash2 className="w-3.5 h-3.5" />} onClick={handleDelete}>Delete</Button>
158
+ </Modal.Footer>
159
+ </Modal>
160
+ );
161
+ }
@@ -1,91 +1,104 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
- import { Download, FileText } from 'lucide-react';
3
- import Table from '../components/Table';
4
- import Button from '../components/Button';
5
- import FormField from '../components/FormField';
2
+ import { FileText, Users } from 'lucide-react';
6
3
  import { useToast } from '../components/Toast';
7
- import { reportsApi, excelApi } from '../api/ApiClient';
4
+ import { reportsApi } from '../api/ApiClient';
8
5
 
9
6
  export default function Reports() {
10
7
  const toast = useToast();
11
- const [payroll, setPayroll] = useState([]);
8
+ const [report, setReport] = useState(null);
12
9
  const [loading, setLoading] = useState(true);
13
- const [exporting, setExporting] = useState(false);
14
- const [month, setMonth] = useState('');
15
10
 
16
- const loadPayroll = useCallback(async (selectedMonth) => {
11
+ const loadReport = useCallback(async () => {
17
12
  setLoading(true);
18
13
  try {
19
- const res = await reportsApi.monthlyPayroll(selectedMonth || undefined);
20
- setPayroll(res?.data?.payroll ?? []);
21
- } catch (err) {
22
- toast.error('Failed to load payroll report');
23
- } finally {
24
- setLoading(false);
25
- }
14
+ const res = await reportsApi.employeesOnLeave();
15
+ setReport(res?.data ?? null);
16
+ } catch { toast.error('Failed to load report'); }
17
+ finally { setLoading(false); }
26
18
  }, []);
27
19
 
28
- useEffect(() => { loadPayroll(); }, [loadPayroll]);
20
+ useEffect(() => { loadReport(); }, [loadReport]);
29
21
 
30
- const handleFilter = () => loadPayroll(month);
31
- const handleClear = () => { setMonth(''); loadPayroll(); };
32
-
33
- const handleExport = async () => {
34
- setExporting(true);
35
- try {
36
- const rows = payroll.map((r) => ({
37
- 'First Name': r.firstName,
38
- 'Last Name': r.lastName,
39
- Position: r.position,
40
- Department: r.departmentName,
41
- 'Gross Salary': `${r.grossSalary?.toLocaleString()} RWF`,
42
- 'Deductions': `${r.totalDeduction?.toLocaleString()} RWF`,
43
- 'Net Salary': `${r.netSalary?.toLocaleString()} RWF`,
44
- Month: r.month,
45
- }));
46
- const filename = month ? `payroll-${month}` : 'payroll-all';
47
- await excelApi.exportCustom('Payroll', rows, filename);
48
- toast.success('Payroll report downloaded');
49
- } catch (err) {
50
- toast.error(err.message ?? 'Export failed');
51
- } finally {
52
- setExporting(false);
53
- }
54
- };
55
-
56
- const columns = [
57
- { key: 'firstName', label: 'First Name' },
58
- { key: 'lastName', label: 'Last Name' },
59
- { key: 'position', label: 'Position' },
60
- { key: 'departmentName', label: 'Department' },
61
- { key: 'netSalary', label: 'Net Salary', render: (v) => `${Number(v).toLocaleString()} RWF` },
62
- { key: 'month', label: 'Month' },
63
- ];
22
+ const departments = report?.departments ?? {};
23
+ const total = report?.total ?? 0;
24
+ const deptNames = Object.keys(departments);
64
25
 
65
26
  return (
66
27
  <div className="space-y-5">
67
28
  <div className="flex items-start justify-between">
68
29
  <div>
69
- <h1 className="text-[18px] font-bold text-gray-800">Payroll Reports</h1>
70
- <p className="text-[13px] text-gray-400 mt-0.5">Monthly employee payroll</p>
30
+ <h1 className="text-[18px] font-bold text-gray-800">Employee Status Report</h1>
31
+ <p className="text-[13px] text-gray-400 mt-0.5">
32
+ Employees currently on leave — organised by department
33
+ </p>
71
34
  </div>
72
- <Button variant="outline" className='bg-[#008A75]' size="sm" icon={<Download className="w-3.5 h-3.5" />} loading={exporting} onClick={handleExport}>Export Excel</Button>
73
- </div>
74
- <div className="bg-white border border-gray-200 p-4 flex items-end gap-3">
75
- <div className="w-64">
76
- <FormField label="Filter by Month">
77
- <FormField.Input type="month" value={month} onChange={setMonth} />
78
- </FormField>
35
+ <div className="flex items-center gap-2 px-4 py-2 bg-yellow-50 border border-yellow-200 rounded-md">
36
+ <Users className="w-4 h-4 text-yellow-600" />
37
+ <span className="text-[13px] font-semibold text-yellow-800">
38
+ Total on leave: {total}
39
+ </span>
79
40
  </div>
80
- <Button size="sm" icon={<FileText className="w-3.5 h-3.5" />} onClick={handleFilter}>Filter</Button>
81
- {month && <Button size="sm" variant="ghost" onClick={handleClear}>Clear</Button>}
82
- </div>
83
- <div className="bg-white border border-gray-200 p-5">
84
- <h2 className="text-[13px] font-semibold text-gray-700 mb-4">
85
- {month ? `Payroll for ${month}` : 'All Payroll Records'} ({payroll.length} entries)
86
- </h2>
87
- <Table columns={columns} data={payroll} rowKey="_id" loading={loading} emptyMessage="No payroll data found." maxHeight="480px" />
88
41
  </div>
42
+
43
+ {loading ? (
44
+ <div className="space-y-4">
45
+ {[1, 2, 3].map((i) => (
46
+ <div key={i} className="bg-white border border-gray-200 p-5">
47
+ <div className="h-5 w-48 animate-pulse bg-gray-200 rounded mb-4" />
48
+ <div className="space-y-2">
49
+ {[1, 2].map((j) => (
50
+ <div key={j} className="h-10 animate-pulse bg-gray-100 rounded" />
51
+ ))}
52
+ </div>
53
+ </div>
54
+ ))}
55
+ </div>
56
+ ) : deptNames.length === 0 ? (
57
+ <div className="bg-white border border-gray-200 p-10 text-center">
58
+ <FileText className="w-10 h-10 text-gray-300 mx-auto mb-3" />
59
+ <p className="text-[14px] text-gray-500">No employees are currently on leave.</p>
60
+ </div>
61
+ ) : (
62
+ deptNames.map((deptName) => {
63
+ const emps = departments[deptName];
64
+ return (
65
+ <div key={deptName} className="bg-white border border-gray-200 overflow-hidden">
66
+ <div className="px-5 py-3 border-b border-gray-100" style={{ backgroundColor: 'var(--color-surface)' }}>
67
+ <h2 className="text-[14px] font-bold text-gray-700">{deptName}</h2>
68
+ <p className="text-[11px] text-gray-400">{emps.length} employee{emps.length > 1 ? 's' : ''} on leave</p>
69
+ </div>
70
+ <div className="overflow-x-auto">
71
+ <table className="w-full">
72
+ <thead>
73
+ <tr className="border-b border-gray-100">
74
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">#</th>
75
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">First Name</th>
76
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">Last Name</th>
77
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">Gender</th>
78
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">Position</th>
79
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">Email</th>
80
+ <th className="px-5 py-3 text-[11px] font-semibold uppercase tracking-wider text-left text-gray-500">Phone</th>
81
+ </tr>
82
+ </thead>
83
+ <tbody className="divide-y divide-gray-50">
84
+ {emps.map((emp, idx) => (
85
+ <tr key={emp._id} className="hover:bg-gray-50 transition-colors">
86
+ <td className="px-5 py-3 text-[12px] text-gray-400 font-mono">{idx + 1}</td>
87
+ <td className="px-5 py-3 text-[13px] font-medium text-gray-800">{emp.empFirstName}</td>
88
+ <td className="px-5 py-3 text-[13px] font-medium text-gray-800">{emp.empLastName}</td>
89
+ <td className="px-5 py-3 text-[12px] text-gray-600 capitalize">{emp.empGender}</td>
90
+ <td className="px-5 py-3 text-[12px] text-gray-600">{emp.position?.posName || '\u2014'}</td>
91
+ <td className="px-5 py-3 text-[12px] text-gray-600">{emp.empEmail || '\u2014'}</td>
92
+ <td className="px-5 py-3 text-[12px] text-gray-600">{emp.empTelephone || '\u2014'}</td>
93
+ </tr>
94
+ ))}
95
+ </tbody>
96
+ </table>
97
+ </div>
98
+ </div>
99
+ );
100
+ })
101
+ )}
89
102
  </div>
90
103
  );
91
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alpe-temp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Employee Payroll Management System (EPMS) - Full-stack Express + React app",
5
5
  "main": "bin/epms.js",
6
6
  "bin": {
@@ -15,13 +15,15 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "bcryptjs": "^2.4.3",
18
+ "connect-mongo": "^6.0.0",
18
19
  "cors": "^2.8.5",
19
20
  "dotenv": "^16.4.5",
20
21
  "exceljs": "^4.4.0",
21
22
  "express": "^4.19.2",
22
- "mongoose": "^8.5.0",
23
+ "express-session": "^1.19.0",
23
24
  "helmet": "^7.1.0",
24
25
  "jsonwebtoken": "^9.0.2",
26
+ "mongoose": "^8.5.0",
25
27
  "uuid": "^10.0.0"
26
28
  },
27
29
  "repository": {
File without changes
File without changes
@@ -1,82 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📦 EXAMPLE CONTROLLER — Handles HTTP requests and sends responses
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // - Receives incoming HTTP requests (from routes)
7
- // - Calls the service to do the actual work
8
- // - Sends back a JSON response to the client
9
- //
10
- // HOW IT WORKS:
11
- // 1. The route calls a controller function (e.g. create)
12
- // 2. The controller reads the request data (req.body, req.params)
13
- // 3. It calls the service to handle the database operation
14
- // 4. It sends back a success or error response
15
- //
16
- // RESPONSE HELPERS (from utils/response.js):
17
- // res_.success(res, data, message) → 200 OK
18
- // res_.created(res, data, message) → 201 Created
19
- // res_.error(res, message, statusCode) → any error
20
- // res_.notFound(res, message) → 404
21
- //
22
- // ═══════════════════════════════════════════════════════════════════════════
23
-
24
- const ExampleService = require('./example.service');
25
- const res_ = require('../../utils/response');
26
-
27
- const ExampleController = {
28
- // ─── POST /api/examples (add new) ────────────────────────────────────
29
- async create(req, res) {
30
- try {
31
- const record = await ExampleService.create(req.body);
32
- return res_.created(res, record, 'Record created');
33
- } catch (err) {
34
- return res_.error(res, err.message, err.statusCode || 500);
35
- }
36
- },
37
-
38
- // ─── GET /api/examples (list all) ────────────────────────────────────
39
- async list(req, res) {
40
- try {
41
- const records = await ExampleService.list();
42
- return res_.success(res, { records });
43
- } catch (err) {
44
- return res_.error(res, err.message, 500);
45
- }
46
- },
47
-
48
- // ─── GET /api/examples/:id (get one) ─────────────────────────────────
49
- async getById(req, res) {
50
- try {
51
- const record = await ExampleService.getById(req.params.id);
52
- if (!record) return res_.notFound(res, 'Record not found');
53
- return res_.success(res, record);
54
- } catch (err) {
55
- return res_.error(res, err.message, 500);
56
- }
57
- },
58
-
59
- // ─── PUT /api/examples/:id (update) ──────────────────────────────────
60
- async update(req, res) {
61
- try {
62
- const record = await ExampleService.update(req.params.id, req.body);
63
- if (!record) return res_.notFound(res, 'Record not found');
64
- return res_.success(res, record, 'Record updated');
65
- } catch (err) {
66
- return res_.error(res, err.message, 500);
67
- }
68
- },
69
-
70
- // ─── DELETE /api/examples/:id (delete) ───────────────────────────────
71
- async remove(req, res) {
72
- try {
73
- const record = await ExampleService.remove(req.params.id);
74
- if (!record) return res_.notFound(res, 'Record not found');
75
- return res_.success(res, null, 'Record deleted');
76
- } catch (err) {
77
- return res_.error(res, err.message, 500);
78
- }
79
- },
80
- };
81
-
82
- module.exports = ExampleController;
@@ -1,47 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📦 EXAMPLE MODEL — Mongoose schema (database structure)
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // Defines the shape of your data in MongoDB.
7
- // Each field = a column in the database.
8
- //
9
- // HOW TO USE:
10
- // 1. Rename "Example" to your feature name (e.g. "Product")
11
- // 2. Change the fields below to match your data
12
- // 3. That's it! The model is ready to use in your service.
13
- //
14
- // EXAMPLE FIELDS (replace these):
15
- // name: { type: String, required: true } ← text field
16
- // price: { type: Number, default: 0 } ← number field
17
- // category: { type: String, enum: ['a','b'] } ← dropdown/choice
18
- // isActive: { type: Boolean, default: true } ← yes/no
19
- // createdAt: added automatically by timestamps ← date
20
- //
21
- // ═══════════════════════════════════════════════════════════════════════════
22
-
23
- const mongoose = require('mongoose');
24
-
25
- const exampleSchema = new mongoose.Schema(
26
- {
27
- // ── YOUR FIELDS HERE ────────────────────────────────────────────────
28
- name: {
29
- type: String,
30
- required: true,
31
- trim: true,
32
- },
33
- description: {
34
- type: String,
35
- trim: true,
36
- },
37
- status: {
38
- type: String,
39
- enum: ['active', 'inactive'],
40
- default: 'active',
41
- },
42
- // Add more fields as needed
43
- },
44
- { timestamps: true } // adds createdAt and updatedAt automatically
45
- );
46
-
47
- module.exports = mongoose.model('Example', exampleSchema);
@@ -1,43 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📦 EXAMPLE ROUTES — Maps URLs to controller functions
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // - Connects a URL path to a controller function
7
- // - Example: GET /api/examples → ExampleController.list
8
- //
9
- // HOW TO ADD A NEW ROUTE:
10
- // router.get('/search', ExampleController.search) ← GET request
11
- // router.post('/', ExampleController.create) ← POST request
12
- // router.put('/:id', ExampleController.update) ← PUT request
13
- // router.delete('/:id', ExampleController.remove) ← DELETE request
14
- //
15
- // COMMON HTTP METHODS:
16
- // GET → fetch data
17
- // POST → create new data
18
- // PUT → update existing data
19
- // DELETE → remove data
20
- //
21
- // AUTHENTICATION:
22
- // - Add `authenticate` before any route to require login
23
- // - Example: router.get('/', authenticate, ExampleController.list)
24
- //
25
- // ═══════════════════════════════════════════════════════════════════════════
26
-
27
- const { Router } = require('express');
28
- const ExampleController = require('./example.controller');
29
- const { authenticate } = require('../../middleware/auth.middleware');
30
-
31
- const router = Router();
32
-
33
- // ─── All routes require authentication ─────────────────────────────────────
34
- router.use(authenticate);
35
-
36
- // ─── CRUD ROUTES ────────────────────────────────────────────────────────────
37
- router.post('/', ExampleController.create); // POST /api/examples
38
- router.get('/', ExampleController.list); // GET /api/examples
39
- router.get('/:id', ExampleController.getById); // GET /api/examples/:id
40
- router.put('/:id', ExampleController.update); // PUT /api/examples/:id
41
- router.delete('/:id', ExampleController.remove); // DELETE /api/examples/:id
42
-
43
- module.exports = router;
@@ -1,58 +0,0 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📦 EXAMPLE SERVICE — Business logic (talks to database)
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // - Every function here does one job (create, list, get, update, delete)
7
- // - The controller calls these functions
8
- // - The model is used here to query MongoDB
9
- //
10
- // HOW TO ADD A NEW FUNCTION:
11
- // Just add a new method to the object below.
12
- // Example: async search(query) { ... }
13
- //
14
- // AVAILABLE FUNCTIONS (customize these):
15
- // create(data) → adds a new record
16
- // list() → gets all records
17
- // getById(id) → gets one record by ID
18
- // update(id, data) → updates a record
19
- // remove(id) → deletes a record
20
- //
21
- // ═══════════════════════════════════════════════════════════════════════════
22
-
23
- const Example = require('./example.model');
24
-
25
- const ExampleService = {
26
- // ─── CREATE ───────────────────────────────────────────────────────────
27
- async create(data) {
28
- const record = await Example.create(data);
29
- return record;
30
- },
31
-
32
- // ─── LIST ALL ─────────────────────────────────────────────────────────
33
- async list() {
34
- return Example.find().sort({ createdAt: -1 });
35
- },
36
-
37
- // ─── GET ONE ──────────────────────────────────────────────────────────
38
- async getById(id) {
39
- return Example.findById(id);
40
- },
41
-
42
- // ─── UPDATE ───────────────────────────────────────────────────────────
43
- async update(id, data) {
44
- return Example.findByIdAndUpdate(id, data, { new: true });
45
- },
46
-
47
- // ─── DELETE ───────────────────────────────────────────────────────────
48
- async remove(id) {
49
- return Example.findByIdAndDelete(id);
50
- },
51
-
52
- // ─── COUNT ────────────────────────────────────────────────────────────
53
- async count() {
54
- return Example.countDocuments();
55
- },
56
- };
57
-
58
- module.exports = ExampleService;
@@ -1,61 +0,0 @@
1
- const ExcelService = require('./excel.service');
2
- const res_ = require('../../utils/response');
3
-
4
- const ExcelController = {
5
- /**
6
- * GET /api/excel/export/users
7
- * Demo: exports a users sheet directly to the browser.
8
- * Replace `sampleData` with a real DB query.
9
- */
10
- async exportUsers(req, res) {
11
- try {
12
- const sampleData = [
13
- { id: 'u-001', name: 'Alice Martin', email: 'alice@example.com', role: 'admin', joined: new Date('2024-01-15'), revenue: 4800 },
14
- { id: 'u-002', name: 'Bob Karenzi', email: 'bob@example.com', role: 'user', joined: new Date('2024-03-22'), revenue: 1200 },
15
- { id: 'u-003', name: 'Claire Umutesi', email: 'claire@example.com', role: 'user', joined: new Date('2024-07-09'), revenue: 2500 },
16
- ];
17
-
18
- const sheets = [
19
- {
20
- name: 'Users',
21
- columns: [
22
- { header: 'ID', key: 'id', width: 10 },
23
- { header: 'Name', key: 'name', width: 25 },
24
- { header: 'Email', key: 'email', width: 35 },
25
- { header: 'Role', key: 'role', width: 12 },
26
- { header: 'Joined', key: 'joined', width: 14, type: 'date' },
27
- { header: 'Revenue', key: 'revenue', width: 15, type: 'currency' },
28
- ],
29
- rows: sampleData,
30
- totals: { revenue: `=SUM(F2:F${sampleData.length + 1})` },
31
- },
32
- ];
33
-
34
- await ExcelService.exportToResponse(res, 'users-report', sheets);
35
- } catch (err) {
36
- return res_.error(res, err.message);
37
- }
38
- },
39
-
40
- /**
41
- * GET /api/excel/export/custom
42
- * Generic export: pass `sheetName` + `data` as JSON body or query params.
43
- * Useful for ad-hoc client-driven exports.
44
- */
45
- async exportCustom(req, res) {
46
- try {
47
- const { sheetName = 'Export', data = [] } = req.body;
48
-
49
- if (!Array.isArray(data) || !data.length) {
50
- return res_.badRequest(res, '`data` must be a non-empty array');
51
- }
52
-
53
- const sheet = ExcelService.buildSheetDef(sheetName, data);
54
- await ExcelService.exportToResponse(res, sheetName, [sheet]);
55
- } catch (err) {
56
- return res_.error(res, err.message);
57
- }
58
- },
59
- };
60
-
61
- module.exports = ExcelController;