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,13 +0,0 @@
1
- const { Router } = require('express');
2
- const ExcelController = require('./excel.controller');
3
- const { authenticate } = require('../../middleware/auth.middleware');
4
-
5
- const router = Router();
6
-
7
- // All export routes require authentication
8
- router.use(authenticate);
9
-
10
- router.get('/export/users', ExcelController.exportUsers);
11
- router.post('/export/custom', ExcelController.exportCustom);
12
-
13
- module.exports = router;
@@ -1,303 +0,0 @@
1
- /**
2
- * excel.service.js
3
- * ─────────────────────────────────────────────────────────────────────────────
4
- * A fast, reusable Excel export utility built on ExcelJS.
5
- *
6
- * QUICK START
7
- * ───────────
8
- * const ExcelService = require('./excel.service');
9
- *
10
- * // 1. Stream directly to an HTTP response
11
- * await ExcelService.exportToResponse(res, 'users-report', [
12
- * {
13
- * name: 'Users',
14
- * columns: [
15
- * { header: 'ID', key: 'id', width: 36 },
16
- * { header: 'Name', key: 'name', width: 25 },
17
- * { header: 'Email', key: 'email', width: 35 },
18
- * ],
19
- * rows: users, // array of objects
20
- * },
21
- * ]);
22
- *
23
- * // 2. Save to disk and get the file path back
24
- * const filePath = await ExcelService.exportToFile('monthly-report', sheets, {
25
- * outputDir: './exports',
26
- * });
27
- *
28
- * // 3. Get a raw Buffer (e.g. to upload to S3)
29
- * const buffer = await ExcelService.exportToBuffer('report', sheets);
30
- * ─────────────────────────────────────────────────────────────────────────────
31
- */
32
-
33
- const ExcelJS = require('exceljs');
34
- const fs = require('fs');
35
- const path = require('path');
36
- const env = require('../../config/env');
37
-
38
- // ─── Colour palette ──────────────────────────────────────────────────────────
39
- const THEME = {
40
- headerBg: 'FF2563EB', // Blue-600
41
- headerFg: 'FFFFFFFF', // White
42
- altRowBg: 'FFF0F4FF', // Light blue-50
43
- borderColor:'FFB0C4DE', // Muted blue-200
44
- totalBg: 'FFEFF6FF', // Blue-50
45
- totalFg: 'FF1D4ED8', // Blue-700
46
- };
47
-
48
- // ─── Shared styles ────────────────────────────────────────────────────────────
49
- const HEADER_STYLE = {
50
- font: { name: 'Arial', bold: true, size: 11, color: { argb: THEME.headerFg } },
51
- fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.headerBg } },
52
- alignment: { horizontal: 'center', vertical: 'middle', wrapText: false },
53
- border: {
54
- top: { style: 'thin', color: { argb: THEME.borderColor } },
55
- bottom: { style: 'medium', color: { argb: THEME.headerBg } },
56
- },
57
- };
58
-
59
- const CELL_BORDER = {
60
- bottom: { style: 'hair', color: { argb: THEME.borderColor } },
61
- right: { style: 'hair', color: { argb: THEME.borderColor } },
62
- };
63
-
64
- // ─── Helpers ─────────────────────────────────────────────────────────────────
65
-
66
- /** Apply header row styling */
67
- const styleHeaderRow = (row) => {
68
- row.height = 28;
69
- row.eachCell((cell) => {
70
- Object.assign(cell, HEADER_STYLE);
71
- cell.font = { ...HEADER_STYLE.font };
72
- cell.fill = { ...HEADER_STYLE.fill, fgColor: { argb: THEME.headerBg } };
73
- cell.alignment = { ...HEADER_STYLE.alignment };
74
- cell.border = { ...HEADER_STYLE.border };
75
- });
76
- };
77
-
78
- /** Apply alternating row colours + light borders */
79
- const styleDataRow = (row, rowIndex) => {
80
- row.height = 20;
81
- const isAlt = rowIndex % 2 === 0;
82
- row.eachCell({ includeEmpty: true }, (cell) => {
83
- cell.font = { name: 'Arial', size: 10 };
84
- cell.alignment = { vertical: 'middle' };
85
- cell.border = CELL_BORDER;
86
- if (isAlt) {
87
- cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.altRowBg } };
88
- }
89
- });
90
- };
91
-
92
- /** Format a column's cells according to a `type` hint */
93
- const applyColumnFormat = (cell, value, type) => {
94
- switch (type) {
95
- case 'currency':
96
- cell.numFmt = '"$"#,##0.00;[Red]("$"#,##0.00)';
97
- cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
98
- break;
99
- case 'percent':
100
- cell.numFmt = '0.0%';
101
- cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
102
- break;
103
- case 'date':
104
- cell.numFmt = 'yyyy-mm-dd';
105
- cell.value = value instanceof Date ? value : new Date(value);
106
- break;
107
- case 'number':
108
- cell.numFmt = '#,##0.##';
109
- cell.value = typeof value === 'string' ? parseFloat(value) || 0 : value;
110
- break;
111
- default:
112
- cell.value = value ?? '';
113
- }
114
- };
115
-
116
- /**
117
- * buildWorkbook
118
- * Populates a workbook from a `sheets` array definition.
119
- *
120
- * @param {ExcelJS.Workbook} wb
121
- * @param {SheetDef[]} sheets
122
- *
123
- * SheetDef {
124
- * name: string – tab name
125
- * columns: ColumnDef[] – column definitions
126
- * rows: object[] – data rows
127
- * totals?: Record<string, string> – key → Excel formula string, e.g. { amount: '=SUM(C2:C100)' }
128
- * freezeHeader?: boolean – default true
129
- * autoFilter?: boolean – default true
130
- * extraSheets?: SheetDef[] – nested; same API
131
- * }
132
- *
133
- * ColumnDef {
134
- * header: string
135
- * key: string
136
- * width?: number (default 18)
137
- * type?: 'text' | 'number' | 'currency' | 'percent' | 'date'
138
- * hidden?: boolean
139
- * }
140
- */
141
- const buildWorkbook = (wb, sheets) => {
142
- for (const sheetDef of sheets) {
143
- const {
144
- name,
145
- columns,
146
- rows = [],
147
- totals,
148
- freezeHeader = true,
149
- autoFilter = true,
150
- } = sheetDef;
151
-
152
- const ws = wb.addWorksheet(name, {
153
- pageSetup: { fitToPage: true, fitToWidth: 1, orientation: 'landscape' },
154
- });
155
-
156
- // ── Columns ──────────────────────────────────────────────────────────────
157
- ws.columns = columns.map(({ header, key, width = 18, hidden = false }) => ({
158
- header,
159
- key,
160
- width,
161
- hidden,
162
- }));
163
-
164
- // ── Header row ───────────────────────────────────────────────────────────
165
- styleHeaderRow(ws.getRow(1));
166
-
167
- // ── Data rows ────────────────────────────────────────────────────────────
168
- rows.forEach((rowData, idx) => {
169
- const row = ws.addRow({});
170
- columns.forEach(({ key, type = 'text' }, colIdx) => {
171
- const cell = row.getCell(colIdx + 1);
172
- applyColumnFormat(cell, rowData[key], type);
173
- });
174
- styleDataRow(row, idx + 1);
175
- });
176
-
177
- // ── Totals row ───────────────────────────────────────────────────────────
178
- if (totals && Object.keys(totals).length) {
179
- const totalRow = ws.addRow({});
180
- totalRow.height = 22;
181
- columns.forEach(({ key }, colIdx) => {
182
- const cell = totalRow.getCell(colIdx + 1);
183
- if (totals[key]) {
184
- cell.value = { formula: totals[key] };
185
- cell.font = { name: 'Arial', bold: true, size: 10, color: { argb: THEME.totalFg } };
186
- cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.totalBg } };
187
- } else if (colIdx === 0) {
188
- cell.value = 'TOTAL';
189
- cell.font = { name: 'Arial', bold: true, size: 10 };
190
- cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: THEME.totalBg } };
191
- }
192
- cell.border = CELL_BORDER;
193
- cell.alignment = { vertical: 'middle' };
194
- });
195
- }
196
-
197
- // ── Auto-filter on header ─────────────────────────────────────────────────
198
- if (autoFilter && columns.length) {
199
- ws.autoFilter = {
200
- from: { row: 1, column: 1 },
201
- to: { row: 1, column: columns.length },
202
- };
203
- }
204
-
205
- // ── Freeze header row ────────────────────────────────────────────────────
206
- if (freezeHeader) {
207
- ws.views = [{ state: 'frozen', ySplit: 1 }];
208
- }
209
- }
210
- };
211
-
212
- // ─── Public API ──────────────────────────────────────────────────────────────
213
-
214
- const ExcelService = {
215
- /**
216
- * exportToResponse
217
- * Stream the workbook directly to an Express `res` object.
218
- *
219
- * @param {import('express').Response} res
220
- * @param {string} filename – without extension
221
- * @param {SheetDef[]} sheets
222
- */
223
- async exportToResponse(res, filename, sheets) {
224
- const wb = new ExcelJS.Workbook();
225
- wb.creator = 'App Export';
226
- wb.created = new Date();
227
- buildWorkbook(wb, sheets);
228
-
229
- const safe = filename.replace(/[^a-z0-9_-]/gi, '_');
230
- res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
231
- res.setHeader('Content-Disposition', `attachment; filename="${safe}.xlsx"`);
232
- res.setHeader('Cache-Control', 'no-cache');
233
-
234
- await wb.xlsx.write(res);
235
- res.end();
236
- },
237
-
238
- /**
239
- * exportToBuffer
240
- * Returns the workbook as a Node.js Buffer (for uploads, email attachments, etc.)
241
- *
242
- * @param {string} filename – metadata only
243
- * @param {SheetDef[]} sheets
244
- * @returns {Promise<Buffer>}
245
- */
246
- async exportToBuffer(filename, sheets) {
247
- const wb = new ExcelJS.Workbook();
248
- wb.creator = filename;
249
- wb.created = new Date();
250
- buildWorkbook(wb, sheets);
251
- return wb.xlsx.writeBuffer();
252
- },
253
-
254
- /**
255
- * exportToFile
256
- * Saves the workbook to disk and returns the absolute file path.
257
- *
258
- * @param {string} filename
259
- * @param {SheetDef[]} sheets
260
- * @param {{ outputDir?: string }} options
261
- * @returns {Promise<string>} absolute path to the saved file
262
- */
263
- async exportToFile(filename, sheets, options = {}) {
264
- const outputDir = options.outputDir || env.EXCEL_OUTPUT_DIR;
265
- fs.mkdirSync(outputDir, { recursive: true });
266
-
267
- const safe = filename.replace(/[^a-z0-9_-]/gi, '_');
268
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
269
- const filePath = path.resolve(outputDir, `${safe}_${stamp}.xlsx`);
270
-
271
- const wb = new ExcelJS.Workbook();
272
- wb.creator = filename;
273
- wb.created = new Date();
274
- buildWorkbook(wb, sheets);
275
- await wb.xlsx.writeFile(filePath);
276
-
277
- return filePath;
278
- },
279
-
280
- /**
281
- * buildSheetDef
282
- * Convenience factory — infers column keys from the first row of data.
283
- *
284
- * @param {string} name – sheet tab name
285
- * @param {object[]} rows – data rows
286
- * @param {object} opts – { columnWidths, columnTypes, totals, ... }
287
- */
288
- buildSheetDef(name, rows, opts = {}) {
289
- const { columnWidths = {}, columnTypes = {}, totals, freezeHeader, autoFilter } = opts;
290
- const keys = rows.length ? Object.keys(rows[0]) : [];
291
-
292
- const columns = keys.map((key) => ({
293
- header: key.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
294
- key,
295
- width: columnWidths[key] || 18,
296
- type: columnTypes[key] || 'text',
297
- }));
298
-
299
- return { name, columns, rows, totals, freezeHeader, autoFilter };
300
- },
301
- };
302
-
303
- module.exports = ExcelService;
@@ -1,70 +0,0 @@
1
- const SalaryService = require('./salary.service');
2
- const res_ = require('../../utils/response');
3
-
4
- const SalaryController = {
5
- async create(req, res) {
6
- try {
7
- const data = req.body;
8
- data.netSalary = data.grossSalary - data.totalDeduction;
9
- const sal = await SalaryService.create(data);
10
- return res_.created(res, sal, 'Salary record created');
11
- } catch (err) {
12
- console.error(`[SalaryController.create]`, err);
13
- return res_.error(res, err.message, err.statusCode || 500);
14
- }
15
- },
16
-
17
- async list(req, res) {
18
- try {
19
- const salaries = await SalaryService.list();
20
- return res_.success(res, { salaries });
21
- } catch (err) {
22
- return res_.error(res, err.message, 500);
23
- }
24
- },
25
-
26
- async getById(req, res) {
27
- try {
28
- const sal = await SalaryService.getById(req.params.id);
29
- if (!sal) return res_.notFound(res, 'Salary record not found');
30
- return res_.success(res, sal);
31
- } catch (err) {
32
- return res_.error(res, err.message, 500);
33
- }
34
- },
35
-
36
- async update(req, res) {
37
- try {
38
- const data = req.body;
39
- if (data.grossSalary !== undefined && data.totalDeduction !== undefined) {
40
- data.netSalary = data.grossSalary - data.totalDeduction;
41
- }
42
- const sal = await SalaryService.update(req.params.id, data);
43
- if (!sal) return res_.notFound(res, 'Salary record not found');
44
- return res_.success(res, sal, 'Salary record updated');
45
- } catch (err) {
46
- return res_.error(res, err.message, 500);
47
- }
48
- },
49
-
50
- async remove(req, res) {
51
- try {
52
- const sal = await SalaryService.remove(req.params.id);
53
- if (!sal) return res_.notFound(res, 'Salary record not found');
54
- return res_.success(res, null, 'Salary record deleted');
55
- } catch (err) {
56
- return res_.error(res, err.message, 500);
57
- }
58
- },
59
-
60
- async average(req, res) {
61
- try {
62
- const avg = await SalaryService.average();
63
- return res_.success(res, { averageSalary: avg });
64
- } catch (err) {
65
- return res_.error(res, err.message, 500);
66
- }
67
- },
68
- };
69
-
70
- module.exports = SalaryController;
@@ -1,23 +0,0 @@
1
- const mongoose = require('mongoose');
2
-
3
- const salarySchema = new mongoose.Schema({
4
- employee: { type: mongoose.Schema.Types.ObjectId, ref: 'Employee', required: true },
5
- grossSalary: { type: Number, required: true },
6
- totalDeduction: { type: Number, required: true, default: 0 },
7
- netSalary: { type: Number, required: true },
8
- month: { type: String, required: true, trim: true },
9
- }, { timestamps: true });
10
-
11
- const Salary = mongoose.model('Salary', salarySchema);
12
-
13
- // Drop stale indexes from previous schema versions
14
- mongoose.connection.once('open', async () => {
15
- try {
16
- await Salary.collection.dropIndex('employeeNumber_1');
17
- } catch {}
18
- try {
19
- await Salary.collection.dropIndex('salaryId_1');
20
- } catch {}
21
- });
22
-
23
- module.exports = Salary;
@@ -1,16 +0,0 @@
1
- const { Router } = require('express');
2
- const SalaryController = require('./salary.controller');
3
- const { authenticate } = require('../../middleware/auth.middleware');
4
-
5
- const router = Router();
6
-
7
- router.use(authenticate);
8
-
9
- router.post('/', SalaryController.create);
10
- router.get('/', SalaryController.list);
11
- router.get('/avg', SalaryController.average);
12
- router.get('/:id', SalaryController.getById);
13
- router.put('/:id', SalaryController.update);
14
- router.delete('/:id', SalaryController.remove);
15
-
16
- module.exports = router;
@@ -1,44 +0,0 @@
1
- const Salary = require('./salary.model');
2
-
3
- const SalaryService = {
4
- async create(data) {
5
- const sal = await Salary.create(data);
6
- return Salary.populate(sal, { path: 'employee', populate: { path: 'department' } });
7
- },
8
-
9
- async list() {
10
- return Salary.find()
11
- .populate({ path: 'employee', populate: { path: 'department' } })
12
- .sort({ createdAt: -1 });
13
- },
14
-
15
- async getById(id) {
16
- return Salary.findById(id)
17
- .populate({ path: 'employee', populate: { path: 'department' } });
18
- },
19
-
20
- async update(id, data) {
21
- return Salary.findByIdAndUpdate(id, data, { new: true })
22
- .populate({ path: 'employee', populate: { path: 'department' } });
23
- },
24
-
25
- async remove(id) {
26
- return Salary.findByIdAndDelete(id);
27
- },
28
-
29
- async average() {
30
- const result = await Salary.aggregate([
31
- { $group: { _id: null, avgSalary: { $avg: '$netSalary' } } },
32
- ]);
33
- return result.length > 0 ? result[0].avgSalary : 0;
34
- },
35
-
36
- async getMonthlyReport(month) {
37
- const match = month ? { month } : {};
38
- return Salary.find(match)
39
- .populate({ path: 'employee', populate: { path: 'department' } })
40
- .sort({ month: -1, createdAt: -1 });
41
- },
42
- };
43
-
44
- module.exports = SalaryService;