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.
- package/bin/cli.js +82 -0
- package/package.json +25 -0
- package/templates/HRMS_Mongodb/README.md +331 -0
- package/templates/HRMS_Mongodb/backend/.env.example +6 -0
- package/templates/HRMS_Mongodb/backend/package-lock.json +1646 -0
- package/templates/HRMS_Mongodb/backend/package.json +26 -0
- package/templates/HRMS_Mongodb/backend/src/config/db.js +9 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/authController.js +187 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/departmentController.js +70 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/employeeController.js +178 -0
- package/templates/HRMS_Mongodb/backend/src/controllers/positionController.js +66 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/auth.js +57 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/errorHandler.js +32 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/restrictToAdmin.js +5 -0
- package/templates/HRMS_Mongodb/backend/src/middleware/validate.js +13 -0
- package/templates/HRMS_Mongodb/backend/src/models/Department.js +19 -0
- package/templates/HRMS_Mongodb/backend/src/models/Employee.js +81 -0
- package/templates/HRMS_Mongodb/backend/src/models/Position.js +19 -0
- package/templates/HRMS_Mongodb/backend/src/models/User.js +40 -0
- package/templates/HRMS_Mongodb/backend/src/routes/authRoutes.js +27 -0
- package/templates/HRMS_Mongodb/backend/src/routes/departmentRoutes.js +33 -0
- package/templates/HRMS_Mongodb/backend/src/routes/employeeRoutes.js +39 -0
- package/templates/HRMS_Mongodb/backend/src/routes/positionRoutes.js +32 -0
- package/templates/HRMS_Mongodb/backend/src/server.js +74 -0
- package/templates/HRMS_Mongodb/backend/src/utils/roles.js +5 -0
- package/templates/HRMS_Mongodb/backend/src/utils/seed.js +78 -0
- package/templates/HRMS_Mongodb/backend/src/validators/authValidator.js +61 -0
- package/templates/HRMS_Mongodb/backend/src/validators/departmentValidator.js +21 -0
- package/templates/HRMS_Mongodb/backend/src/validators/employeeValidator.js +27 -0
- package/templates/HRMS_Mongodb/backend/src/validators/positionValidator.js +26 -0
- package/templates/HRMS_Mongodb/frontend/index.html +19 -0
- package/templates/HRMS_Mongodb/frontend/package-lock.json +2812 -0
- package/templates/HRMS_Mongodb/frontend/package.json +25 -0
- package/templates/HRMS_Mongodb/frontend/public/favicon.svg +4 -0
- package/templates/HRMS_Mongodb/frontend/src/App.jsx +50 -0
- package/templates/HRMS_Mongodb/frontend/src/api/axios.js +54 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ProtectedRoute.jsx +26 -0
- package/templates/HRMS_Mongodb/frontend/src/components/layout/DashboardLayout.jsx +16 -0
- package/templates/HRMS_Mongodb/frontend/src/components/layout/Sidebar.jsx +108 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Button.jsx +33 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Input.jsx +20 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Modal.jsx +48 -0
- package/templates/HRMS_Mongodb/frontend/src/components/ui/Select.jsx +27 -0
- package/templates/HRMS_Mongodb/frontend/src/context/AuthContext.jsx +97 -0
- package/templates/HRMS_Mongodb/frontend/src/index.css +34 -0
- package/templates/HRMS_Mongodb/frontend/src/main.jsx +16 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Dashboard.jsx +78 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Departments.jsx +144 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Employees.jsx +297 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/LeaveReport.jsx +113 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Login.jsx +92 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Positions.jsx +157 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/Register.jsx +93 -0
- package/templates/HRMS_Mongodb/frontend/src/pages/ResetPassword.jsx +135 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/roles.js +1 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/session.js +5 -0
- package/templates/HRMS_Mongodb/frontend/src/utils/validation.js +66 -0
- package/templates/HRMS_Mongodb/frontend/vite.config.js +16 -0
- package/templates/HRMS_Mysql/backend/db.js +13 -0
- package/templates/HRMS_Mysql/backend/package-lock.json +1614 -0
- package/templates/HRMS_Mysql/backend/package.json +21 -0
- package/templates/HRMS_Mysql/backend/server.js +421 -0
- package/templates/HRMS_Mysql/frontend/dist/assets/index-CtLtQf3_.js +75 -0
- package/templates/HRMS_Mysql/frontend/dist/assets/index-Dq1AXlEY.css +1 -0
- package/templates/HRMS_Mysql/frontend/dist/index.html +14 -0
- package/templates/HRMS_Mysql/frontend/dist/vite.svg +1 -0
- package/templates/HRMS_Mysql/frontend/index.html +13 -0
- package/templates/HRMS_Mysql/frontend/package-lock.json +2978 -0
- package/templates/HRMS_Mysql/frontend/package.json +25 -0
- package/templates/HRMS_Mysql/frontend/postcss.config.js +6 -0
- package/templates/HRMS_Mysql/frontend/public/vite.svg +1 -0
- package/templates/HRMS_Mysql/frontend/src/App.jsx +55 -0
- package/templates/HRMS_Mysql/frontend/src/api.js +11 -0
- package/templates/HRMS_Mysql/frontend/src/components/Layout.jsx +59 -0
- package/templates/HRMS_Mysql/frontend/src/index.css +7 -0
- package/templates/HRMS_Mysql/frontend/src/main.jsx +13 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Dashboard.jsx +45 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Departments.jsx +108 -0
- package/templates/HRMS_Mysql/frontend/src/pages/EmployeeStatusReport.jsx +72 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Employees.jsx +252 -0
- package/templates/HRMS_Mysql/frontend/src/pages/ForgotPassword.jsx +66 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Login.jsx +79 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Positions.jsx +109 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Register.jsx +95 -0
- package/templates/HRMS_Mysql/frontend/src/pages/Users.jsx +133 -0
- package/templates/HRMS_Mysql/frontend/tailwind.config.js +26 -0
- package/templates/HRMS_Mysql/frontend/vite.config.js +15 -0
- package/templates/HRMS_Mysql/hrms_schema.sql +57 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { useNavigate } from 'react-router-dom'
|
|
3
|
+
import api from '../api'
|
|
4
|
+
|
|
5
|
+
export default function ForgotPassword() {
|
|
6
|
+
const [email, setEmail] = useState('');
|
|
7
|
+
const [message, setMessage] = useState('');
|
|
8
|
+
const [error, setError] = useState('');
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
setError('');
|
|
14
|
+
setMessage('');
|
|
15
|
+
try {
|
|
16
|
+
const res = await api.post('/forgot-password', { email });
|
|
17
|
+
setMessage(`Temporary password sent: ${res.data.tempPassword}`);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
setError(err.response?.data?.error || 'Email not found');
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="min-h-screen bg-primary-50 flex items-center justify-center">
|
|
25
|
+
<div className="bg-white rounded-lg shadow-lg p-8 w-full max-w-md">
|
|
26
|
+
<div className="text-center mb-6">
|
|
27
|
+
<h1 className="text-2xl font-bold text-primary-700">Reset Password</h1>
|
|
28
|
+
<p className="text-primary-500">Enter your registered email</p>
|
|
29
|
+
</div>
|
|
30
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
31
|
+
{error && (
|
|
32
|
+
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded text-sm">
|
|
33
|
+
{error}
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
{message && (
|
|
37
|
+
<div className="bg-green-100 border border-green-400 text-green-700 px-4 py-2 rounded text-sm">
|
|
38
|
+
{message}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
<div>
|
|
42
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
43
|
+
<input
|
|
44
|
+
type="email"
|
|
45
|
+
value={email}
|
|
46
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
47
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
48
|
+
required
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<button
|
|
52
|
+
type="submit"
|
|
53
|
+
className="w-full bg-primary-600 text-white py-2 rounded hover:bg-primary-500 transition-colors"
|
|
54
|
+
>
|
|
55
|
+
Send Temporary Password
|
|
56
|
+
</button>
|
|
57
|
+
</form>
|
|
58
|
+
<div className="mt-4 text-center">
|
|
59
|
+
<a href="/login" className="text-sm text-primary-600 hover:underline">
|
|
60
|
+
Back to Login
|
|
61
|
+
</a>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { useNavigate, Link } from 'react-router-dom'
|
|
3
|
+
import api from '../api'
|
|
4
|
+
|
|
5
|
+
export default function Login() {
|
|
6
|
+
const [username, setUsername] = useState('');
|
|
7
|
+
const [password, setPassword] = useState('');
|
|
8
|
+
const [error, setError] = useState('');
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
|
|
11
|
+
const handleSubmit = async (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
setError('');
|
|
14
|
+
try {
|
|
15
|
+
const res = await api.post('/login', { username, password });
|
|
16
|
+
if (res.data.success) {
|
|
17
|
+
navigate('/');
|
|
18
|
+
}
|
|
19
|
+
} catch (err) {
|
|
20
|
+
setError(err.response?.data?.error || 'Login failed');
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className="min-h-screen bg-primary-50 flex items-center justify-center">
|
|
26
|
+
<div className="bg-white rounded-lg shadow-lg p-8 w-full max-w-md">
|
|
27
|
+
<div className="text-center mb-6">
|
|
28
|
+
<h1 className="text-2xl font-bold text-primary-700">HRMS</h1>
|
|
29
|
+
<p className="text-primary-500">DAB Enterprise LTD</p>
|
|
30
|
+
</div>
|
|
31
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
32
|
+
{error && (
|
|
33
|
+
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded text-sm">
|
|
34
|
+
{error}
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
<div>
|
|
38
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
|
39
|
+
<input
|
|
40
|
+
type="text"
|
|
41
|
+
value={username}
|
|
42
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
43
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
44
|
+
required
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
|
49
|
+
<input
|
|
50
|
+
type="password"
|
|
51
|
+
value={password}
|
|
52
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
53
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
54
|
+
required
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
<button
|
|
58
|
+
type="submit"
|
|
59
|
+
className="w-full bg-primary-600 text-white py-2 rounded hover:bg-primary-500 transition-colors"
|
|
60
|
+
>
|
|
61
|
+
Login
|
|
62
|
+
</button>
|
|
63
|
+
</form>
|
|
64
|
+
<div className="mt-4 text-center space-y-2">
|
|
65
|
+
<div>
|
|
66
|
+
<Link to="/forgot-password" className="text-sm text-primary-600 hover:underline">
|
|
67
|
+
Forgot Password?
|
|
68
|
+
</Link>
|
|
69
|
+
</div>
|
|
70
|
+
<div>
|
|
71
|
+
<Link to="/register" className="text-sm text-primary-600 hover:underline">
|
|
72
|
+
Don't have an account? Register
|
|
73
|
+
</Link>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import api from '../api'
|
|
3
|
+
|
|
4
|
+
export default function Positions() {
|
|
5
|
+
const [positions, setPositions] = useState([]);
|
|
6
|
+
const [form, setForm] = useState({ PosName: '', RequiredQualification: '' });
|
|
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('/positions');
|
|
14
|
+
setPositions(res.data);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (e) => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
if (editing) {
|
|
20
|
+
await api.put(`/positions/${editing}`, form);
|
|
21
|
+
} else {
|
|
22
|
+
await api.post('/positions', form);
|
|
23
|
+
}
|
|
24
|
+
setForm({ PosName: '', RequiredQualification: '' });
|
|
25
|
+
setEditing(null);
|
|
26
|
+
setShowForm(false);
|
|
27
|
+
load();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleEdit = (pos) => {
|
|
31
|
+
setForm({ PosName: pos.PosName, RequiredQualification: pos.RequiredQualification });
|
|
32
|
+
setEditing(pos.PosID);
|
|
33
|
+
setShowForm(true);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleDelete = async (id) => {
|
|
37
|
+
if (confirm('Delete this position?')) {
|
|
38
|
+
await api.delete(`/positions/${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">Positions</h1>
|
|
47
|
+
<button onClick={() => { setShowForm(true); setEditing(null); setForm({ PosName: '', RequiredQualification: '' }); }}
|
|
48
|
+
className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500 transition-colors">
|
|
49
|
+
Add Position
|
|
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">Position Name</label>
|
|
58
|
+
<input type="text" value={form.PosName}
|
|
59
|
+
onChange={(e) => setForm({ ...form, PosName: e.target.value })}
|
|
60
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required />
|
|
61
|
+
</div>
|
|
62
|
+
<div>
|
|
63
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Required Qualification</label>
|
|
64
|
+
<input type="text" value={form.RequiredQualification}
|
|
65
|
+
onChange={(e) => setForm({ ...form, RequiredQualification: e.target.value })}
|
|
66
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" />
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="mt-4 space-x-2">
|
|
70
|
+
<button type="submit" className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500">
|
|
71
|
+
{editing ? 'Update' : 'Save'}
|
|
72
|
+
</button>
|
|
73
|
+
<button type="button" onClick={() => setShowForm(false)}
|
|
74
|
+
className="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">Cancel</button>
|
|
75
|
+
</div>
|
|
76
|
+
</form>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
80
|
+
<table className="w-full">
|
|
81
|
+
<thead className="bg-primary-600 text-white">
|
|
82
|
+
<tr>
|
|
83
|
+
<th className="px-4 py-3 text-left">ID</th>
|
|
84
|
+
<th className="px-4 py-3 text-left">Position Name</th>
|
|
85
|
+
<th className="px-4 py-3 text-left">Required Qualification</th>
|
|
86
|
+
<th className="px-4 py-3 text-right">Actions</th>
|
|
87
|
+
</tr>
|
|
88
|
+
</thead>
|
|
89
|
+
<tbody>
|
|
90
|
+
{positions.map(pos => (
|
|
91
|
+
<tr key={pos.PosID} className="border-t hover:bg-primary-50">
|
|
92
|
+
<td className="px-4 py-3">{pos.PosID}</td>
|
|
93
|
+
<td className="px-4 py-3">{pos.PosName}</td>
|
|
94
|
+
<td className="px-4 py-3">{pos.RequiredQualification || '-'}</td>
|
|
95
|
+
<td className="px-4 py-3 text-right space-x-2">
|
|
96
|
+
<button onClick={() => handleEdit(pos)} className="text-primary-600 hover:underline text-sm">Edit</button>
|
|
97
|
+
<button onClick={() => handleDelete(pos.PosID)} className="text-red-600 hover:underline text-sm">Delete</button>
|
|
98
|
+
</td>
|
|
99
|
+
</tr>
|
|
100
|
+
))}
|
|
101
|
+
{positions.length === 0 && (
|
|
102
|
+
<tr><td colSpan="4" className="px-4 py-8 text-center text-gray-500">No positions found</td></tr>
|
|
103
|
+
)}
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { useNavigate, Link } from 'react-router-dom'
|
|
3
|
+
import api from '../api'
|
|
4
|
+
|
|
5
|
+
export default function Register() {
|
|
6
|
+
const [form, setForm] = useState({ firstName: '', lastName: '', email: '', username: '', password: '' });
|
|
7
|
+
const [error, setError] = useState('');
|
|
8
|
+
const navigate = useNavigate();
|
|
9
|
+
|
|
10
|
+
const handleChange = (e) => setForm({ ...form, [e.target.name]: e.target.value });
|
|
11
|
+
|
|
12
|
+
const handleSubmit = async (e) => {
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
setError('');
|
|
15
|
+
try {
|
|
16
|
+
const res = await api.post('/register', form);
|
|
17
|
+
if (res.data.success) {
|
|
18
|
+
navigate('/');
|
|
19
|
+
}
|
|
20
|
+
} catch (err) {
|
|
21
|
+
setError(err.response?.data?.error || 'Registration failed');
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="min-h-screen bg-primary-50 flex items-center justify-center">
|
|
27
|
+
<div className="bg-white rounded-lg shadow-lg p-8 w-full max-w-md">
|
|
28
|
+
<div className="text-center mb-6">
|
|
29
|
+
<h1 className="text-2xl font-bold text-primary-700">HRMS</h1>
|
|
30
|
+
<p className="text-primary-500">Create Account</p>
|
|
31
|
+
</div>
|
|
32
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
33
|
+
{error && (
|
|
34
|
+
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded text-sm">
|
|
35
|
+
{error}
|
|
36
|
+
</div>
|
|
37
|
+
)}
|
|
38
|
+
<div className="grid grid-cols-2 gap-4">
|
|
39
|
+
<div>
|
|
40
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">First Name</label>
|
|
41
|
+
<input
|
|
42
|
+
type="text" name="firstName" value={form.firstName} onChange={handleChange}
|
|
43
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
44
|
+
required
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Last Name</label>
|
|
49
|
+
<input
|
|
50
|
+
type="text" name="lastName" value={form.lastName} onChange={handleChange}
|
|
51
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
52
|
+
required
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
58
|
+
<input
|
|
59
|
+
type="email" name="email" value={form.email} onChange={handleChange}
|
|
60
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
61
|
+
required
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
|
|
66
|
+
<input
|
|
67
|
+
type="text" name="username" value={form.username} onChange={handleChange}
|
|
68
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
69
|
+
required
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Password</label>
|
|
74
|
+
<input
|
|
75
|
+
type="password" name="password" value={form.password} onChange={handleChange}
|
|
76
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
77
|
+
required
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<button
|
|
81
|
+
type="submit"
|
|
82
|
+
className="w-full bg-primary-600 text-white py-2 rounded hover:bg-primary-500 transition-colors"
|
|
83
|
+
>
|
|
84
|
+
Register
|
|
85
|
+
</button>
|
|
86
|
+
</form>
|
|
87
|
+
<div className="mt-4 text-center">
|
|
88
|
+
<Link to="/login" className="text-sm text-primary-600 hover:underline">
|
|
89
|
+
Already have an account? Login
|
|
90
|
+
</Link>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import api from '../api'
|
|
3
|
+
|
|
4
|
+
export default function Users() {
|
|
5
|
+
const [users, setUsers] = useState([]);
|
|
6
|
+
const [employees, setEmployees] = useState([]);
|
|
7
|
+
const [form, setForm] = useState({ EmpID: '', UserName: '', Password: '' });
|
|
8
|
+
const [editing, setEditing] = useState(null);
|
|
9
|
+
const [showForm, setShowForm] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
load();
|
|
13
|
+
api.get('/employees').then(r => setEmployees(r.data));
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
const load = async () => {
|
|
17
|
+
const res = await api.get('/users');
|
|
18
|
+
setUsers(res.data);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const handleSubmit = async (e) => {
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
if (editing) {
|
|
24
|
+
await api.put(`/users/${editing}`, { UserName: form.UserName, Password: form.Password || undefined });
|
|
25
|
+
} else {
|
|
26
|
+
await api.post('/users', form);
|
|
27
|
+
}
|
|
28
|
+
setForm({ EmpID: '', UserName: '', Password: '' });
|
|
29
|
+
setEditing(null);
|
|
30
|
+
setShowForm(false);
|
|
31
|
+
load();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleEdit = (user) => {
|
|
35
|
+
setForm({ EmpID: user.EmpID, UserName: user.UserName, Password: '' });
|
|
36
|
+
setEditing(user.UserID);
|
|
37
|
+
setShowForm(true);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleDelete = async (id) => {
|
|
41
|
+
if (confirm('Delete this user?')) {
|
|
42
|
+
await api.delete(`/users/${id}`);
|
|
43
|
+
load();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const availableEmployees = employees.filter(emp =>
|
|
48
|
+
!users.some(u => u.EmpID === emp.EmpID && u.UserID !== editing)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<div className="flex justify-between items-center mb-6">
|
|
54
|
+
<h1 className="text-2xl font-bold text-primary-800">Users</h1>
|
|
55
|
+
<button onClick={() => { setShowForm(true); setEditing(null); setForm({ EmpID: '', UserName: '', Password: '' }); }}
|
|
56
|
+
className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500 transition-colors">
|
|
57
|
+
Add User
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
{showForm && (
|
|
62
|
+
<form onSubmit={handleSubmit} className="bg-white rounded-lg shadow p-4 mb-6">
|
|
63
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
64
|
+
<div>
|
|
65
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Employee *</label>
|
|
66
|
+
<select value={form.EmpID}
|
|
67
|
+
onChange={(e) => setForm({ ...form, EmpID: e.target.value })}
|
|
68
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required>
|
|
69
|
+
<option value="">Select Employee</option>
|
|
70
|
+
{(editing ? employees : availableEmployees).map(emp => (
|
|
71
|
+
<option key={emp.EmpID} value={emp.EmpID}>{emp.EmpFirstName} {emp.EmpLastName} - {emp.EmpEmail}</option>
|
|
72
|
+
))}
|
|
73
|
+
</select>
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">Username *</label>
|
|
77
|
+
<input type="text" value={form.UserName}
|
|
78
|
+
onChange={(e) => setForm({ ...form, UserName: e.target.value })}
|
|
79
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500" required />
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
83
|
+
Password {editing ? '(leave blank to keep current)' : '*'}
|
|
84
|
+
</label>
|
|
85
|
+
<input type="password" value={form.Password}
|
|
86
|
+
onChange={(e) => setForm({ ...form, Password: e.target.value })}
|
|
87
|
+
className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
88
|
+
required={!editing} />
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div className="mt-4 space-x-2">
|
|
92
|
+
<button type="submit" className="bg-primary-600 text-white px-4 py-2 rounded hover:bg-primary-500">
|
|
93
|
+
{editing ? 'Update' : 'Save'}
|
|
94
|
+
</button>
|
|
95
|
+
<button type="button" onClick={() => setShowForm(false)}
|
|
96
|
+
className="bg-gray-400 text-white px-4 py-2 rounded hover:bg-gray-500">Cancel</button>
|
|
97
|
+
</div>
|
|
98
|
+
</form>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
102
|
+
<table className="w-full">
|
|
103
|
+
<thead className="bg-primary-600 text-white">
|
|
104
|
+
<tr>
|
|
105
|
+
<th className="px-4 py-3 text-left">ID</th>
|
|
106
|
+
<th className="px-4 py-3 text-left">Username</th>
|
|
107
|
+
<th className="px-4 py-3 text-left">Employee Name</th>
|
|
108
|
+
<th className="px-4 py-3 text-left">Email</th>
|
|
109
|
+
<th className="px-4 py-3 text-right">Actions</th>
|
|
110
|
+
</tr>
|
|
111
|
+
</thead>
|
|
112
|
+
<tbody>
|
|
113
|
+
{users.map(user => (
|
|
114
|
+
<tr key={user.UserID} className="border-t hover:bg-primary-50">
|
|
115
|
+
<td className="px-4 py-3">{user.UserID}</td>
|
|
116
|
+
<td className="px-4 py-3">{user.UserName}</td>
|
|
117
|
+
<td className="px-4 py-3">{user.EmpFirstName} {user.EmpLastName}</td>
|
|
118
|
+
<td className="px-4 py-3">{user.EmpEmail}</td>
|
|
119
|
+
<td className="px-4 py-3 text-right space-x-2">
|
|
120
|
+
<button onClick={() => handleEdit(user)} className="text-primary-600 hover:underline text-sm">Edit</button>
|
|
121
|
+
<button onClick={() => handleDelete(user.UserID)} className="text-red-600 hover:underline text-sm">Delete</button>
|
|
122
|
+
</td>
|
|
123
|
+
</tr>
|
|
124
|
+
))}
|
|
125
|
+
{users.length === 0 && (
|
|
126
|
+
<tr><td colSpan="5" className="px-4 py-8 text-center text-gray-500">No users found</td></tr>
|
|
127
|
+
)}
|
|
128
|
+
</tbody>
|
|
129
|
+
</table>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
content: [
|
|
4
|
+
"./index.html",
|
|
5
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
colors: {
|
|
10
|
+
primary: {
|
|
11
|
+
50: '#fdf8f0',
|
|
12
|
+
100: '#f9edd6',
|
|
13
|
+
200: '#f2d9a8',
|
|
14
|
+
300: '#e9c070',
|
|
15
|
+
400: '#e0a645',
|
|
16
|
+
500: '#d48f2a',
|
|
17
|
+
600: '#b87322',
|
|
18
|
+
700: '#96571d',
|
|
19
|
+
800: '#7a4720',
|
|
20
|
+
900: '#653c1d',
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
plugins: [],
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 5173,
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:5000',
|
|
11
|
+
changeOrigin: true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- HRMS Database Schema
|
|
2
|
+
-- Database: HRMS
|
|
3
|
+
-- Entity Relationship Diagram (ERD):
|
|
4
|
+
--
|
|
5
|
+
-- Department 1---* Employee *---1 Position
|
|
6
|
+
-- Employee 1---1 Users
|
|
7
|
+
--
|
|
8
|
+
-- Tables:
|
|
9
|
+
-- Department (DepartID INT PK, DepartName VARCHAR)
|
|
10
|
+
-- Position (PosID INT PK, PosName VARCHAR, RequiredQualification VARCHAR)
|
|
11
|
+
-- Employee (EmpID INT PK, EmpFirstName, EmpLastName, EmpGender, EmpDateOfBirth,
|
|
12
|
+
-- EmpEmail, EmpTelephone, EmpAddress, EmpHireDate, EmpStatus,
|
|
13
|
+
-- DepartID FK, PosID FK)
|
|
14
|
+
-- Users (UserID INT PK, EmpID FK UNIQUE, UserName, Password)
|
|
15
|
+
|
|
16
|
+
CREATE DATABASE IF NOT EXISTS HRMS;
|
|
17
|
+
USE HRMS;
|
|
18
|
+
|
|
19
|
+
-- Department table
|
|
20
|
+
CREATE TABLE IF NOT EXISTS Department (
|
|
21
|
+
DepartID INT AUTO_INCREMENT PRIMARY KEY,
|
|
22
|
+
DepartName VARCHAR(100) NOT NULL
|
|
23
|
+
) ENGINE=InnoDB;
|
|
24
|
+
|
|
25
|
+
-- Position table
|
|
26
|
+
CREATE TABLE IF NOT EXISTS Positionemp(
|
|
27
|
+
PosID INT AUTO_INCREMENT PRIMARY KEY,
|
|
28
|
+
PosName VARCHAR(100) NOT NULL,
|
|
29
|
+
RequiredQualification VARCHAR(255)
|
|
30
|
+
) ENGINE=InnoDB;
|
|
31
|
+
|
|
32
|
+
-- Employee table
|
|
33
|
+
CREATE TABLE IF NOT EXISTS Employee (
|
|
34
|
+
EmpID INT AUTO_INCREMENT PRIMARY KEY,
|
|
35
|
+
EmpFirstName VARCHAR(100) NOT NULL,
|
|
36
|
+
EmpLastName VARCHAR(100) NOT NULL,
|
|
37
|
+
EmpGender VARCHAR(10),
|
|
38
|
+
EmpDateOfBirth DATE,
|
|
39
|
+
EmpEmail VARCHAR(100) UNIQUE,
|
|
40
|
+
EmpTelephone VARCHAR(20),
|
|
41
|
+
EmpAddress TEXT,
|
|
42
|
+
EmpHireDate DATE,
|
|
43
|
+
EmpStatus ENUM('on leave','left','blacklisted','deceased','on mission') DEFAULT 'on leave',
|
|
44
|
+
DepartID INT,
|
|
45
|
+
PosID INT,
|
|
46
|
+
FOREIGN KEY (DepartID) REFERENCES Department(DepartID) ON DELETE SET NULL,
|
|
47
|
+
FOREIGN KEY (PosID) REFERENCES Positionemp(PosID) ON DELETE SET NULL
|
|
48
|
+
) ENGINE=InnoDB;
|
|
49
|
+
|
|
50
|
+
-- Users table
|
|
51
|
+
CREATE TABLE IF NOT EXISTS Users (
|
|
52
|
+
UserID INT AUTO_INCREMENT PRIMARY KEY,
|
|
53
|
+
EmpID INT UNIQUE,
|
|
54
|
+
UserName VARCHAR(50) UNIQUE NOT NULL,
|
|
55
|
+
Password VARCHAR(255) NOT NULL,
|
|
56
|
+
FOREIGN KEY (EmpID) REFERENCES Employee(EmpID) ON DELETE CASCADE
|
|
57
|
+
) ENGINE=InnoDB;
|