create-jinmankn-app 1.0.12 → 1.0.13
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/package.json +1 -1
- package/templates/EPMS(Employee Payroll Managment System)/package-lock.json +13 -0
- package/templates/SMS(Stock Managment System)/backend-project/server.js +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/components/AppLayout.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Login.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Product.jsx +211 -78
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Profile.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Register.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Reports.jsx +1 -1
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Sales.jsx +6 -6
- package/templates/SMS(Stock Managment System)/frontend-project/src/pages/Warehouse.jsx +150 -44
- package/templates/SMS(Stock Managment System)/package-lock.json +13 -0
- package/templates/index/README.md +34 -0
- package/templates/index/backend/config/db.js +12 -0
- package/templates/index/backend/controllers/authController.js +88 -0
- package/templates/index/backend/controllers/departmentController.js +74 -0
- package/templates/index/backend/controllers/employeeController.js +204 -0
- package/templates/index/backend/controllers/positionController.js +78 -0
- package/templates/index/backend/controllers/reportController.js +45 -0
- package/templates/index/backend/middleware/auth.js +27 -0
- package/templates/index/backend/models/Department.js +15 -0
- package/templates/index/backend/models/Employee.js +73 -0
- package/templates/index/backend/models/Position.js +20 -0
- package/templates/index/backend/models/User.js +32 -0
- package/templates/index/backend/package-lock.json +2190 -0
- package/templates/index/backend/package.json +22 -0
- package/templates/index/backend/routes/authRoutes.js +11 -0
- package/templates/index/backend/routes/departmentRoutes.js +17 -0
- package/templates/index/backend/routes/employeeRoutes.js +19 -0
- package/templates/index/backend/routes/positionRoutes.js +17 -0
- package/templates/index/backend/routes/protectedRoutes.js +10 -0
- package/templates/index/backend/routes/reportRoutes.js +9 -0
- package/templates/index/backend/server.js +30 -0
- package/templates/index/frontend/README.md +16 -0
- package/templates/index/frontend/eslint.config.js +21 -0
- package/templates/index/frontend/index.html +13 -0
- package/templates/index/frontend/package-lock.json +3095 -0
- package/templates/index/frontend/package.json +31 -0
- package/templates/index/frontend/public/favicon.svg +1 -0
- package/templates/index/frontend/src/App.css +0 -0
- package/templates/index/frontend/src/App.jsx +35 -0
- package/templates/index/frontend/src/assets/hero.png +0 -0
- package/templates/index/frontend/src/components/DashboardLayout.jsx +90 -0
- package/templates/index/frontend/src/components/ProtectedRoute.jsx +9 -0
- package/templates/index/frontend/src/index.css +59 -0
- package/templates/index/frontend/src/main.jsx +11 -0
- package/templates/index/frontend/src/pages/DashboardHome.jsx +58 -0
- package/templates/index/frontend/src/pages/Department.jsx +150 -0
- package/templates/index/frontend/src/pages/Employee.jsx +366 -0
- package/templates/index/frontend/src/pages/Login.jsx +81 -0
- package/templates/index/frontend/src/pages/Position.jsx +169 -0
- package/templates/index/frontend/src/pages/Register.jsx +102 -0
- package/templates/index/frontend/src/pages/Reports.jsx +94 -0
- package/templates/index/frontend/vite.config.js +7 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
14
|
+
"axios": "^1.16.1",
|
|
15
|
+
"react": "^19.2.6",
|
|
16
|
+
"react-dom": "^19.2.6",
|
|
17
|
+
"react-router-dom": "^7.15.1",
|
|
18
|
+
"tailwindcss": "^4.3.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@eslint/js": "^10.0.1",
|
|
22
|
+
"@types/react": "^19.2.14",
|
|
23
|
+
"@types/react-dom": "^19.2.3",
|
|
24
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
25
|
+
"eslint": "^10.3.0",
|
|
26
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
27
|
+
"eslint-plugin-react-refresh": "^0.5.2",
|
|
28
|
+
"globals": "^17.6.0",
|
|
29
|
+
"vite": "^8.0.12"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
2
|
+
import DashboardLayout from './components/DashboardLayout';
|
|
3
|
+
import ProtectedRoute from './components/ProtectedRoute';
|
|
4
|
+
import DashboardHome from './pages/DashboardHome';
|
|
5
|
+
import Register from './pages/Register';
|
|
6
|
+
import Login from './pages/Login';
|
|
7
|
+
import Department from './pages/Department';
|
|
8
|
+
import Employee from './pages/Employee';
|
|
9
|
+
import Position from './pages/Position';
|
|
10
|
+
import Reports from './pages/Reports';
|
|
11
|
+
|
|
12
|
+
function App() {
|
|
13
|
+
return (
|
|
14
|
+
<BrowserRouter>
|
|
15
|
+
<Routes>
|
|
16
|
+
<Route path="/login" element={<Login />} />
|
|
17
|
+
<Route path="/register" element={<Register />} />
|
|
18
|
+
|
|
19
|
+
<Route path="/dashboard" element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
|
|
20
|
+
<Route index element={<DashboardHome />} />
|
|
21
|
+
<Route path="department" element={<Department />} />
|
|
22
|
+
<Route path="employee" element={<Employee />} />
|
|
23
|
+
<Route path="position" element={<Position />} />
|
|
24
|
+
<Route path="reports" element={<Reports />} />
|
|
25
|
+
</Route>
|
|
26
|
+
|
|
27
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
28
|
+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
29
|
+
</Routes>
|
|
30
|
+
</BrowserRouter>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default App;
|
|
35
|
+
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { NavLink, Outlet, useNavigate } from 'react-router-dom';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
|
|
5
|
+
const navItems = [
|
|
6
|
+
{ to: '/dashboard', end: true, label: 'Overview' },
|
|
7
|
+
{ to: '/dashboard/employee', label: 'Employees' },
|
|
8
|
+
{ to: '/dashboard/department', label: 'Departments' },
|
|
9
|
+
{ to: '/dashboard/position', label: 'Positions' },
|
|
10
|
+
{ to: '/dashboard/reports', label: 'Reports' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
function DashboardLayout() {
|
|
14
|
+
const [user, setUser] = useState(null);
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
const navigate = useNavigate();
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const storedUser = localStorage.getItem('user');
|
|
20
|
+
if (storedUser) {
|
|
21
|
+
setUser(JSON.parse(storedUser));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
axios.get('http://localhost:5006/api/auth/me', {
|
|
25
|
+
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
|
26
|
+
})
|
|
27
|
+
.then((response) => {
|
|
28
|
+
setUser(response.data.user);
|
|
29
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
30
|
+
})
|
|
31
|
+
.catch((err) => {
|
|
32
|
+
if (!storedUser) {
|
|
33
|
+
setError(err.response?.data?.message || 'Could not load user');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const handleLogout = () => {
|
|
39
|
+
localStorage.removeItem('token');
|
|
40
|
+
localStorage.removeItem('user');
|
|
41
|
+
navigate('/login');
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="min-h-screen bg-gray-100">
|
|
46
|
+
<aside className="fixed inset-y-0 left-0 flex w-56 flex-col bg-teal-900 text-white">
|
|
47
|
+
<div className="border-b border-teal-800 px-4 py-4">
|
|
48
|
+
<h1 className="text-lg font-bold">DAB HRMS</h1>
|
|
49
|
+
<p className="text-sm text-teal-200">Human Resource Management System</p>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<nav className="flex-1 space-y-1 overflow-y-auto px-2 py-4">
|
|
53
|
+
{navItems.map((item) => (
|
|
54
|
+
<NavLink
|
|
55
|
+
key={item.to}
|
|
56
|
+
to={item.to}
|
|
57
|
+
end={item.end}
|
|
58
|
+
className={({ isActive }) => `nav-link ${isActive ? 'nav-link-active' : ''}`}
|
|
59
|
+
>
|
|
60
|
+
{item.label}
|
|
61
|
+
</NavLink>
|
|
62
|
+
))}
|
|
63
|
+
</nav>
|
|
64
|
+
|
|
65
|
+
<div className="border-t border-teal-800 px-4 py-4">
|
|
66
|
+
{error && <p className="mb-2 text-xs text-red-300">{error}</p>}
|
|
67
|
+
{user && <p className="text-sm">{user.name || user.email}</p>}
|
|
68
|
+
<button type="button" onClick={handleLogout} className="mt-3 w-full btn-secondary text-sm">
|
|
69
|
+
Log out
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
</aside>
|
|
73
|
+
|
|
74
|
+
<div className="ml-56 min-h-screen">
|
|
75
|
+
<header className="border-b border-gray-300 bg-white px-6 py-4">
|
|
76
|
+
<h2 className="text-lg font-semibold">
|
|
77
|
+
{user ? `Hello, ${user.name || user.email}` : 'Dashboard'}
|
|
78
|
+
</h2>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<main className="p-6">
|
|
82
|
+
<Outlet />
|
|
83
|
+
</main>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default DashboardLayout;
|
|
90
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
body {
|
|
4
|
+
@apply bg-gray-100 text-gray-900;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@layer components {
|
|
8
|
+
.input-field {
|
|
9
|
+
@apply w-full rounded border border-gray-300 px-3 py-2 focus:border-teal-700 focus:outline-none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.btn-primary {
|
|
13
|
+
@apply rounded bg-teal-700 px-4 py-2 text-white hover:bg-teal-800 disabled:opacity-50;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.btn-secondary {
|
|
17
|
+
@apply rounded border border-gray-300 bg-white px-4 py-2 text-gray-700 hover:bg-gray-50;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.btn-link {
|
|
21
|
+
@apply text-blue-600 hover:underline;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.btn-danger {
|
|
25
|
+
@apply text-red-600 hover:underline;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.box {
|
|
29
|
+
@apply rounded border border-gray-300 bg-white p-4;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.alert-success {
|
|
33
|
+
@apply mb-4 rounded border border-green-300 bg-green-50 px-3 py-2 text-sm text-green-800;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.alert-error {
|
|
37
|
+
@apply mb-4 rounded border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.nav-link {
|
|
41
|
+
@apply block rounded px-3 py-2 text-sm text-gray-200 hover:bg-teal-800;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.nav-link-active {
|
|
45
|
+
@apply bg-blue-700 text-white hover:bg-blue-700;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.data-table {
|
|
49
|
+
@apply w-full border-collapse text-left text-sm;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.data-table th {
|
|
53
|
+
@apply border border-gray-300 bg-gray-50 px-3 py-2 font-medium;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.data-table td {
|
|
57
|
+
@apply border border-gray-300 px-3 py-2;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
|
|
5
|
+
function DashboardHome() {
|
|
6
|
+
const [departments, setDepartments] = useState([]);
|
|
7
|
+
const [employees, setEmployees] = useState([]);
|
|
8
|
+
const [positions, setPositions] = useState([]);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
12
|
+
Promise.all([
|
|
13
|
+
axios.get('http://localhost:5006/api/departments/list', { headers }),
|
|
14
|
+
axios.get('http://localhost:5006/api/employees/list', { headers }),
|
|
15
|
+
axios.get('http://localhost:5006/api/positions/list', { headers }),
|
|
16
|
+
])
|
|
17
|
+
.then(([deptRes, empRes, posRes]) => {
|
|
18
|
+
setDepartments(deptRes.data);
|
|
19
|
+
setEmployees(empRes.data);
|
|
20
|
+
setPositions(posRes.data);
|
|
21
|
+
})
|
|
22
|
+
.catch((err) => console.error('Overview load failed:', err));
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div>
|
|
27
|
+
<h1 className="mb-6 text-2xl font-bold">Overview</h1>
|
|
28
|
+
|
|
29
|
+
<div className="mb-6 grid gap-4 sm:grid-cols-3">
|
|
30
|
+
<div className="box">
|
|
31
|
+
<p className="text-sm text-gray-600">Employees</p>
|
|
32
|
+
<p className="text-2xl font-bold">{employees.length}</p>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="box">
|
|
35
|
+
<p className="text-sm text-gray-600">Departments</p>
|
|
36
|
+
<p className="text-2xl font-bold">{departments.length}</p>
|
|
37
|
+
</div>
|
|
38
|
+
<div className="box">
|
|
39
|
+
<p className="text-sm text-gray-600">Positions</p>
|
|
40
|
+
<p className="text-2xl font-bold">{positions.length}</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="box">
|
|
45
|
+
<h2 className="mb-3 font-semibold">Quick links</h2>
|
|
46
|
+
<ul className="space-y-2 text-blue-600">
|
|
47
|
+
<li><Link to="/dashboard/employee" className="hover:underline">Manage Employees</Link></li>
|
|
48
|
+
<li><Link to="/dashboard/department" className="hover:underline">Manage Departments</Link></li>
|
|
49
|
+
<li><Link to="/dashboard/position" className="hover:underline">Manage Positions</Link></li>
|
|
50
|
+
<li><Link to="/dashboard/reports" className="hover:underline">View Reports</Link></li>
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default DashboardHome;
|
|
58
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Department() {
|
|
5
|
+
const [departments, setDepartments] = useState([]);
|
|
6
|
+
const [departmentName, setDepartmentName] = useState('');
|
|
7
|
+
const [editingId, setEditingId] = useState(null);
|
|
8
|
+
const [message, setMessage] = useState('');
|
|
9
|
+
|
|
10
|
+
const headers = { Authorization: `Bearer ${localStorage.getItem('token')}` };
|
|
11
|
+
|
|
12
|
+
const getDepartments = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const res = await axios.get('http://localhost:5006/api/departments/list', { headers });
|
|
15
|
+
setDepartments(res.data);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error(err);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
getDepartments();
|
|
23
|
+
}, []);
|
|
24
|
+
|
|
25
|
+
const handleSubmit = async (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
try {
|
|
28
|
+
if (editingId) {
|
|
29
|
+
await axios.put(
|
|
30
|
+
`http://localhost:5006/api/departments/${editingId}`,
|
|
31
|
+
{ departmentName },
|
|
32
|
+
{ headers }
|
|
33
|
+
);
|
|
34
|
+
setMessage('Department updated successfully');
|
|
35
|
+
} else {
|
|
36
|
+
await axios.post(
|
|
37
|
+
'http://localhost:5006/api/departments/add',
|
|
38
|
+
{ departmentName },
|
|
39
|
+
{ headers }
|
|
40
|
+
);
|
|
41
|
+
setMessage('Department added successfully');
|
|
42
|
+
}
|
|
43
|
+
setDepartmentName('');
|
|
44
|
+
setEditingId(null);
|
|
45
|
+
getDepartments();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
setMessage(err.response?.data?.message || 'Failed to save department');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleEdit = (department) => {
|
|
52
|
+
setDepartmentName(department.departmentName);
|
|
53
|
+
setEditingId(department._id);
|
|
54
|
+
setMessage('');
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleDelete = async (id) => {
|
|
58
|
+
if (!window.confirm('Delete this department?')) return;
|
|
59
|
+
try {
|
|
60
|
+
await axios.delete(`http://localhost:5006/api/departments/${id}`, { headers });
|
|
61
|
+
setMessage('Department deleted successfully');
|
|
62
|
+
if (editingId === id) {
|
|
63
|
+
setEditingId(null);
|
|
64
|
+
setDepartmentName('');
|
|
65
|
+
}
|
|
66
|
+
getDepartments();
|
|
67
|
+
} catch (err) {
|
|
68
|
+
setMessage(err.response?.data?.message || 'Failed to delete department');
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const handleCancel = () => {
|
|
73
|
+
setEditingId(null);
|
|
74
|
+
setDepartmentName('');
|
|
75
|
+
setMessage('');
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const isSuccess = message.includes('successfully');
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
<h1 className="mb-6 text-2xl font-bold">Department Management</h1>
|
|
83
|
+
|
|
84
|
+
{message && (
|
|
85
|
+
<p className={isSuccess ? 'alert-success' : 'alert-error'}>{message}</p>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
<form onSubmit={handleSubmit} className="mb-6 box space-y-4">
|
|
89
|
+
<h2 className="font-semibold">{editingId ? 'Edit Department' : 'Add Department'}</h2>
|
|
90
|
+
<div>
|
|
91
|
+
<label className="mb-1 block text-sm">Department Name</label>
|
|
92
|
+
<input
|
|
93
|
+
type="text"
|
|
94
|
+
value={departmentName}
|
|
95
|
+
onChange={(e) => setDepartmentName(e.target.value)}
|
|
96
|
+
className="input-field"
|
|
97
|
+
placeholder="e.g. Human Resources"
|
|
98
|
+
required
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="flex items-center gap-3">
|
|
102
|
+
<button type="submit" className="btn-primary">
|
|
103
|
+
{editingId ? 'Update Department' : 'Add Department'}
|
|
104
|
+
</button>
|
|
105
|
+
{editingId && (
|
|
106
|
+
<button type="button" className="btn-secondary" onClick={handleCancel}>
|
|
107
|
+
Cancel
|
|
108
|
+
</button>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
</form>
|
|
112
|
+
|
|
113
|
+
<div className="box">
|
|
114
|
+
<h2 className="mb-3 font-semibold">Department List ({departments.length})</h2>
|
|
115
|
+
{departments.length === 0 ? (
|
|
116
|
+
<p className="text-gray-600">No departments yet.</p>
|
|
117
|
+
) : (
|
|
118
|
+
<div className="overflow-x-auto">
|
|
119
|
+
<table className="data-table">
|
|
120
|
+
<thead>
|
|
121
|
+
<tr>
|
|
122
|
+
<th>Name</th>
|
|
123
|
+
<th>Actions</th>
|
|
124
|
+
</tr>
|
|
125
|
+
</thead>
|
|
126
|
+
<tbody>
|
|
127
|
+
{departments.map((dept) => (
|
|
128
|
+
<tr key={dept._id}>
|
|
129
|
+
<td>{dept.departmentName}</td>
|
|
130
|
+
<td className="space-x-2">
|
|
131
|
+
<button type="button" className="btn-secondary btn-small" onClick={() => handleEdit(dept)}>
|
|
132
|
+
Edit
|
|
133
|
+
</button>
|
|
134
|
+
<button type="button" className="btn-danger btn-small" onClick={() => handleDelete(dept._id)}>
|
|
135
|
+
Delete
|
|
136
|
+
</button>
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
))}
|
|
140
|
+
</tbody>
|
|
141
|
+
</table>
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default Department;
|
|
150
|
+
|