create-nodemin-app 1.0.16

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 (88) hide show
  1. package/bin/cli.js +82 -0
  2. package/package.json +25 -0
  3. package/templates/HRMS_Mongodb/README.md +331 -0
  4. package/templates/HRMS_Mongodb/backend/.env.example +6 -0
  5. package/templates/HRMS_Mongodb/backend/package-lock.json +1646 -0
  6. package/templates/HRMS_Mongodb/backend/package.json +26 -0
  7. package/templates/HRMS_Mongodb/backend/src/config/db.js +9 -0
  8. package/templates/HRMS_Mongodb/backend/src/controllers/authController.js +187 -0
  9. package/templates/HRMS_Mongodb/backend/src/controllers/departmentController.js +70 -0
  10. package/templates/HRMS_Mongodb/backend/src/controllers/employeeController.js +178 -0
  11. package/templates/HRMS_Mongodb/backend/src/controllers/positionController.js +66 -0
  12. package/templates/HRMS_Mongodb/backend/src/middleware/auth.js +57 -0
  13. package/templates/HRMS_Mongodb/backend/src/middleware/errorHandler.js +32 -0
  14. package/templates/HRMS_Mongodb/backend/src/middleware/restrictToAdmin.js +5 -0
  15. package/templates/HRMS_Mongodb/backend/src/middleware/validate.js +13 -0
  16. package/templates/HRMS_Mongodb/backend/src/models/Department.js +19 -0
  17. package/templates/HRMS_Mongodb/backend/src/models/Employee.js +81 -0
  18. package/templates/HRMS_Mongodb/backend/src/models/Position.js +19 -0
  19. package/templates/HRMS_Mongodb/backend/src/models/User.js +40 -0
  20. package/templates/HRMS_Mongodb/backend/src/routes/authRoutes.js +27 -0
  21. package/templates/HRMS_Mongodb/backend/src/routes/departmentRoutes.js +33 -0
  22. package/templates/HRMS_Mongodb/backend/src/routes/employeeRoutes.js +39 -0
  23. package/templates/HRMS_Mongodb/backend/src/routes/positionRoutes.js +32 -0
  24. package/templates/HRMS_Mongodb/backend/src/server.js +74 -0
  25. package/templates/HRMS_Mongodb/backend/src/utils/roles.js +5 -0
  26. package/templates/HRMS_Mongodb/backend/src/utils/seed.js +78 -0
  27. package/templates/HRMS_Mongodb/backend/src/validators/authValidator.js +61 -0
  28. package/templates/HRMS_Mongodb/backend/src/validators/departmentValidator.js +21 -0
  29. package/templates/HRMS_Mongodb/backend/src/validators/employeeValidator.js +27 -0
  30. package/templates/HRMS_Mongodb/backend/src/validators/positionValidator.js +26 -0
  31. package/templates/HRMS_Mongodb/frontend/index.html +19 -0
  32. package/templates/HRMS_Mongodb/frontend/package-lock.json +2812 -0
  33. package/templates/HRMS_Mongodb/frontend/package.json +25 -0
  34. package/templates/HRMS_Mongodb/frontend/public/favicon.svg +4 -0
  35. package/templates/HRMS_Mongodb/frontend/src/App.jsx +50 -0
  36. package/templates/HRMS_Mongodb/frontend/src/api/axios.js +54 -0
  37. package/templates/HRMS_Mongodb/frontend/src/components/ProtectedRoute.jsx +26 -0
  38. package/templates/HRMS_Mongodb/frontend/src/components/layout/DashboardLayout.jsx +16 -0
  39. package/templates/HRMS_Mongodb/frontend/src/components/layout/Sidebar.jsx +108 -0
  40. package/templates/HRMS_Mongodb/frontend/src/components/ui/Button.jsx +33 -0
  41. package/templates/HRMS_Mongodb/frontend/src/components/ui/Input.jsx +20 -0
  42. package/templates/HRMS_Mongodb/frontend/src/components/ui/Modal.jsx +48 -0
  43. package/templates/HRMS_Mongodb/frontend/src/components/ui/Select.jsx +27 -0
  44. package/templates/HRMS_Mongodb/frontend/src/context/AuthContext.jsx +97 -0
  45. package/templates/HRMS_Mongodb/frontend/src/index.css +34 -0
  46. package/templates/HRMS_Mongodb/frontend/src/main.jsx +16 -0
  47. package/templates/HRMS_Mongodb/frontend/src/pages/Dashboard.jsx +78 -0
  48. package/templates/HRMS_Mongodb/frontend/src/pages/Departments.jsx +144 -0
  49. package/templates/HRMS_Mongodb/frontend/src/pages/Employees.jsx +297 -0
  50. package/templates/HRMS_Mongodb/frontend/src/pages/LeaveReport.jsx +113 -0
  51. package/templates/HRMS_Mongodb/frontend/src/pages/Login.jsx +92 -0
  52. package/templates/HRMS_Mongodb/frontend/src/pages/Positions.jsx +157 -0
  53. package/templates/HRMS_Mongodb/frontend/src/pages/Register.jsx +93 -0
  54. package/templates/HRMS_Mongodb/frontend/src/pages/ResetPassword.jsx +135 -0
  55. package/templates/HRMS_Mongodb/frontend/src/utils/roles.js +1 -0
  56. package/templates/HRMS_Mongodb/frontend/src/utils/session.js +5 -0
  57. package/templates/HRMS_Mongodb/frontend/src/utils/validation.js +66 -0
  58. package/templates/HRMS_Mongodb/frontend/vite.config.js +16 -0
  59. package/templates/HRMS_Mysql/backend/db.js +13 -0
  60. package/templates/HRMS_Mysql/backend/package-lock.json +1614 -0
  61. package/templates/HRMS_Mysql/backend/package.json +21 -0
  62. package/templates/HRMS_Mysql/backend/server.js +421 -0
  63. package/templates/HRMS_Mysql/frontend/dist/assets/index-CtLtQf3_.js +75 -0
  64. package/templates/HRMS_Mysql/frontend/dist/assets/index-Dq1AXlEY.css +1 -0
  65. package/templates/HRMS_Mysql/frontend/dist/index.html +14 -0
  66. package/templates/HRMS_Mysql/frontend/dist/vite.svg +1 -0
  67. package/templates/HRMS_Mysql/frontend/index.html +13 -0
  68. package/templates/HRMS_Mysql/frontend/package-lock.json +2978 -0
  69. package/templates/HRMS_Mysql/frontend/package.json +25 -0
  70. package/templates/HRMS_Mysql/frontend/postcss.config.js +6 -0
  71. package/templates/HRMS_Mysql/frontend/public/vite.svg +1 -0
  72. package/templates/HRMS_Mysql/frontend/src/App.jsx +55 -0
  73. package/templates/HRMS_Mysql/frontend/src/api.js +11 -0
  74. package/templates/HRMS_Mysql/frontend/src/components/Layout.jsx +59 -0
  75. package/templates/HRMS_Mysql/frontend/src/index.css +7 -0
  76. package/templates/HRMS_Mysql/frontend/src/main.jsx +13 -0
  77. package/templates/HRMS_Mysql/frontend/src/pages/Dashboard.jsx +45 -0
  78. package/templates/HRMS_Mysql/frontend/src/pages/Departments.jsx +108 -0
  79. package/templates/HRMS_Mysql/frontend/src/pages/EmployeeStatusReport.jsx +72 -0
  80. package/templates/HRMS_Mysql/frontend/src/pages/Employees.jsx +252 -0
  81. package/templates/HRMS_Mysql/frontend/src/pages/ForgotPassword.jsx +66 -0
  82. package/templates/HRMS_Mysql/frontend/src/pages/Login.jsx +79 -0
  83. package/templates/HRMS_Mysql/frontend/src/pages/Positions.jsx +109 -0
  84. package/templates/HRMS_Mysql/frontend/src/pages/Register.jsx +95 -0
  85. package/templates/HRMS_Mysql/frontend/src/pages/Users.jsx +133 -0
  86. package/templates/HRMS_Mysql/frontend/tailwind.config.js +26 -0
  87. package/templates/HRMS_Mysql/frontend/vite.config.js +15 -0
  88. package/templates/HRMS_Mysql/hrms_schema.sql +57 -0
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "hrms-frontend",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "axios": "^1.6.0",
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0",
15
+ "react-router-dom": "^6.20.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/react": "^18.2.37",
19
+ "@vitejs/plugin-react": "^4.2.0",
20
+ "autoprefixer": "^10.4.16",
21
+ "postcss": "^8.4.31",
22
+ "tailwindcss": "^3.3.5",
23
+ "vite": "^5.0.0"
24
+ }
25
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#b87322" rx="4"/><text x="16" y="22" text-anchor="middle" fill="white" font-size="18" font-weight="bold" font-family="Arial">H</text></svg>
@@ -0,0 +1,55 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Routes, Route, Navigate } from 'react-router-dom'
3
+ import api from './api'
4
+ import Login from './pages/Login'
5
+ import Register from './pages/Register'
6
+ import ForgotPassword from './pages/ForgotPassword'
7
+ import Layout from './components/Layout'
8
+ import Dashboard from './pages/Dashboard'
9
+ import Employees from './pages/Employees'
10
+ import Departments from './pages/Departments'
11
+ import Positions from './pages/Positions'
12
+ import Users from './pages/Users'
13
+ import EmployeeStatusReport from './pages/EmployeeStatusReport'
14
+
15
+ function ProtectedRoute({ children }) {
16
+ const [auth, setAuth] = useState(null);
17
+ const [loading, setLoading] = useState(true);
18
+
19
+ useEffect(() => {
20
+ api.get('/me')
21
+ .then(res => setAuth(res.data))
22
+ .catch(() => setAuth(null))
23
+ .finally(() => setLoading(false));
24
+ }, []);
25
+
26
+ if (loading) {
27
+ return (
28
+ <div className="flex items-center justify-center min-h-screen bg-primary-50">
29
+ <div className="text-primary-700 text-xl">Loading...</div>
30
+ </div>
31
+ );
32
+ }
33
+
34
+ if (!auth) return <Navigate to="/login" replace />;
35
+ return children;
36
+ }
37
+
38
+ export default function App() {
39
+ return (
40
+ <Routes>
41
+ <Route path="/login" element={<Login />} />
42
+ <Route path="/register" element={<Register />} />
43
+ <Route path="/forgot-password" element={<ForgotPassword />} />
44
+ <Route path="/" element={<ProtectedRoute><Layout /></ProtectedRoute>}>
45
+ <Route index element={<Dashboard />} />
46
+ <Route path="employees" element={<Employees />} />
47
+ <Route path="departments" element={<Departments />} />
48
+ <Route path="positions" element={<Positions />} />
49
+ <Route path="users" element={<Users />} />
50
+ <Route path="reports/employee-status" element={<EmployeeStatusReport />} />
51
+ </Route>
52
+ <Route path="*" element={<Navigate to="/" replace />} />
53
+ </Routes>
54
+ );
55
+ }
@@ -0,0 +1,11 @@
1
+ import axios from 'axios';
2
+
3
+ const api = axios.create({
4
+ baseURL: 'http://localhost:5000/api',
5
+ withCredentials: true,
6
+ headers: {
7
+ 'Content-Type': 'application/json'
8
+ }
9
+ });
10
+
11
+ export default api;
@@ -0,0 +1,59 @@
1
+ import React from 'react'
2
+ import { Outlet, NavLink, useNavigate } from 'react-router-dom'
3
+ import api from '../api'
4
+
5
+ const navItems = [
6
+ { to: '/', label: 'Dashboard', end: true },
7
+ { to: '/employees', label: 'Employees' },
8
+ { to: '/departments', label: 'Departments' },
9
+ { to: '/positions', label: 'Positions' },
10
+ { to: '/users', label: 'Users' },
11
+ { to: '/reports/employee-status', label: 'Leave Report' },
12
+ ];
13
+
14
+ export default function Layout() {
15
+ const navigate = useNavigate();
16
+
17
+ const handleLogout = async () => {
18
+ await api.post('/logout');
19
+ navigate('/login');
20
+ };
21
+
22
+ return (
23
+ <div className="min-h-screen bg-primary-50 flex">
24
+ <aside className="w-64 bg-primary-700 text-white flex flex-col">
25
+ <div className="p-4 border-b border-primary-600">
26
+ <h1 className="text-xl font-bold">HRMS</h1>
27
+ <p className="text-primary-200 text-sm">DAB Enterprise LTD</p>
28
+ </div>
29
+ <nav className="flex-1 p-2 space-y-1">
30
+ {navItems.map(item => (
31
+ <NavLink
32
+ key={item.to}
33
+ to={item.to}
34
+ end={item.end}
35
+ className={({ isActive }) =>
36
+ `block px-3 py-2 rounded text-sm transition-colors ${
37
+ isActive ? 'bg-primary-500 text-white' : 'text-primary-100 hover:bg-primary-600'
38
+ }`
39
+ }
40
+ >
41
+ {item.label}
42
+ </NavLink>
43
+ ))}
44
+ </nav>
45
+ <div className="p-4 border-t border-primary-600">
46
+ <button
47
+ onClick={handleLogout}
48
+ className="w-full px-3 py-2 rounded text-sm bg-primary-500 hover:bg-primary-400 text-white transition-colors"
49
+ >
50
+ Logout
51
+ </button>
52
+ </div>
53
+ </aside>
54
+ <main className="flex-1 p-6 overflow-auto">
55
+ <Outlet />
56
+ </main>
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,7 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ @apply bg-primary-50 text-gray-800;
7
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router-dom'
4
+ import App from './App'
5
+ import './index.css'
6
+
7
+ ReactDOM.createRoot(document.getElementById('root')).render(
8
+ <React.StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </React.StrictMode>
13
+ )
@@ -0,0 +1,45 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import api from '../api'
3
+
4
+ export default function Dashboard() {
5
+ const [stats, setStats] = useState({ employees: 0, departments: 0, positions: 0, users: 0 });
6
+
7
+ useEffect(() => {
8
+ Promise.all([
9
+ api.get('/employees'),
10
+ api.get('/departments'),
11
+ api.get('/positions'),
12
+ api.get('/users'),
13
+ ]).then(([emp, dep, pos, usr]) => {
14
+ setStats({
15
+ employees: emp.data.length,
16
+ departments: dep.data.length,
17
+ positions: pos.data.length,
18
+ users: usr.data.length,
19
+ });
20
+ }).catch(() => {});
21
+ }, []);
22
+
23
+ const cards = [
24
+ { label: 'Employees', value: stats.employees, color: 'bg-primary-500' },
25
+ { label: 'Departments', value: stats.departments, color: 'bg-primary-600' },
26
+ { label: 'Positions', value: stats.positions, color: 'bg-primary-400' },
27
+ { label: 'Users', value: stats.users, color: 'bg-primary-700' },
28
+ ];
29
+
30
+ return (
31
+ <div>
32
+ <h1 className="text-2xl font-bold text-primary-800 mb-6">Dashboard</h1>
33
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
34
+ {cards.map(card => (
35
+ <div key={card.label} className="bg-white rounded-lg shadow p-6">
36
+ <div className={`inline-block px-3 py-1 rounded text-white text-sm mb-2 ${card.color}`}>
37
+ {card.label}
38
+ </div>
39
+ <div className="text-3xl font-bold text-gray-800">{card.value}</div>
40
+ </div>
41
+ ))}
42
+ </div>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,108 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import api from '../api'
3
+
4
+ export default function Departments() {
5
+ const [departments, setDepartments] = useState([]);
6
+ const [form, setForm] = useState({ DepartName: '' });
7
+ const [editing, setEditing] = useState(null);
8
+ const [showForm, setShowForm] = useState(false);
9
+
10
+ useEffect(() => { load(); }, []);
11
+
12
+ const load = async () => {
13
+ const res = await api.get('/departments');
14
+ setDepartments(res.data);
15
+ };
16
+
17
+ const handleSubmit = async (e) => {
18
+ e.preventDefault();
19
+ if (editing) {
20
+ await api.put(`/departments/${editing}`, form);
21
+ } else {
22
+ await api.post('/departments', form);
23
+ }
24
+ setForm({ DepartName: '' });
25
+ setEditing(null);
26
+ setShowForm(false);
27
+ load();
28
+ };
29
+
30
+ const handleEdit = (dep) => {
31
+ setForm({ DepartName: dep.DepartName });
32
+ setEditing(dep.DepartID);
33
+ setShowForm(true);
34
+ };
35
+
36
+ const handleDelete = async (id) => {
37
+ if (confirm('Delete this department?')) {
38
+ await api.delete(`/departments/${id}`);
39
+ load();
40
+ }
41
+ };
42
+
43
+ return (
44
+ <div>
45
+ <div className="flex justify-between items-center mb-6">
46
+ <h1 className="text-2xl font-bold text-primary-800">Departments</h1>
47
+ <button onClick={() => { setShowForm(true); setEditing(null); setForm({ DepartName: '' }); }}
48
+ className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500 transition-colors">
49
+ Add Department
50
+ </button>
51
+ </div>
52
+
53
+ {showForm && (
54
+ <form onSubmit={handleSubmit} className="bg-white rounded-lg shadow p-4 mb-6">
55
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
56
+ <div>
57
+ <label className="block text-sm font-medium text-gray-700 mb-1">Department Name</label>
58
+ <input
59
+ type="text" value={form.DepartName}
60
+ onChange={(e) => setForm({ ...form, DepartName: e.target.value })}
61
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500"
62
+ required
63
+ />
64
+ </div>
65
+ </div>
66
+ <div className="mt-4 space-x-2">
67
+ <button type="submit" className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500">
68
+ {editing ? 'Update' : 'Save'}
69
+ </button>
70
+ <button type="button" onClick={() => setShowForm(false)}
71
+ className="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">
72
+ Cancel
73
+ </button>
74
+ </div>
75
+ </form>
76
+ )}
77
+
78
+ <div className="bg-white rounded-lg shadow overflow-hidden">
79
+ <table className="w-full">
80
+ <thead className="bg-primary-600 text-white">
81
+ <tr>
82
+ <th className="px-4 py-3 text-left">ID</th>
83
+ <th className="px-4 py-3 text-left">Department Name</th>
84
+ <th className="px-4 py-3 text-right">Actions</th>
85
+ </tr>
86
+ </thead>
87
+ <tbody>
88
+ {departments.map(dep => (
89
+ <tr key={dep.DepartID} className="border-t hover:bg-primary-50">
90
+ <td className="px-4 py-3">{dep.DepartID}</td>
91
+ <td className="px-4 py-3">{dep.DepartName}</td>
92
+ <td className="px-4 py-3 text-right space-x-2">
93
+ <button onClick={() => handleEdit(dep)}
94
+ className="text-primary-600 hover:underline text-sm">Edit</button>
95
+ <button onClick={() => handleDelete(dep.DepartID)}
96
+ className="text-red-600 hover:underline text-sm">Delete</button>
97
+ </td>
98
+ </tr>
99
+ ))}
100
+ {departments.length === 0 && (
101
+ <tr><td colSpan="3" className="px-4 py-8 text-center text-gray-500">No departments found</td></tr>
102
+ )}
103
+ </tbody>
104
+ </table>
105
+ </div>
106
+ </div>
107
+ );
108
+ }
@@ -0,0 +1,72 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import api from '../api'
3
+
4
+ export default function EmployeeStatusReport() {
5
+ const [report, setReport] = useState(null);
6
+ const [loading, setLoading] = useState(true);
7
+
8
+ useEffect(() => {
9
+ api.get('/reports/employees-on-leave')
10
+ .then(res => setReport(res.data))
11
+ .catch(() => {})
12
+ .finally(() => setLoading(false));
13
+ }, []);
14
+
15
+ if (loading) {
16
+ return <div className="text-center py-8 text-primary-700">Loading report...</div>;
17
+ }
18
+
19
+ return (
20
+ <div>
21
+ <div className="flex justify-between items-center mb-6">
22
+ <h1 className="text-2xl font-bold text-primary-800">Employee Status Report</h1>
23
+ <span className="bg-primary-600 text-white px-4 py-2 rounded text-sm">
24
+ Total on Leave: {report?.total || 0}
25
+ </span>
26
+ </div>
27
+
28
+ {report && Object.keys(report.departments).length === 0 && (
29
+ <div className="bg-white rounded-lg shadow p-8 text-center text-gray-500">
30
+ No employees currently on leave.
31
+ </div>
32
+ )}
33
+
34
+ {report && Object.entries(report.departments).map(([dept, emps]) => (
35
+ <div key={dept} className="bg-white rounded-lg shadow mb-6 overflow-hidden">
36
+ <div className="bg-primary-600 text-white px-4 py-3 font-semibold flex justify-between">
37
+ <span>{dept}</span>
38
+ <span>{emps.length} employee{emps.length !== 1 ? 's' : ''}</span>
39
+ </div>
40
+ <table className="w-full">
41
+ <thead className="bg-primary-100">
42
+ <tr>
43
+ <th className="px-4 py-2 text-left text-sm">ID</th>
44
+ <th className="px-4 py-2 text-left text-sm">Name</th>
45
+ <th className="px-4 py-2 text-left text-sm">Gender</th>
46
+ <th className="px-4 py-2 text-left text-sm">Email</th>
47
+ <th className="px-4 py-2 text-left text-sm">Phone</th>
48
+ <th className="px-4 py-2 text-left text-sm">Position</th>
49
+ <th className="px-4 py-2 text-left text-sm">Status</th>
50
+ </tr>
51
+ </thead>
52
+ <tbody>
53
+ {emps.map(emp => (
54
+ <tr key={emp.EmpID} className="border-t hover:bg-primary-50">
55
+ <td className="px-4 py-2 text-sm">{emp.EmpID}</td>
56
+ <td className="px-4 py-2 text-sm">{emp.EmpFirstName} {emp.EmpLastName}</td>
57
+ <td className="px-4 py-2 text-sm">{emp.EmpGender || '-'}</td>
58
+ <td className="px-4 py-2 text-sm">{emp.EmpEmail}</td>
59
+ <td className="px-4 py-2 text-sm">{emp.EmpTelephone || '-'}</td>
60
+ <td className="px-4 py-2 text-sm">{emp.PosName || '-'}</td>
61
+ <td className="px-4 py-2 text-sm">
62
+ <span className="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-xs">on leave</span>
63
+ </td>
64
+ </tr>
65
+ ))}
66
+ </tbody>
67
+ </table>
68
+ </div>
69
+ ))}
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,252 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import api from '../api'
3
+
4
+ export default function Employees() {
5
+ const [employees, setEmployees] = useState([]);
6
+ const [departments, setDepartments] = useState([]);
7
+ const [positions, setPositions] = useState([]);
8
+ const [search, setSearch] = useState('');
9
+ const [form, setForm] = useState({
10
+ EmpFirstName: '', EmpLastName: '', EmpGender: '', EmpDateOfBirth: '',
11
+ EmpEmail: '', EmpTelephone: '', EmpAddress: '', EmpHireDate: '',
12
+ EmpStatus: 'on leave', DepartID: '', PosID: ''
13
+ });
14
+ const [editing, setEditing] = useState(null);
15
+ const [showForm, setShowForm] = useState(false);
16
+
17
+ useEffect(() => {
18
+ load();
19
+ api.get('/departments').then(r => setDepartments(r.data));
20
+ api.get('/positions').then(r => setPositions(r.data));
21
+ }, []);
22
+
23
+ const load = async () => {
24
+ const params = search ? { params: { search } } : {};
25
+ const res = await api.get('/employees', params);
26
+ setEmployees(res.data);
27
+ };
28
+
29
+ useEffect(() => {
30
+ const timer = setTimeout(load, 500);
31
+ return () => clearTimeout(timer);
32
+ }, [search]);
33
+
34
+ const handleSubmit = async (e) => {
35
+ e.preventDefault();
36
+ const payload = {
37
+ ...form,
38
+ DepartID: form.DepartID ? parseInt(form.DepartID) : null,
39
+ PosID: form.PosID ? parseInt(form.PosID) : null
40
+ };
41
+ if (editing) {
42
+ await api.put(`/employees/${editing}`, payload);
43
+ } else {
44
+ await api.post('/employees', payload);
45
+ }
46
+ resetForm();
47
+ load();
48
+ };
49
+
50
+ const handleEdit = (emp) => {
51
+ setForm({
52
+ EmpFirstName: emp.EmpFirstName || '',
53
+ EmpLastName: emp.EmpLastName || '',
54
+ EmpGender: emp.EmpGender || '',
55
+ EmpDateOfBirth: emp.EmpDateOfBirth ? emp.EmpDateOfBirth.split('T')[0] : '',
56
+ EmpEmail: emp.EmpEmail || '',
57
+ EmpTelephone: emp.EmpTelephone || '',
58
+ EmpAddress: emp.EmpAddress || '',
59
+ EmpHireDate: emp.EmpHireDate ? emp.EmpHireDate.split('T')[0] : '',
60
+ EmpStatus: emp.EmpStatus || 'on leave',
61
+ DepartID: emp.DepartID ? emp.DepartID.toString() : '',
62
+ PosID: emp.PosID ? emp.PosID.toString() : ''
63
+ });
64
+ setEditing(emp.EmpID);
65
+ setShowForm(true);
66
+ };
67
+
68
+ const handleDelete = async (id) => {
69
+ if (confirm('Delete this employee?')) {
70
+ await api.delete(`/employees/${id}`);
71
+ load();
72
+ }
73
+ };
74
+
75
+ const resetForm = () => {
76
+ setForm({
77
+ EmpFirstName: '', EmpLastName: '', EmpGender: '', EmpDateOfBirth: '',
78
+ EmpEmail: '', EmpTelephone: '', EmpAddress: '', EmpHireDate: '',
79
+ EmpStatus: 'on leave', DepartID: '', PosID: ''
80
+ });
81
+ setEditing(null);
82
+ setShowForm(false);
83
+ };
84
+
85
+ const statusColors = {
86
+ 'on leave': 'bg-yellow-100 text-yellow-800',
87
+ 'left': 'bg-gray-100 text-gray-800',
88
+ 'blacklisted': 'bg-red-100 text-red-800',
89
+ 'deceased': 'bg-gray-200 text-gray-900',
90
+ 'on mission': 'bg-blue-100 text-blue-800',
91
+ };
92
+
93
+ return (
94
+ <div>
95
+ <div className="flex justify-between items-center mb-6">
96
+ <h1 className="text-2xl font-bold text-primary-800">Employees</h1>
97
+ <button onClick={() => { setShowForm(true); setEditing(null); }}
98
+ className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500 transition-colors">
99
+ Add Employee
100
+ </button>
101
+ </div>
102
+
103
+ <div className="mb-4">
104
+ <input
105
+ type="text" placeholder="Search by name, email or phone..."
106
+ value={search}
107
+ onChange={(e) => setSearch(e.target.value)}
108
+ className="w-full md:w-96 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500"
109
+ />
110
+ </div>
111
+
112
+ {showForm && (
113
+ <form onSubmit={handleSubmit} className="bg-white rounded-lg shadow p-4 mb-6">
114
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
115
+ <div>
116
+ <label className="block text-sm font-medium text-gray-700 mb-1">First Name *</label>
117
+ <input type="text" value={form.EmpFirstName}
118
+ onChange={(e) => setForm({ ...form, EmpFirstName: e.target.value })}
119
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required />
120
+ </div>
121
+ <div>
122
+ <label className="block text-sm font-medium text-gray-700 mb-1">Last Name *</label>
123
+ <input type="text" value={form.EmpLastName}
124
+ onChange={(e) => setForm({ ...form, EmpLastName: e.target.value })}
125
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required />
126
+ </div>
127
+ <div>
128
+ <label className="block text-sm font-medium text-gray-700 mb-1">Gender</label>
129
+ <select value={form.EmpGender}
130
+ onChange={(e) => setForm({ ...form, EmpGender: e.target.value })}
131
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500">
132
+ <option value="">Select</option>
133
+ <option value="Male">Male</option>
134
+ <option value="Female">Female</option>
135
+ </select>
136
+ </div>
137
+ <div>
138
+ <label className="block text-sm font-medium text-gray-700 mb-1">Date of Birth</label>
139
+ <input type="date" value={form.EmpDateOfBirth}
140
+ onChange={(e) => setForm({ ...form, EmpDateOfBirth: e.target.value })}
141
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" />
142
+ </div>
143
+ <div>
144
+ <label className="block text-sm font-medium text-gray-700 mb-1">Email *</label>
145
+ <input type="email" value={form.EmpEmail}
146
+ onChange={(e) => setForm({ ...form, EmpEmail: e.target.value })}
147
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required />
148
+ </div>
149
+ <div>
150
+ <label className="block text-sm font-medium text-gray-700 mb-1">Telephone</label>
151
+ <input type="text" value={form.EmpTelephone}
152
+ onChange={(e) => setForm({ ...form, EmpTelephone: e.target.value })}
153
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" />
154
+ </div>
155
+ <div>
156
+ <label className="block text-sm font-medium text-gray-700 mb-1">Address</label>
157
+ <textarea value={form.EmpAddress}
158
+ onChange={(e) => setForm({ ...form, EmpAddress: e.target.value })}
159
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" rows="1" />
160
+ </div>
161
+ <div>
162
+ <label className="block text-sm font-medium text-gray-700 mb-1">Hire Date</label>
163
+ <input type="date" value={form.EmpHireDate}
164
+ onChange={(e) => setForm({ ...form, EmpHireDate: e.target.value })}
165
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" />
166
+ </div>
167
+ <div>
168
+ <label className="block text-sm font-medium text-gray-700 mb-1">Status</label>
169
+ <select value={form.EmpStatus}
170
+ onChange={(e) => setForm({ ...form, EmpStatus: e.target.value })}
171
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500">
172
+ <option value="on leave">On Leave</option>
173
+ <option value="left">Left</option>
174
+ <option value="blacklisted">Blacklisted</option>
175
+ <option value="deceased">Deceased</option>
176
+ <option value="on mission">On Mission</option>
177
+ </select>
178
+ </div>
179
+ <div>
180
+ <label className="block text-sm font-medium text-gray-700 mb-1">Department</label>
181
+ <select value={form.DepartID}
182
+ onChange={(e) => setForm({ ...form, DepartID: e.target.value })}
183
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500">
184
+ <option value="">Select</option>
185
+ {departments.map(d => <option key={d.DepartID} value={d.DepartID}>{d.DepartName}</option>)}
186
+ </select>
187
+ </div>
188
+ <div>
189
+ <label className="block text-sm font-medium text-gray-700 mb-1">Position</label>
190
+ <select value={form.PosID}
191
+ onChange={(e) => setForm({ ...form, PosID: e.target.value })}
192
+ className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500">
193
+ <option value="">Select</option>
194
+ {positions.map(p => <option key={p.PosID} value={p.PosID}>{p.PosName}</option>)}
195
+ </select>
196
+ </div>
197
+ </div>
198
+ <div className="mt-4 space-x-2">
199
+ <button type="submit" className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500">
200
+ {editing ? 'Update' : 'Save'}
201
+ </button>
202
+ <button type="button" onClick={resetForm}
203
+ className="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">Cancel</button>
204
+ </div>
205
+ </form>
206
+ )}
207
+
208
+ <div className="bg-white rounded-lg shadow overflow-x-auto">
209
+ <table className="w-full min-w-[800px]">
210
+ <thead className="bg-primary-600 text-white">
211
+ <tr>
212
+ <th className="px-4 py-3 text-left">ID</th>
213
+ <th className="px-4 py-3 text-left">Name</th>
214
+ <th className="px-4 py-3 text-left">Gender</th>
215
+ <th className="px-4 py-3 text-left">Email</th>
216
+ <th className="px-4 py-3 text-left">Phone</th>
217
+ <th className="px-4 py-3 text-left">Department</th>
218
+ <th className="px-4 py-3 text-left">Position</th>
219
+ <th className="px-4 py-3 text-left">Status</th>
220
+ <th className="px-4 py-3 text-right">Actions</th>
221
+ </tr>
222
+ </thead>
223
+ <tbody>
224
+ {employees.map(emp => (
225
+ <tr key={emp.EmpID} className="border-t hover:bg-primary-50">
226
+ <td className="px-4 py-3">{emp.EmpID}</td>
227
+ <td className="px-4 py-3">{emp.EmpFirstName} {emp.EmpLastName}</td>
228
+ <td className="px-4 py-3">{emp.EmpGender || '-'}</td>
229
+ <td className="px-4 py-3">{emp.EmpEmail}</td>
230
+ <td className="px-4 py-3">{emp.EmpTelephone || '-'}</td>
231
+ <td className="px-4 py-3">{emp.DepartName || '-'}</td>
232
+ <td className="px-4 py-3">{emp.PosName || '-'}</td>
233
+ <td className="px-4 py-3">
234
+ <span className={`px-2 py-1 rounded text-xs ${statusColors[emp.EmpStatus] || 'bg-gray-100 text-gray-800'}`}>
235
+ {emp.EmpStatus}
236
+ </span>
237
+ </td>
238
+ <td className="px-4 py-3 text-right space-x-2">
239
+ <button onClick={() => handleEdit(emp)} className="text-primary-600 hover:underline text-sm">Edit</button>
240
+ <button onClick={() => handleDelete(emp.EmpID)} className="text-red-600 hover:underline text-sm">Delete</button>
241
+ </td>
242
+ </tr>
243
+ ))}
244
+ {employees.length === 0 && (
245
+ <tr><td colSpan="9" className="px-4 py-8 text-center text-gray-500">No employees found</td></tr>
246
+ )}
247
+ </tbody>
248
+ </table>
249
+ </div>
250
+ </div>
251
+ );
252
+ }