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,38 +1,13 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📱 BOTTOM NAV LAYOUT — Navbar at the bottom (mobile-first style)
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // Renders the app shell with a bottom navigation bar.
7
- // Switch to this by setting navigation: 'bottomnav' in config.js
8
- //
9
- // HOW IT LOOKS:
10
- // ┌────────────────────────────────┐
11
- // │ │
12
- // │ PAGE CONTENT │ ← scrollable
13
- // │ │
14
- // ├────────────────────────────────┤
15
- // │ 📊 👥 💰 🏢 📄 │ ← bottom nav (fixed)
16
- // └────────────────────────────────┘
17
- //
18
- // ═══════════════════════════════════════════════════════════════════════════
19
-
20
1
  import React, { useState } from 'react';
21
2
  import { NavLink, Outlet } from 'react-router-dom';
22
- import {
23
- LayoutDashboard, Users, DollarSign, Building2,
24
- FileText, User, LogOut, ChevronDown,
25
- } from 'lucide-react';
3
+ import { LayoutDashboard, Users, Building2, Briefcase, FileText, User, LogOut, ChevronDown } from 'lucide-react';
26
4
  import { useShell } from './useShell';
27
5
 
28
- // ─── NAVIGATION ITEMS ────────────────────────────────────────────────────────
29
- // Add new pages here. The icon + label + path = one nav item.
30
- //
31
6
  const NAV_ITEMS = [
32
7
  { label: 'Home', path: '/dashboard/overview', Icon: LayoutDashboard },
33
8
  { label: 'Employees',path: '/dashboard/employees', Icon: Users },
34
- { label: 'Salary', path: '/dashboard/salary', Icon: DollarSign },
35
9
  { label: 'Depts', path: '/dashboard/departments', Icon: Building2 },
10
+ { label: 'Positions',path: '/dashboard/positions', Icon: Briefcase },
36
11
  { label: 'Reports', path: '/dashboard/reports', Icon: FileText },
37
12
  ];
38
13
 
