create-myexam-app 1.0.20 → 1.0.21
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/projects/SCMS/backend/.env +2 -2
- package/projects/SCMS/backend/config/db.js +12 -0
- package/projects/SCMS/backend/controllers/authController.js +90 -0
- package/projects/SCMS/backend/controllers/deliveryController.js +79 -0
- package/projects/SCMS/backend/controllers/productController.js +74 -0
- package/projects/SCMS/backend/controllers/reportsController.js +77 -0
- package/projects/SCMS/backend/controllers/shipmentController.js +80 -0
- package/projects/SCMS/backend/controllers/supplierController.js +58 -0
- package/projects/SCMS/backend/middleware/auth.js +32 -0
- package/projects/SCMS/backend/models/User.js +28 -0
- package/projects/SCMS/backend/models/delivery.js +33 -0
- package/projects/SCMS/backend/models/product.js +43 -0
- package/projects/SCMS/backend/models/shipment.js +34 -0
- package/projects/SCMS/backend/models/supplier.js +36 -0
- package/projects/SCMS/backend/package-lock.json +2190 -778
- package/projects/SCMS/backend/package.json +23 -23
- package/projects/SCMS/backend/routes/SupplierRoutes.js +15 -0
- package/projects/SCMS/backend/routes/authRoutes.js +11 -0
- package/projects/SCMS/backend/routes/deliveryRoutes.js +18 -0
- package/projects/SCMS/backend/routes/productRoutes.js +15 -0
- package/projects/SCMS/backend/routes/protectedRoutes.js +10 -0
- package/projects/SCMS/backend/routes/reportsRoutes.js +8 -0
- package/projects/SCMS/backend/routes/shipmentRoutes.js +18 -0
- package/projects/SCMS/backend/server.js +28 -8
- package/projects/SCMS/frontend/index.html +1 -1
- package/projects/SCMS/frontend/package-lock.json +781 -152
- package/projects/SCMS/frontend/package.json +5 -1
- package/projects/SCMS/frontend/src/App.jsx +28 -115
- package/projects/SCMS/frontend/src/components/DashboardLayout.jsx +103 -0
- package/projects/SCMS/frontend/src/components/ProtectedRoute.jsx +30 -0
- package/projects/SCMS/frontend/src/index.css +110 -107
- package/projects/SCMS/frontend/src/pages/DashboardHome.jsx +34 -0
- package/projects/SCMS/frontend/src/pages/Delivery.jsx +183 -0
- package/projects/SCMS/frontend/src/pages/Login.jsx +81 -0
- package/projects/SCMS/frontend/src/pages/Profile.jsx +62 -0
- package/projects/SCMS/frontend/src/pages/Register.jsx +110 -0
- package/projects/SCMS/frontend/src/pages/Reports.jsx +94 -0
- package/projects/SCMS/frontend/src/pages/Shipment.jsx +182 -0
- package/projects/SCMS/frontend/src/pages/Supplier.jsx +165 -0
- package/projects/SCMS/frontend/vite.config.js +2 -2
- package/projects/SCMS/backend/db/connectDB.js +0 -11
- package/projects/SCMS/frontend/public/icons.svg +0 -24
- package/projects/SCMS/frontend/src/App.css +0 -184
- package/projects/SCMS/frontend/src/assets/hero.png +0 -0
- package/projects/SCMS/frontend/src/assets/react.svg +0 -1
- package/projects/SCMS/frontend/src/assets/vite.svg +0 -1
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
"preview": "vite preview"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
14
|
+
"axios": "^1.16.1",
|
|
13
15
|
"react": "^19.2.6",
|
|
14
|
-
"react-dom": "^19.2.6"
|
|
16
|
+
"react-dom": "^19.2.6",
|
|
17
|
+
"react-router-dom": "^7.15.1",
|
|
18
|
+
"tailwindcss": "^4.3.0"
|
|
15
19
|
},
|
|
16
20
|
"devDependencies": {
|
|
17
21
|
"@eslint/js": "^10.0.1",
|
|
@@ -1,122 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import './
|
|
1
|
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
2
|
+
import DashboardLayout from './components/DashboardLayout';
|
|
3
|
+
import DashboardHome from './pages/DashboardHome';
|
|
4
|
+
import Register from './pages/Register';
|
|
5
|
+
import Login from './pages/Login';
|
|
6
|
+
import Profile from './pages/Profile';
|
|
7
|
+
import Supplier from './pages/Supplier';
|
|
8
|
+
import Shipment from './pages/Shipment';
|
|
9
|
+
import Delivery from './pages/Delivery';
|
|
10
|
+
import Reports from './pages/Reports';
|
|
11
|
+
import ProtectedRoute from './components/ProtectedRoute';
|
|
6
12
|
|
|
7
13
|
function App() {
|
|
8
|
-
const [count, setCount] = useState(0)
|
|
9
|
-
|
|
10
14
|
return (
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
<img src={reactLogo} className="framework" alt="React logo" />
|
|
16
|
-
<img src={viteLogo} className="vite" alt="Vite logo" />
|
|
17
|
-
</div>
|
|
18
|
-
<div>
|
|
19
|
-
<h1>Get started</h1>
|
|
20
|
-
<p>
|
|
21
|
-
Edit <code>src/App.jsx</code> and save to test <code>HMR</code>
|
|
22
|
-
</p>
|
|
23
|
-
</div>
|
|
24
|
-
<button
|
|
25
|
-
type="button"
|
|
26
|
-
className="counter"
|
|
27
|
-
onClick={() => setCount((count) => count + 1)}
|
|
28
|
-
>
|
|
29
|
-
Count is {count}
|
|
30
|
-
</button>
|
|
31
|
-
</section>
|
|
32
|
-
|
|
33
|
-
<div className="ticks"></div>
|
|
15
|
+
<BrowserRouter>
|
|
16
|
+
<Routes>
|
|
17
|
+
<Route path="/login" element={<Login />} />
|
|
18
|
+
<Route path="/register" element={<Register />} />
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<li>
|
|
44
|
-
<a href="https://vite.dev/" target="_blank">
|
|
45
|
-
<img className="logo" src={viteLogo} alt="" />
|
|
46
|
-
Explore Vite
|
|
47
|
-
</a>
|
|
48
|
-
</li>
|
|
49
|
-
<li>
|
|
50
|
-
<a href="https://react.dev/" target="_blank">
|
|
51
|
-
<img className="button-icon" src={reactLogo} alt="" />
|
|
52
|
-
Learn more
|
|
53
|
-
</a>
|
|
54
|
-
</li>
|
|
55
|
-
</ul>
|
|
56
|
-
</div>
|
|
57
|
-
<div id="social">
|
|
58
|
-
<svg className="icon" role="presentation" aria-hidden="true">
|
|
59
|
-
<use href="/icons.svg#social-icon"></use>
|
|
60
|
-
</svg>
|
|
61
|
-
<h2>Connect with us</h2>
|
|
62
|
-
<p>Join the Vite community</p>
|
|
63
|
-
<ul>
|
|
64
|
-
<li>
|
|
65
|
-
<a href="https://github.com/vitejs/vite" target="_blank">
|
|
66
|
-
<svg
|
|
67
|
-
className="button-icon"
|
|
68
|
-
role="presentation"
|
|
69
|
-
aria-hidden="true"
|
|
70
|
-
>
|
|
71
|
-
<use href="/icons.svg#github-icon"></use>
|
|
72
|
-
</svg>
|
|
73
|
-
GitHub
|
|
74
|
-
</a>
|
|
75
|
-
</li>
|
|
76
|
-
<li>
|
|
77
|
-
<a href="https://chat.vite.dev/" target="_blank">
|
|
78
|
-
<svg
|
|
79
|
-
className="button-icon"
|
|
80
|
-
role="presentation"
|
|
81
|
-
aria-hidden="true"
|
|
82
|
-
>
|
|
83
|
-
<use href="/icons.svg#discord-icon"></use>
|
|
84
|
-
</svg>
|
|
85
|
-
Discord
|
|
86
|
-
</a>
|
|
87
|
-
</li>
|
|
88
|
-
<li>
|
|
89
|
-
<a href="https://x.com/vite_js" target="_blank">
|
|
90
|
-
<svg
|
|
91
|
-
className="button-icon"
|
|
92
|
-
role="presentation"
|
|
93
|
-
aria-hidden="true"
|
|
94
|
-
>
|
|
95
|
-
<use href="/icons.svg#x-icon"></use>
|
|
96
|
-
</svg>
|
|
97
|
-
X.com
|
|
98
|
-
</a>
|
|
99
|
-
</li>
|
|
100
|
-
<li>
|
|
101
|
-
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
|
102
|
-
<svg
|
|
103
|
-
className="button-icon"
|
|
104
|
-
role="presentation"
|
|
105
|
-
aria-hidden="true"
|
|
106
|
-
>
|
|
107
|
-
<use href="/icons.svg#bluesky-icon"></use>
|
|
108
|
-
</svg>
|
|
109
|
-
Bluesky
|
|
110
|
-
</a>
|
|
111
|
-
</li>
|
|
112
|
-
</ul>
|
|
113
|
-
</div>
|
|
114
|
-
</section>
|
|
20
|
+
<Route path="/dashboard" element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
|
|
21
|
+
<Route index element={<DashboardHome />} />
|
|
22
|
+
<Route path="supplier" element={<Supplier />} />
|
|
23
|
+
<Route path="shipment" element={<Shipment />} />
|
|
24
|
+
<Route path="delivery" element={<Delivery />} />
|
|
25
|
+
<Route path="reports" element={<Reports />} />
|
|
26
|
+
<Route path="profile" element={<Profile />} />
|
|
27
|
+
</Route>
|
|
115
28
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
29
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
30
|
+
</Routes>
|
|
31
|
+
</BrowserRouter>
|
|
32
|
+
);
|
|
120
33
|
}
|
|
121
34
|
|
|
122
|
-
export default App
|
|
35
|
+
export default App;
|
|
@@ -0,0 +1,103 @@
|
|
|
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', label: 'Overview', end: true },
|
|
7
|
+
{ to: '/dashboard/supplier', label: 'Supplier' },
|
|
8
|
+
{ to: '/dashboard/shipment', label: 'Shipment' },
|
|
9
|
+
{ to: '/dashboard/delivery', label: 'Delivery' },
|
|
10
|
+
{ to: '/dashboard/reports', label: 'Reports' },
|
|
11
|
+
{ to: '/dashboard/profile', label: 'Profile' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function DashboardLayout() {
|
|
15
|
+
const [user, setUser] = useState(null);
|
|
16
|
+
const [error, setError] = useState('');
|
|
17
|
+
const navigate = useNavigate();
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const storedUser = localStorage.getItem('user');
|
|
21
|
+
if (storedUser) {
|
|
22
|
+
setUser(JSON.parse(storedUser));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
axios
|
|
26
|
+
.get('http://localhost:5001/api/auth/me', {
|
|
27
|
+
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
|
28
|
+
})
|
|
29
|
+
.then((response) => {
|
|
30
|
+
setUser(response.data.user);
|
|
31
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
32
|
+
})
|
|
33
|
+
.catch((err) => {
|
|
34
|
+
if (!storedUser) {
|
|
35
|
+
setError(err.response?.data?.message || 'Could not load user');
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const handleLogout = () => {
|
|
41
|
+
localStorage.removeItem('token');
|
|
42
|
+
localStorage.removeItem('user');
|
|
43
|
+
navigate('/login');
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="min-h-screen bg-brand-50">
|
|
48
|
+
<aside className="fixed inset-y-0 left-0 w-60 bg-brand-950 text-slate-100 flex flex-col border-r border-brand-800">
|
|
49
|
+
<div className="px-5 py-5 border-b border-brand-800">
|
|
50
|
+
<p className="text-xs font-semibold uppercase tracking-wider text-brand-400">SCMS</p>
|
|
51
|
+
<h1 className="text-base font-semibold text-white mt-0.5">Supply Chain</h1>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<nav className="flex-1 px-3 py-4 space-y-0.5">
|
|
55
|
+
{navItems.map(({ to, label, end }) => (
|
|
56
|
+
<NavLink
|
|
57
|
+
key={to}
|
|
58
|
+
to={to}
|
|
59
|
+
end={end}
|
|
60
|
+
className={({ isActive }) =>
|
|
61
|
+
isActive ? `nav-link ${'nav-link-active'}` : 'nav-link'
|
|
62
|
+
}
|
|
63
|
+
>
|
|
64
|
+
{label}
|
|
65
|
+
</NavLink>
|
|
66
|
+
))}
|
|
67
|
+
</nav>
|
|
68
|
+
|
|
69
|
+
<div className="px-4 py-4 border-t border-brand-800">
|
|
70
|
+
{error && <p className="text-xs text-red-400 mb-2">{error}</p>}
|
|
71
|
+
{user && (
|
|
72
|
+
<>
|
|
73
|
+
<p className="text-sm font-medium text-white truncate">{user.name}</p>
|
|
74
|
+
<p className="text-xs text-slate-400 truncate mt-0.5">{user.email}</p>
|
|
75
|
+
</>
|
|
76
|
+
)}
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={handleLogout}
|
|
80
|
+
className="mt-3 w-full text-sm font-medium text-slate-300 hover:text-white border border-brand-700 rounded-lg px-3 py-2 hover:bg-brand-800 transition-colors"
|
|
81
|
+
>
|
|
82
|
+
Log out
|
|
83
|
+
</button>
|
|
84
|
+
</div>
|
|
85
|
+
</aside>
|
|
86
|
+
|
|
87
|
+
<div className="ml-60 flex min-h-screen flex-col">
|
|
88
|
+
<header className="bg-white border-b border-slate-200/80 px-6 py-4">
|
|
89
|
+
<p className="text-xs text-muted">Welcome back</p>
|
|
90
|
+
<h2 className="text-lg font-semibold text-brand-900">
|
|
91
|
+
{user ? `Hello, ${user.name}` : 'Dashboard'}
|
|
92
|
+
</h2>
|
|
93
|
+
</header>
|
|
94
|
+
|
|
95
|
+
<main className="flex-1 p-6 overflow-auto">
|
|
96
|
+
<Outlet />
|
|
97
|
+
</main>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default DashboardLayout;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Navigate } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
export default function ProtectedRoute({ children }) {
|
|
5
|
+
const [loading, setLoading] = useState(true);
|
|
6
|
+
const [authorized, setAuthorized] = useState(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const token = localStorage.getItem('token');
|
|
10
|
+
if (!token) {
|
|
11
|
+
setLoading(false);
|
|
12
|
+
setAuthorized(false);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fetch('http://localhost:5001/api/protected', {
|
|
17
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
18
|
+
})
|
|
19
|
+
.then((res) => {
|
|
20
|
+
if (res.ok) setAuthorized(true);
|
|
21
|
+
else setAuthorized(false);
|
|
22
|
+
})
|
|
23
|
+
.catch(() => setAuthorized(false))
|
|
24
|
+
.finally(() => setLoading(false));
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
if (loading) return <div>Loading...</div>;
|
|
28
|
+
if (!authorized) return <Navigate to="/login" replace />;
|
|
29
|
+
return children;
|
|
30
|
+
}
|
|
@@ -1,111 +1,114 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--
|
|
7
|
-
--
|
|
8
|
-
--
|
|
9
|
-
--
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--mono: ui-monospace, Consolas, monospace;
|
|
17
|
-
|
|
18
|
-
font: 18px/145% var(--sans);
|
|
19
|
-
letter-spacing: 0.18px;
|
|
20
|
-
color-scheme: light dark;
|
|
21
|
-
color: var(--text);
|
|
22
|
-
background: var(--bg);
|
|
23
|
-
font-synthesis: none;
|
|
24
|
-
text-rendering: optimizeLegibility;
|
|
25
|
-
-webkit-font-smoothing: antialiased;
|
|
26
|
-
-moz-osx-font-smoothing: grayscale;
|
|
27
|
-
|
|
28
|
-
@media (max-width: 1024px) {
|
|
29
|
-
font-size: 16px;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@media (prefers-color-scheme: dark) {
|
|
34
|
-
:root {
|
|
35
|
-
--text: #9ca3af;
|
|
36
|
-
--text-h: #f3f4f6;
|
|
37
|
-
--bg: #16171d;
|
|
38
|
-
--border: #2e303a;
|
|
39
|
-
--code-bg: #1f2028;
|
|
40
|
-
--accent: #c084fc;
|
|
41
|
-
--accent-bg: rgba(192, 132, 252, 0.15);
|
|
42
|
-
--accent-border: rgba(192, 132, 252, 0.5);
|
|
43
|
-
--social-bg: rgba(47, 48, 58, 0.5);
|
|
44
|
-
--shadow:
|
|
45
|
-
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
#social .button-icon {
|
|
49
|
-
filter: invert(1) brightness(2);
|
|
50
|
-
}
|
|
1
|
+
@import 'tailwindcss';
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-brand-950: #0a1628;
|
|
5
|
+
--color-brand-900: #0f2035;
|
|
6
|
+
--color-brand-800: #152d45;
|
|
7
|
+
--color-brand-700: #1a4558;
|
|
8
|
+
--color-brand-600: #1f6b6b;
|
|
9
|
+
--color-brand-500: #2a8585;
|
|
10
|
+
--color-brand-400: #4a9e9e;
|
|
11
|
+
--color-brand-100: #e8f2f2;
|
|
12
|
+
--color-brand-50: #f4f9f9;
|
|
13
|
+
--color-surface: #fafbfc;
|
|
14
|
+
--color-muted: #64748b;
|
|
15
|
+
--font-sans: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
51
16
|
}
|
|
52
17
|
|
|
53
18
|
body {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
font-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
19
|
+
@apply bg-surface text-brand-900 antialiased;
|
|
20
|
+
font-family: var(--font-sans);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Auth pages */
|
|
24
|
+
.auth-page {
|
|
25
|
+
@apply min-h-screen flex items-center justify-center px-4 bg-brand-50;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.auth-card {
|
|
29
|
+
@apply w-full max-w-md bg-white rounded-xl border border-brand-100 shadow-sm p-8;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Forms */
|
|
33
|
+
.input {
|
|
34
|
+
@apply w-full border border-slate-200 rounded-lg px-3 py-2 text-sm
|
|
35
|
+
text-brand-900 placeholder:text-slate-400
|
|
36
|
+
focus:outline-none focus:ring-2 focus:ring-brand-600/30 focus:border-brand-600
|
|
37
|
+
transition-colors;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.btn-primary {
|
|
41
|
+
@apply bg-brand-700 text-white text-sm font-medium px-4 py-2 rounded-lg
|
|
42
|
+
hover:bg-brand-800 disabled:opacity-50 transition-colors;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.btn-primary-full {
|
|
46
|
+
@apply bg-brand-700 text-white text-sm font-medium w-full py-2.5 rounded-lg
|
|
47
|
+
hover:bg-brand-800 disabled:opacity-50 transition-colors;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.btn-ghost {
|
|
51
|
+
@apply text-sm font-medium text-brand-600 hover:text-brand-800 transition-colors;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.btn-danger {
|
|
55
|
+
@apply text-sm font-medium text-red-600 hover:text-red-700 transition-colors;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Layout */
|
|
59
|
+
.page-content {
|
|
60
|
+
@apply max-w-4xl mx-auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.page-title {
|
|
64
|
+
@apply text-xl font-semibold text-brand-900 mb-1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.page-subtitle {
|
|
68
|
+
@apply text-sm text-muted mb-6;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.card {
|
|
72
|
+
@apply bg-white rounded-xl border border-slate-200/80 shadow-sm;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.card-body {
|
|
76
|
+
@apply p-5;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.form-card {
|
|
80
|
+
@apply bg-white rounded-xl border border-slate-200/80 shadow-sm p-5 space-y-3 mb-6;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.list-card {
|
|
84
|
+
@apply bg-white rounded-xl border border-slate-200/80 shadow-sm p-5 space-y-1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stat-card {
|
|
88
|
+
@apply bg-white rounded-xl border border-slate-200/80 shadow-sm p-5;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.stat-card h2 {
|
|
92
|
+
@apply text-sm font-semibold text-brand-700 mb-3;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.stat-card p {
|
|
96
|
+
@apply text-sm text-slate-600;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.alert-success {
|
|
100
|
+
@apply mb-4 text-sm text-brand-700 bg-brand-50 border border-brand-100 rounded-lg px-3 py-2;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.alert-error {
|
|
104
|
+
@apply mb-4 text-sm text-red-600 bg-red-50 border border-red-100 rounded-lg px-3 py-2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.nav-link {
|
|
108
|
+
@apply block rounded-lg px-3 py-2 text-sm font-medium text-slate-400
|
|
109
|
+
hover:bg-brand-800/60 hover:text-white transition-colors;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.nav-link-active {
|
|
113
|
+
@apply bg-brand-700 text-white;
|
|
111
114
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom';
|
|
2
|
+
|
|
3
|
+
const quickLinks = [
|
|
4
|
+
{ to: '/dashboard/supplier', label: 'Suppliers', desc: 'Manage supplier records' },
|
|
5
|
+
{ to: '/dashboard/shipment', label: 'Shipments', desc: 'Track shipment status' },
|
|
6
|
+
{ to: '/dashboard/delivery', label: 'Deliveries', desc: 'Record deliveries' },
|
|
7
|
+
{ to: '/dashboard/reports', label: 'Reports', desc: 'View summary reports' },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function DashboardHome() {
|
|
11
|
+
return (
|
|
12
|
+
<div className="page-content">
|
|
13
|
+
<h1 className="page-title">Overview</h1>
|
|
14
|
+
<p className="page-subtitle">Manage your supply chain from one place.</p>
|
|
15
|
+
|
|
16
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
17
|
+
{quickLinks.map(({ to, label, desc }) => (
|
|
18
|
+
<Link
|
|
19
|
+
key={to}
|
|
20
|
+
to={to}
|
|
21
|
+
className="card card-body hover:border-brand-600/40 transition-colors group"
|
|
22
|
+
>
|
|
23
|
+
<h3 className="text-sm font-semibold text-brand-800 group-hover:text-brand-700">
|
|
24
|
+
{label}
|
|
25
|
+
</h3>
|
|
26
|
+
<p className="text-sm text-muted mt-1">{desc}</p>
|
|
27
|
+
</Link>
|
|
28
|
+
))}
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default DashboardHome;
|