create-myexam-app 1.0.19 → 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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/projects/SCMS/backend/.env +5 -0
  3. package/projects/SCMS/backend/config/db.js +12 -0
  4. package/projects/SCMS/backend/controllers/authController.js +90 -0
  5. package/projects/SCMS/backend/controllers/deliveryController.js +79 -0
  6. package/projects/SCMS/backend/controllers/productController.js +74 -0
  7. package/projects/SCMS/backend/controllers/reportsController.js +77 -0
  8. package/projects/SCMS/backend/controllers/shipmentController.js +80 -0
  9. package/projects/SCMS/backend/controllers/supplierController.js +58 -0
  10. package/projects/SCMS/backend/middleware/auth.js +32 -0
  11. package/projects/SCMS/backend/models/User.js +28 -0
  12. package/projects/SCMS/backend/models/delivery.js +33 -0
  13. package/projects/SCMS/backend/models/product.js +43 -0
  14. package/projects/SCMS/backend/models/shipment.js +34 -0
  15. package/projects/SCMS/backend/models/supplier.js +36 -0
  16. package/projects/SCMS/backend/package-lock.json +2190 -0
  17. package/projects/SCMS/backend/package.json +23 -0
  18. package/projects/SCMS/backend/routes/SupplierRoutes.js +15 -0
  19. package/projects/SCMS/backend/routes/authRoutes.js +11 -0
  20. package/projects/SCMS/backend/routes/deliveryRoutes.js +18 -0
  21. package/projects/SCMS/backend/routes/productRoutes.js +15 -0
  22. package/projects/SCMS/backend/routes/protectedRoutes.js +10 -0
  23. package/projects/SCMS/backend/routes/reportsRoutes.js +8 -0
  24. package/projects/SCMS/backend/routes/shipmentRoutes.js +18 -0
  25. package/projects/SCMS/backend/server.js +35 -0
  26. package/projects/SCMS/frontend/README.md +16 -0
  27. package/projects/SCMS/frontend/eslint.config.js +21 -0
  28. package/projects/SCMS/frontend/index.html +13 -0
  29. package/projects/SCMS/frontend/package-lock.json +3053 -0
  30. package/projects/SCMS/frontend/package.json +31 -0
  31. package/projects/SCMS/frontend/public/favicon.svg +1 -0
  32. package/projects/SCMS/frontend/src/App.jsx +35 -0
  33. package/projects/SCMS/frontend/src/components/DashboardLayout.jsx +103 -0
  34. package/projects/SCMS/frontend/src/components/ProtectedRoute.jsx +30 -0
  35. package/projects/SCMS/frontend/src/index.css +114 -0
  36. package/projects/SCMS/frontend/src/main.jsx +10 -0
  37. package/projects/SCMS/frontend/src/pages/DashboardHome.jsx +34 -0
  38. package/projects/SCMS/frontend/src/pages/Delivery.jsx +183 -0
  39. package/projects/SCMS/frontend/src/pages/Login.jsx +81 -0
  40. package/projects/SCMS/frontend/src/pages/Profile.jsx +62 -0
  41. package/projects/SCMS/frontend/src/pages/Register.jsx +110 -0
  42. package/projects/SCMS/frontend/src/pages/Reports.jsx +94 -0
  43. package/projects/SCMS/frontend/src/pages/Shipment.jsx +182 -0
  44. package/projects/SCMS/frontend/src/pages/Supplier.jsx +165 -0
  45. package/projects/SCMS/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>