@@ -42,111 +17,53 @@ export default function BottomNav() {
42
17
 
43
18
  return (
44
19
  <div className="flex flex-col h-screen overflow-hidden">
45
- {/* ─── TOP BAR (user menu, logo) ─────────────────────────────────── */}
46
- <header
47
- className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
48
- style={{
49
- backgroundColor: 'var(--color-nav-bg)',
50
- color: 'var(--color-nav-text)',
51
- borderBottom: '1px solid var(--color-border)',
52
- }}
53
- >
20
+ <header className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
21
+ style={{ backgroundColor: 'var(--color-nav-bg)', color: 'var(--color-nav-text)', borderBottom: '1px solid var(--color-border)' }}>
54
22
  <div className="flex items-center gap-2 mr-4">
55
23
  <img src="/logo.png" alt="logo" className="w-7 h-9" />
56
24
  <div className="flex flex-col mt-4">
57
- <span className="text-[15px] font-bold tracking-tight leading-tight">
58
- EP<span style={{ color: 'var(--color-primary)' }}>MS</span>
59
- </span>
60
- <span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">
61
- Employee Payroll
62
- </span>
25
+ <span className="text-[15px] font-bold tracking-tight leading-tight">HR<span style={{ color: 'var(--color-primary)' }}>MS</span></span>
26
+ <span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">DAB Enterprise</span>
63
27
  </div>
64
28
  </div>
65
-
66
- {/* Spacer */}
67
29
  <div className="flex-1" />
68
-
69
- {/* User dropdown */}
70
30
  <div className="relative">
71
- <button
72
- onClick={() => setMenuOpen((v) => !v)}
73
- className="flex items-center gap-2 px-3 py-1.5 text-[12px] font-medium transition-colors hover:opacity-80"
74
- >
75
- <div
76
- className="w-6 h-6 flex items-center justify-center"
77
- style={{ backgroundColor: 'var(--color-primary)', color: '#fff' }}
78
- >
31
+ <button onClick={() => setMenuOpen((v) => !v)} className="flex items-center gap-2 px-3 py-1.5 text-[12px] font-medium transition-colors hover:opacity-80">
32
+ <div className="w-6 h-6 flex items-center justify-center" style={{ backgroundColor: 'var(--color-primary)', color: '#fff' }}>
79
33
  <User className="w-3 h-3" />
80
34
  </div>
81
- <span className="hidden sm:inline">{user?.name ?? 'Admin'}</span>
35
+ <span className="hidden sm:inline">{user?.userName ?? 'Admin'}</span>
82
36
  <ChevronDown className={`w-3 h-3 transition-transform ${menuOpen ? 'rotate-180' : ''}`} />
83
37
  </button>
84
38
  {menuOpen && (
85
39
  <>
86
40
  <div className="fixed inset-0 z-10" onClick={() => setMenuOpen(false)} />
87
- <div
88
- className="absolute right-0 top-full mt-1 z-20 shadow-lg py-1 min-w-[150px]"
89
- style={{ backgroundColor: 'var(--color-card)', border: '1px solid var(--color-border)' }}
90
- >
41
+ <div className="absolute right-0 top-full mt-1 z-20 shadow-lg py-1 min-w-[150px]"
42
+ style={{ backgroundColor: 'var(--color-card)', border: '1px solid var(--color-border)' }}>
91
43
  <div className="px-3 py-2 border-b" style={{ borderColor: 'var(--color-border)' }}>
92
- <p className="text-[12px] font-semibold" style={{ color: 'var(--color-text)' }}>
93
- {user?.name ?? 'Admin'}
94
- </p>
95
- <p className="text-[11px]" style={{ color: 'var(--color-text-muted)' }}>
96
- {user?.email ?? ''}
97
- </p>
44
+ <p className="text-[12px] font-semibold" style={{ color: 'var(--color-text)' }}>{user?.userName ?? 'Admin'}</p>
98
45
  </div>
99
- <button
100
- onClick={() => { setMenuOpen(false); handleLogout(); }}
101
- className="w-full flex items-center gap-2 px-3 py-2 text-[12px] font-medium transition-colors"
46
+ <button onClick={() => { setMenuOpen(false); handleLogout(); }} className="w-full flex items-center gap-2 px-3 py-2 text-[12px] font-medium transition-colors"
102
47
  style={{ color: 'var(--color-danger)' }}
103
48
  onMouseEnter={(e) => e.target.style.backgroundColor = 'var(--color-danger)'}
104
- onMouseLeave={(e) => e.target.style.backgroundColor = 'transparent'}
105
- >
106
- <LogOut className="w-3.5 h-3.5" />
107
- Logout
49
+ onMouseLeave={(e) => e.target.style.backgroundColor = 'transparent'}>
50
+ <LogOut className="w-3.5 h-3.5" /> Logout
108
51
  </button>
109
52
  </div>
110
53
  </>
111
54
  )}
112
55
  </div>
113
56
  </header>
114
-
115
- {/* ─── MAIN CONTENT ──────────────────────────────────────────────── */}
116
- <main
117
- className="flex-1 overflow-y-auto"
118
- style={{
119
- backgroundColor: 'var(--color-surface)',
120
- padding: 'var(--text-base)',
121
- }}
122
- >
123
- <div className="max-w-6xl mx-auto">
124
- <Outlet />
125
- </div>
57
+ <main className="flex-1 overflow-y-auto" style={{ backgroundColor: 'var(--color-surface)', padding: 'var(--text-base)' }}>
58
+ <div className="max-w-6xl mx-auto"><Outlet /></div>
126
59
  </main>
127
-
128
- {/* ─── BOTTOM NAV ──────────────────────────────────────────────────── */}
129
- <nav
130
- className="flex-shrink-0 flex items-center justify-around h-[64px] px-2 z-20"
131
- style={{
132
- backgroundColor: 'var(--color-nav-bg)',
133
- borderTop: '1px solid var(--color-border)',
134
- }}
135
- >
60
+ <nav className="flex-shrink-0 flex items-center justify-around h-[64px] px-2 z-20"
61
+ style={{ backgroundColor: 'var(--color-nav-bg)', borderTop: '1px solid var(--color-border)' }}>
136
62
  {NAV_ITEMS.map(({ label, path, Icon }) => (
137
- <NavLink
138
- key={path}
139
- to={path}
140
- className="flex flex-col items-center gap-0.5 px-2 py-1 text-[10px] font-medium transition-colors"
141
- style={({ isActive }) => ({
142
- color: isActive ? 'var(--color-nav-active)' : 'var(--color-nav-text)',
143
- })}
144
- >
63
+ <NavLink key={path} to={path} className="flex flex-col items-center gap-0.5 px-2 py-1 text-[10px] font-medium transition-colors"
64
+ style={({ isActive }) => ({ color: isActive ? 'var(--color-nav-active)' : 'var(--color-nav-text)' })}>
145
65
  {({ isActive }) => (
146
- <>
147
- <Icon className="w-5 h-5" />
148
- <span>{label}</span>
149
- </>
66
+ <><Icon className="w-5 h-5" /><span>{label}</span></>
150
67
  )}
151
68
  </NavLink>
152
69
  ))}
@@ -1,43 +1,13 @@
1
- // ═══════════════════════════════════════════════════════════════════════════
2
- // 📐 TOP NAV LAYOUT — Navbar at the top (classic web style)
3
- // ═══════════════════════════════════════════════════════════════════════════
4
- //
5
- // WHAT THIS FILE DOES:
6
- // Renders the app shell with a horizontal nav bar at the top.
7
- // This is the default layout (navigation: 'topnav' in config.js).
8
- //
9
- // HOW IT LOOKS:
10
- // ┌──────────────────────────────────────────────┐
11
- // │ LOGO Home Employees Salary Reports 👤│ ← top nav (fixed)
12
- // ├──────────────────────────────────────────────┤
13
- // │ │
14
- // │ PAGE CONTENT │ ← scrollable
15
- // │ │
16
- // └──────────────────────────────────────────────┘
17
- //
18
- // HOW TO ADD A NEW NAV ITEM:
19
- // Just add an entry to the NAV_ITEMS array below.
20
- // Example: { label: 'Products', path: '/dashboard/products', Icon: Package }
21
- //
22
- // ═══════════════════════════════════════════════════════════════════════════
23
-
24
1
  import React, { useState } from 'react';
25
2
  import { NavLink, Outlet } from 'react-router-dom';
26
- import {
27
- LayoutDashboard, Users, DollarSign, Building2,
28
- FileText, User, LogOut, ChevronDown,
29
- } from 'lucide-react';
3
+ import { LayoutDashboard, Users, Building2, Briefcase, FileText, User, LogOut, ChevronDown } from 'lucide-react';
30
4
  import { useShell } from './useShell';
31
5
 
32
- // ─── NAVIGATION ITEMS ────────────────────────────────────────────────────────
33
- // To add a new page, just add an object to this array.
34
- // Each item needs: label (shown text), path (URL), Icon (from lucide-react)
35
- //
36
6
  const NAV_ITEMS = [
37
7
  { label: 'Overview', path: '/dashboard/overview', Icon: LayoutDashboard },
38
8
  { label: 'Employees', path: '/dashboard/employees', Icon: Users },
39
- { label: 'Salary', path: '/dashboard/salary', Icon: DollarSign },
40
9
  { label: 'Departments', path: '/dashboard/departments', Icon: Building2 },
10
+ { label: 'Positions', path: '/dashboard/positions', Icon: Briefcase },
41
11
  { label: 'Reports', path: '/dashboard/reports', Icon: FileText },
42
12
  ];
43
13
 
@@ -47,103 +17,54 @@ export default function TopNav() {
47
17
 
48
18
  return (
49
19
  <div className="flex flex-col h-screen overflow-hidden">
50
- {/* ─── TOP NAV BAR ───────────────────────────────────────────────── */}
51
- <header
52
- className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
53
- style={{
54
- backgroundColor: 'var(--color-nav-bg)',
55
- color: 'var(--color-nav-text)',
56
- borderBottom: '1px solid var(--color-border)',
57
- }}
58
- >
59
- {/* Logo + Brand */}
20
+ <header className="flex-shrink-0 flex items-center h-[52px] px-4 gap-1 z-20"
21
+ style={{ backgroundColor: 'var(--color-nav-bg)', color: 'var(--color-nav-text)', borderBottom: '1px solid var(--color-border)' }}>
60
22
  <div className="flex items-center gap-2 mr-4 pr-4" style={{ borderRight: '1px solid var(--color-border)' }}>
61
23
  <img src="/logo.png" alt="logo" className="w-7 h-9" />
62
24
  <div className="flex flex-col mt-4">
63
- <span className="text-[15px] font-bold tracking-tight leading-tight">
64
- EP<span style={{ color: 'var(--color-primary)' }}>MS</span>
65
- </span>
66
- <span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">
67
- Employee Payroll
68
- </span>
25
+ <span className="text-[15px] font-bold tracking-tight leading-tight">HR<span style={{ color: 'var(--color-primary)' }}>MS</span></span>
26
+ <span className="text-[7px] font-medium uppercase tracking-wider leading-tight opacity-60">DAB Enterprise</span>
69
27
  </div>
70
28
  </div>
71
-
72
- {/* Navigation links */}
73
29
  <nav className="flex items-center gap-0.5 flex-1">
74
30
  {NAV_ITEMS.map(({ label, path, Icon }) => (
75
- <NavLink
76
- key={path}
77
- to={path}
78
- className="flex items-center gap-1.5 px-3 py-1.5 text-[12px] font-medium transition-colors"
79
- style={({ isActive }) => ({
80
- backgroundColor: isActive ? 'var(--color-primary)' : 'transparent',
81
- color: isActive ? '#ffffff' : 'var(--color-nav-text)',
82
- })}
83
- >
31
+ <NavLink key={path} to={path} className="flex items-center gap-1.5 px-3 py-1.5 text-[12px] font-medium transition-colors"
32
+ style={({ isActive }) => ({ backgroundColor: isActive ? 'var(--color-primary)' : 'transparent', color: isActive ? '#ffffff' : 'var(--color-nav-text)' })}>
84
33
  <Icon className="w-3.5 h-3.5 flex-shrink-0" />
85
34
  <span className="hidden sm:inline">{label}</span>
86
35
  </NavLink>
87
36
  ))}
88
37
  </nav>
89
-
90
- {/* User dropdown */}
91
38
  <div className="relative ml-auto">
92
- <button
93
- onClick={() => setMenuOpen((v) => !v)}
94
- className="flex items-center gap-2 px-3 py-1.5 text-[12px] font-medium transition-colors hover:opacity-80"
95
- >
96
- <div
97
- className="w-6 h-6 flex items-center justify-center"
98
- style={{ backgroundColor: 'var(--color-primary)', color: '#fff' }}
99
- >
39
+ <button onClick={() => setMenuOpen((v) => !v)} className="flex items-center gap-2 px-3 py-1.5 text-[12px] font-medium transition-colors hover:opacity-80">
40
+ <div className="w-6 h-6 flex items-center justify-center" style={{ backgroundColor: 'var(--color-primary)', color: '#fff' }}>
100
41
  <User className="w-3 h-3" />
101
42
  </div>
102
- <span className="hidden sm:inline">{user?.name ?? 'Admin'}</span>
43
+ <span className="hidden sm:inline">{user?.userName ?? 'Admin'}</span>
103
44
  <ChevronDown className={`w-3 h-3 transition-transform ${menuOpen ? 'rotate-180' : ''}`} />
104
45
  </button>
105
46
  {menuOpen && (
106
47
  <>
107
48
  <div className="fixed inset-0 z-10" onClick={() => setMenuOpen(false)} />
108
- <div
109
- className="absolute right-0 top-full mt-1 z-20 shadow-lg py-1 min-w-[150px]"
110
- style={{ backgroundColor: 'var(--color-card)', border: '1px solid var(--color-border)' }}
111
- >
49
+ <div className="absolute right-0 top-full mt-1 z-20 shadow-lg py-1 min-w-[150px]"
50
+ style={{ backgroundColor: 'var(--color-card)', border: '1px solid var(--color-border)' }}>
112
51
  <div className="px-3 py-2 border-b" style={{ borderColor: 'var(--color-border)' }}>
113
- <p className="text-[12px] font-semibold" style={{ color: 'var(--color-text)' }}>
114
- {user?.name ?? 'Admin'}
115
- </p>
116
- <p className="text-[11px]" style={{ color: 'var(--color-text-muted)' }}>
117
- {user?.email ?? ''}
118
- </p>
52
+ <p className="text-[12px] font-semibold" style={{ color: 'var(--color-text)' }}>{user?.userName ?? 'Admin'}</p>
119
53
  </div>
120
- <button
121
- onClick={() => { setMenuOpen(false); handleLogout(); }}
54
+ <button onClick={() => { setMenuOpen(false); handleLogout(); }}
122
55
  className="w-full flex items-center gap-2 px-3 py-2 text-[12px] font-medium transition-colors"
123
56
  style={{ color: 'var(--color-danger)' }}
124
57
  onMouseEnter={(e) => e.target.style.backgroundColor = 'var(--color-danger)'}
125
- onMouseLeave={(e) => e.target.style.backgroundColor = 'transparent'}
126
- >
127
- <LogOut className="w-3.5 h-3.5" />
128
- Logout
58
+ onMouseLeave={(e) => e.target.style.backgroundColor = 'transparent'}>
59
+ <LogOut className="w-3.5 h-3.5" /> Logout
129
60
  </button>
130
61
  </div>
131
62
  </>
132
63
  )}
133
64
  </div>
134
65
  </header>
135
-
136
- {/* ─── MAIN CONTENT ──────────────────────────────────────────────── */}
137
- <main
138
- className="flex-1 overflow-y-auto"
139
- style={{
140
- backgroundColor: 'var(--color-surface)',
141
- padding: '24px',
142
- }}
143
- >
144
- <div className="max-w-7xl mx-auto">
145
- <Outlet />
146
- </div>
66
+ <main className="flex-1 overflow-y-auto" style={{ backgroundColor: 'var(--color-surface)', padding: '24px' }}>
67
+ <div className="max-w-7xl mx-auto"><Outlet /></div>
147
68
  </main>
148
69
  </div>
149
70
  );
@@ -1,44 +1,30 @@
1
- /**
2
- * layouts/useShell.js – Shared auth logic for every layout shell.
3
- *
4
- * Returns { user, handleLogout } — plug into any shell component.
5
- */
6
-
7
- import { useEffect, useState } from 'react';
8
- import { useNavigate } from 'react-router-dom';
9
- import { authApi } from '../api/ApiClient';
10
-
11
- export function useShell() {
12
- const [user, setUser] = useState(null);
13
- const navigate = useNavigate();
14
-
15
- useEffect(() => {
16
- const token = localStorage.getItem('token');
17
- if (!token) { navigate('/'); return; }
18
-
19
- try {
20
- const payload = JSON.parse(atob(token.split('.')[1]));
21
- if (payload.exp < Date.now() / 1000) {
22
- localStorage.removeItem('token');
23
- localStorage.removeItem('user');
24
- navigate('/');
25
- return;
26
- }
27
- } catch {
28
- navigate('/');
29
- return;
30
- }
31
-
32
- const stored = localStorage.getItem('user');
33
- setUser(stored ? JSON.parse(stored) : {});
34
- }, [navigate]);
35
-
36
- const handleLogout = async () => {
37
- try { await authApi.logout(); } catch { /* ignore */ }
38
- localStorage.removeItem('token');
39
- localStorage.removeItem('user');
40
- navigate('/');
41
- };
42
-
43
- return { user, handleLogout };
44
- }
1
+ import { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { authApi } from '../api/ApiClient';
4
+
5
+ export function useShell() {
6
+ const [user, setUser] = useState(null);
7
+ const [loading, setLoading] = useState(true);
8
+ const navigate = useNavigate();
9
+
10
+ useEffect(() => {
11
+ const checkAuth = async () => {
12
+ try {
13
+ const res = await authApi.me();
14
+ setUser(res?.data?.user ?? null);
15
+ } catch {
16
+ navigate('/');
17
+ } finally {
18
+ setLoading(false);
19
+ }
20
+ };
21
+ checkAuth();
22
+ }, [navigate]);
23
+
24
+ const handleLogout = async () => {
25
+ try { await authApi.logout(); } catch { /* ignore */ }
26
+ navigate('/');
27
+ };
28
+
29
+ return { user, loading, handleLogout };
30
+ }
@@ -8,13 +8,12 @@ import Register from './Auth/Register'
8
8
  import LayOut from './LayOut'
9
9
  import Home from './pages/Home'
10
10
  import Employee from './pages/Employee'
11
- import Salary from './pages/Salary'
12
11
  import Department from './pages/Department'
12
+ import Position from './pages/Position'
13
13
  import Reports from './pages/Reports'
14
14
  import Profile from './pages/Profile'
15
15
  import { ToastProvider } from './components'
16
16
 
17
-
18
17
  const router = createBrowserRouter(
19
18
  createRoutesFromElements(
20
19
  <>
@@ -24,8 +23,8 @@ const router = createBrowserRouter(
24
23
  <Route path='/dashboard' element={<LayOut />} >
25
24
  <Route path='overview' element={<Home />} />
26
25
  <Route path='employees' element={<Employee />} />
27
- <Route path='salary' element={<Salary />} />
28
26
  <Route path='departments' element={<Department />} />
27
+ <Route path='positions' element={<Position />} />
29
28
  <Route path='reports' element={<Reports />} />
30
29
  <Route path='profile' element={<Profile />} />
31
30
  </Route>
@@ -20,20 +20,14 @@ export default function Department() {
20
20
  try {
21
21
  const res = await departmentApi.list();
22
22
  setDepartments(res?.data?.departments ?? []);
23
- } catch (err) {
24
- toast.error('Failed to load departments');
25
- } finally {
26
- setLoading(false);
27
- }
23
+ } catch { toast.error('Failed to load departments'); }
24
+ finally { setLoading(false); }
28
25
  }, []);
29
26
 
30
27
  useEffect(() => { loadAll(); }, [loadAll]);
31
28
 
32
29
  const columns = [
33
- { key: 'departmentCode', label: 'Code' },
34
- { key: 'departmentName', label: 'Department Name' },
35
- { key: 'grossSalary', label: 'Gross Salary', render: (v) => `${Number(v).toLocaleString()} RWF` },
36
- { key: 'totalDeduction', label: 'Deductions', render: (v) => `${Number(v).toLocaleString()} RWF` },
30
+ { key: 'departName', label: 'Department Name' },
37
31
  {
38
32
  key: 'actions', label: '', align: 'right',
39
33
  render: (_, row) => (
@@ -52,7 +46,7 @@ export default function Department() {
52
46
  <h1 className="text-[18px] font-bold text-gray-800">Departments</h1>
53
47
  <p className="text-[13px] text-gray-400 mt-0.5">{departments.length} departments</p>
54
48
  </div>
55
- <Button size="sm" icon={<Plus className="w-3.5 h-3.5" />} onClick={() => setCreateOpen(true)}>Add Department</Button>
49
+ <Button size="sm" icon={<Plus className="w-3.5 h-3.5" />} onClick={() => setCreateOpen(true)}>Add Department</Button>
56
50
  </div>
57
51
  <div className="bg-white border border-gray-200 p-5">
58
52
  <Table columns={columns} data={departments} rowKey="_id" loading={loading} emptyMessage="No departments yet." maxHeight="480px" />
@@ -66,17 +60,14 @@ export default function Department() {
66
60
 
67
61
  function CreateModal({ onClose, onCreated }) {
68
62
  const toast = useToast();
69
- const [form, setForm] = useState({ departmentCode: '', departmentName: '', grossSalary: '', totalDeduction: '0' });
63
+ const [form, setForm] = useState({ departName: '' });
70
64
  const [loading, setLoading] = useState(false);
71
65
  const [errors, setErrors] = useState({});
72
66
  const set = (f) => (v) => setForm((p) => ({ ...p, [f]: v }));
73
67
 
74
68
  const validate = () => {
75
69
  const e = {};
76
- if (!form.departmentCode) e.departmentCode = 'Required';
77
- if (!form.departmentName) e.departmentName = 'Required';
78
- if (!form.grossSalary) e.grossSalary = 'Required';
79
- if (form.totalDeduction === '') e.totalDeduction = 'Required';
70
+ if (!form.departName) e.departName = 'Required';
80
71
  setErrors(e);
81
72
  return Object.keys(e).length === 0;
82
73
  };
@@ -85,30 +76,18 @@ function CreateModal({ onClose, onCreated }) {
85
76
  if (!validate()) return;
86
77
  setLoading(true);
87
78
  try {
88
- await departmentApi.create({ ...form, grossSalary: Number(form.grossSalary), totalDeduction: Number(form.totalDeduction) });
79
+ await departmentApi.create(form);
89
80
  toast.success('Department created');
90
81
  onCreated();
91
- } catch (err) {
92
- toast.error(err.message ?? 'Failed to create department');
93
- } finally {
94
- setLoading(false);
95
- }
82
+ } catch (err) { toast.error(err.message ?? 'Failed to create department'); }
83
+ finally { setLoading(false); }
96
84
  };
97
85
 
98
86
  return (
99
87
  <Modal open title="Add Department" size="sm" onClose={onClose}>
100
88
  <div className="space-y-3">
101
- <FormField label="Department Code" required error={errors.departmentCode}>
102
- <FormField.Input value={form.departmentCode} onChange={set('departmentCode')} placeholder="e.g. HR" />
103
- </FormField>
104
- <FormField label="Department Name" required error={errors.departmentName}>
105
- <FormField.Input value={form.departmentName} onChange={set('departmentName')} placeholder="e.g. Human Resources" />
106
- </FormField>
107
- <FormField label="Gross Salary (RWF)" required error={errors.grossSalary}>
108
- <FormField.Input type="number" value={form.grossSalary} onChange={set('grossSalary')} placeholder="300000" />
109
- </FormField>
110
- <FormField label="Total Deduction (RWF)" required error={errors.totalDeduction}>
111
- <FormField.Input type="number" value={form.totalDeduction} onChange={set('totalDeduction')} placeholder="30000" />
89
+ <FormField label="Department Name" required error={errors.departName}>
90
+ <FormField.Input value={form.departName} onChange={set('departName')} placeholder="e.g. Human Resources" />
112
91
  </FormField>
113
92
  </div>
114
93
  <Modal.Footer>
@@ -121,36 +100,23 @@ function CreateModal({ onClose, onCreated }) {
121
100
 
122
101
  function EditModal({ department, onClose, onUpdated }) {
123
102
  const toast = useToast();
124
- const [form, setForm] = useState({
125
- departmentCode: department.departmentCode || '',
126
- departmentName: department.departmentName || '',
127
- grossSalary: department.grossSalary || '',
128
- totalDeduction: department.totalDeduction ?? '0',
129
- });
103
+ const [form, setForm] = useState({ departName: department.departName || '' });
130
104
  const [loading, setLoading] = useState(false);
131
105
  const set = (f) => (v) => setForm((p) => ({ ...p, [f]: v }));
132
106
 
133
107
  const handleSave = async () => {
134
108
  setLoading(true);
135
109
  try {
136
- await departmentApi.update(department._id, { ...form, grossSalary: Number(form.grossSalary), totalDeduction: Number(form.totalDeduction) });
110
+ await departmentApi.update(department._id, form);
137
111
  toast.success('Department updated');
138
112
  onUpdated();
139
- } catch (err) {
140
- toast.error(err.message ?? 'Update failed');
141
- } finally {
142
- setLoading(false);
143
- }
113
+ } catch (err) { toast.error(err.message ?? 'Update failed'); }
114
+ finally { setLoading(false); }
144
115
  };
145
116
 
146
117
  return (
147
- <Modal open title={`Edit — ${department.departmentName}`} size="sm" onClose={onClose}>
148
- <div className="space-y-3">
149
- <FormField label="Department Code"><FormField.Input value={form.departmentCode} onChange={set('departmentCode')} /></FormField>
150
- <FormField label="Department Name"><FormField.Input value={form.departmentName} onChange={set('departmentName')} /></FormField>
151
- <FormField label="Gross Salary (RWF)"><FormField.Input type="number" value={form.grossSalary} onChange={set('grossSalary')} /></FormField>
152
- <FormField label="Total Deduction (RWF)"><FormField.Input type="number" value={form.totalDeduction} onChange={set('totalDeduction')} /></FormField>
153
- </div>
118
+ <Modal open title={`Edit — ${department.departName}`} size="sm" onClose={onClose}>
119
+ <FormField label="Department Name"><FormField.Input value={form.departName} onChange={set('departName')} /></FormField>
154
120
  <Modal.Footer>
155
121
  <Button variant="outline" onClick={onClose}>Cancel</Button>
156
122
  <Button loading={loading} onClick={handleSave}>Save Changes</Button>
@@ -167,18 +133,15 @@ function DeleteModal({ department, onClose, onDeleted }) {
167
133
  setLoading(true);
168
134
  try {
169
135
  await departmentApi.remove(department._id);
170
- toast.success(`${department.departmentName} removed`);
136
+ toast.success(`${department.departName} removed`);
171
137
  onDeleted();
172
- } catch (err) {
173
- toast.error(err.message ?? 'Delete failed');
174
- } finally {
175
- setLoading(false);
176
- }
138
+ } catch (err) { toast.error(err.message ?? 'Delete failed'); }
139
+ finally { setLoading(false); }
177
140
  };
178
141
 
179
142
  return (
180
143
  <Modal open title="Confirm Deletion" size="sm" onClose={onClose}>
181
- <p className="text-[13px] text-gray-600">Delete <strong>{department.departmentName}</strong>? This cannot be undone.</p>
144
+ <p className="text-[13px] text-gray-600">Delete <strong>{department.departName}</strong>? This cannot be undone.</p>
182
145
  <Modal.Footer>
183
146
  <Button variant="outline" onClick={onClose}>Cancel</Button>
184
147
  <Button variant="danger" loading={loading} icon={<Trash2 className="w-3.5 h-3.5" />} onClick={handleDelete}>Delete</Button>