@@ -0,0 +1,35 @@
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';
12
+
13
+ function App() {
14
+ return (
15
+ <BrowserRouter>
16
+ <Routes>
17
+ <Route path="/login" element={<Login />} />
18
+ <Route path="/register" element={<Register />} />
19
+
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>
28
+
29
+ <Route path="/" element={<Navigate to="/dashboard" replace />} />
30
+ </Routes>
31
+ </BrowserRouter>
32
+ );
33
+ }
34
+
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
+ }
@@ -0,0 +1,114 @@
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;
16
+ }
17
+
18
+ body {
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;
114
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -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;
@@ -0,0 +1,183 @@
1
+ import { useEffect, useState } from 'react';
2
+ import axios from 'axios';
3
+
4
+ function Delivery() {
5
+ const [deliveries, setDeliveries] = useState([]);
6
+ const [shipments, setShipments] = useState([]);
7
+ const [deliveryCode, setDeliveryCode] = useState('');
8
+ const [deliveryDate, setDeliveryDate] = useState('');
9
+ const [quantityDelivered, setQuantityDelivered] = useState('');
10
+ const [deliveryStatus, setDeliveryStatus] = useState('Pending');
11
+ const [shipmentId, setShipmentId] = useState('');
12
+ const [editId, setEditId] = useState(null);
13
+ const [message, setMessage] = useState('');
14
+
15
+ const headers = {
16
+ Authorization: `Bearer ${localStorage.getItem('token')}`,
17
+ };
18
+
19
+ const clearForm = () => {
20
+ setDeliveryCode('');
21
+ setDeliveryDate('');
22
+ setQuantityDelivered('');
23
+ setDeliveryStatus('Pending');
24
+ setShipmentId('');
25
+ setEditId(null);
26
+ };
27
+
28
+ const loadShipments = async () => {
29
+ try {
30
+ const res = await axios.get('http://localhost:5001/api/shipment/getshipments', { headers });
31
+ setShipments(res.data);
32
+ } catch (error) {
33
+ console.error(error);
34
+ }
35
+ };
36
+
37
+ const loadDeliveries = async () => {
38
+ try {
39
+ const res = await axios.get('http://localhost:5001/api/delivery/getdeliveries', { headers });
40
+ setDeliveries(res.data);
41
+ } catch (error) {
42
+ console.error(error);
43
+ }
44
+ };
45
+
46
+ useEffect(() => {
47
+ loadShipments();
48
+ loadDeliveries();
49
+ }, []);
50
+
51
+ const handleSubmit = async (e) => {
52
+ e.preventDefault();
53
+ try {
54
+ if (editId) {
55
+ await axios.put(
56
+ `http://localhost:5001/api/delivery/updatedelivery/${editId}`,
57
+ { deliveryCode, deliveryDate, quantityDelivered, deliveryStatus, shipmentId },
58
+ { headers }
59
+ );
60
+ setMessage('Delivery updated successfully');
61
+ } else {
62
+ await axios.post(
63
+ 'http://localhost:5001/api/delivery/adddelivery',
64
+ { deliveryCode, deliveryDate, quantityDelivered, deliveryStatus, shipmentId },
65
+ { headers }
66
+ );
67
+ setMessage('Delivery added successfully');
68
+ }
69
+ clearForm();
70
+ loadDeliveries();
71
+ } catch (error) {
72
+ console.error(error);
73
+ setMessage('Failed to save delivery');
74
+ }
75
+ };
76
+
77
+ const handleEdit = (delivery) => {
78
+ setDeliveryCode(delivery.deliveryCode);
79
+ setDeliveryDate(delivery.deliveryDate?.split('T')[0] || '');
80
+ setQuantityDelivered(delivery.quantityDelivered);
81
+ setDeliveryStatus(delivery.deliveryStatus);
82
+ setShipmentId(delivery.shipment?._id || '');
83
+ setEditId(delivery._id);
84
+ };
85
+
86
+ const handleDelete = async (id) => {
87
+ try {
88
+ await axios.delete(`http://localhost:5001/api/delivery/deletedelivery/${id}`, { headers });
89
+ loadDeliveries();
90
+ } catch (error) {
91
+ console.error(error);
92
+ }
93
+ };
94
+
95
+ return (
96
+ <div className="page-content">
97
+ <h1 className="page-title">Delivery Management</h1>
98
+ <p className="page-subtitle">Record and track deliveries</p>
99
+
100
+ {message && <p className="alert-success">{message}</p>}
101
+
102
+ <form onSubmit={handleSubmit} className="form-card">
103
+ <input
104
+ type="text"
105
+ placeholder="Delivery Code"
106
+ value={deliveryCode}
107
+ onChange={(e) => setDeliveryCode(e.target.value)}
108
+ className="input"
109
+ required
110
+ />
111
+ <input
112
+ type="date"
113
+ value={deliveryDate}
114
+ onChange={(e) => setDeliveryDate(e.target.value)}
115
+ className="input"
116
+ required
117
+ />
118
+ <input
119
+ type="number"
120
+ placeholder="Quantity Delivered"
121
+ value={quantityDelivered}
122
+ onChange={(e) => setQuantityDelivered(e.target.value)}
123
+ className="input"
124
+ required
125
+ />
126
+ <select
127
+ value={deliveryStatus}
128
+ onChange={(e) => setDeliveryStatus(e.target.value)}
129
+ className="input"
130
+ required
131
+ >
132
+ <option value="Pending">Pending</option>
133
+ <option value="Delivered">Delivered</option>
134
+ <option value="Returned">Returned</option>
135
+ </select>
136
+ <select
137
+ value={shipmentId}
138
+ onChange={(e) => setShipmentId(e.target.value)}
139
+ className="input"
140
+ required
141
+ >
142
+ <option value="">Select shipment</option>
143
+ {shipments.map((shipment) => (
144
+ <option key={shipment._id} value={shipment._id}>
145
+ {shipment.shipmentNumber} - {shipment.destination}
146
+ </option>
147
+ ))}
148
+ </select>
149
+ <button type="submit" className="btn-primary">
150
+ {editId ? 'Update Delivery' : 'Add Delivery'}
151
+ </button>
152
+ </form>
153
+
154
+ <div className="space-y-3">
155
+ {deliveries.length === 0 ? (
156
+ <p className="text-sm text-muted">No delivery records yet.</p>
157
+ ) : (
158
+ deliveries.map((delivery) => (
159
+ <div key={delivery._id} className="list-card">
160
+ <p className="text-sm font-medium text-brand-800">{delivery.deliveryCode}</p>
161
+ <p className="text-sm text-muted">Date: {delivery.deliveryDate?.split('T')[0]}</p>
162
+ <p className="text-sm text-muted">Quantity: {delivery.quantityDelivered}</p>
163
+ <p className="text-sm text-muted">Status: {delivery.deliveryStatus}</p>
164
+ <p className="text-sm text-muted">
165
+ Shipment: {delivery.shipment ? delivery.shipment.shipmentNumber : 'Unassigned'}
166
+ </p>
167
+ <div className="flex gap-4 pt-2">
168
+ <button type="button" onClick={() => handleEdit(delivery)} className="btn-ghost">
169
+ Edit
170
+ </button>
171
+ <button type="button" onClick={() => handleDelete(delivery._id)} className="btn-danger">
172
+ Delete
173
+ </button>
174
+ </div>
175
+ </div>
176
+ ))
177
+ )}
178
+ </div>
179
+ </div>
180
+ );
181
+ }
182
+
183
+ export default Delivery;
@@ -0,0 +1,81 @@
1
+ import { useState } from 'react';
2
+ import axios from 'axios';
3
+ import { Link, useNavigate } from 'react-router-dom';
4
+
5
+ function Login() {
6
+ const navigate = useNavigate();
7
+ const [email, setEmail] = useState('');
8
+ const [password, setPassword] = useState('');
9
+ const [error, setError] = useState('');
10
+ const [message, setMessage] = useState('');
11
+ const [loading, setLoading] = useState(false);
12
+
13
+ const handleSubmit = async (e) => {
14
+ e.preventDefault();
15
+ setError('');
16
+ setMessage('');
17
+ setLoading(true);
18
+
19
+ try {
20
+ const response = await axios.post('http://localhost:5001/api/auth/login', { email, password });
21
+ localStorage.setItem('token', response.data.token);
22
+ localStorage.setItem('user', JSON.stringify(response.data.user));
23
+ setMessage('Login successful');
24
+ navigate('/dashboard');
25
+ } catch (err) {
26
+ setError(err.response?.data?.message || 'Login failed');
27
+ console.error('Server Error:', err.response?.data || err.message);
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+
33
+ return (
34
+ <div className="auth-page">
35
+ <div className="auth-card">
36
+ <p className="text-xs font-semibold uppercase tracking-wider text-brand-600 mb-1">SCMS</p>
37
+ <h1 className="text-xl font-semibold text-brand-900 mb-6">Sign in</h1>
38
+
39
+ {message && <p className="alert-success">{message}</p>}
40
+ {error && <p className="alert-error">{error}</p>}
41
+
42
+ <form onSubmit={handleSubmit} className="space-y-4">
43
+ <div>
44
+ <label className="block text-sm font-medium text-slate-700 mb-1">Email</label>
45
+ <input
46
+ type="text"
47
+ required
48
+ value={email}
49
+ onChange={(e) => setEmail(e.target.value)}
50
+ className="input"
51
+ />
52
+ </div>
53
+
54
+ <div>
55
+ <label className="block text-sm font-medium text-slate-700 mb-1">Password</label>
56
+ <input
57
+ type="password"
58
+ required
59
+ value={password}
60
+ onChange={(e) => setPassword(e.target.value)}
61
+ className="input"
62
+ />
63
+ </div>
64
+
65
+ <button type="submit" disabled={loading} className="btn-primary-full">
66
+ {loading ? 'Signing in...' : 'Sign in'}
67
+ </button>
68
+ </form>
69
+
70
+ <p className="mt-5 text-sm text-muted text-center">
71
+ No account?{' '}
72
+ <Link to="/register" className="text-brand-700 font-medium hover:text-brand-800">
73
+ Register
74
+ </Link>
75
+ </p>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ export default Login;