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
|
@@ -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;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Profile() {
|
|
5
|
+
const [user, setUser] = useState(null);
|
|
6
|
+
const [error, setError] = useState('');
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
axios
|
|
10
|
+
.get('http://localhost:5001/api/auth/me', {
|
|
11
|
+
headers: { Authorization: `Bearer ${localStorage.getItem('token')}` },
|
|
12
|
+
})
|
|
13
|
+
.then((response) => {
|
|
14
|
+
setUser(response.data.user);
|
|
15
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
16
|
+
})
|
|
17
|
+
.catch((err) => {
|
|
18
|
+
const storedUser = localStorage.getItem('user');
|
|
19
|
+
if (storedUser) {
|
|
20
|
+
setUser(JSON.parse(storedUser));
|
|
21
|
+
} else {
|
|
22
|
+
setError(err.response?.data?.message || 'Failed to load profile');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
if (!user) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="page-content">
|
|
30
|
+
<p className="text-muted">{error || 'No profile data found. Try signing in again.'}</p>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="page-content max-w-lg">
|
|
37
|
+
<h1 className="page-title">Profile</h1>
|
|
38
|
+
<p className="page-subtitle">Your account details</p>
|
|
39
|
+
|
|
40
|
+
{error && <p className="alert-error">{error}</p>}
|
|
41
|
+
|
|
42
|
+
<div className="card divide-y divide-slate-100">
|
|
43
|
+
<div className="px-5 py-3.5 flex justify-between gap-4">
|
|
44
|
+
<span className="text-sm text-muted">Name</span>
|
|
45
|
+
<span className="text-sm text-brand-900">{user.name}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="px-5 py-3.5 flex justify-between gap-4">
|
|
48
|
+
<span className="text-sm text-muted">Email</span>
|
|
49
|
+
<span className="text-sm text-brand-900">{user.email}</span>
|
|
50
|
+
</div>
|
|
51
|
+
{user.id && (
|
|
52
|
+
<div className="px-5 py-3.5 flex justify-between gap-4">
|
|
53
|
+
<span className="text-sm text-muted">User ID</span>
|
|
54
|
+
<span className="text-sm text-brand-900 font-mono truncate">{user.id}</span>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default Profile;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { Link, useNavigate } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
function Register() {
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const [name, setName] = useState('');
|
|
8
|
+
const [email, setEmail] = useState('');
|
|
9
|
+
const [password, setPassword] = useState('');
|
|
10
|
+
const [error, setError] = useState('');
|
|
11
|
+
const [message, setMessage] = useState('');
|
|
12
|
+
const [loading, setLoading] = useState(false);
|
|
13
|
+
|
|
14
|
+
const handleSubmit = async (e) => {
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
setError('');
|
|
17
|
+
setMessage('');
|
|
18
|
+
setLoading(true);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const response = await axios.post('http://localhost:5001/api/auth/register', {
|
|
22
|
+
name,
|
|
23
|
+
email,
|
|
24
|
+
password,
|
|
25
|
+
});
|
|
26
|
+
localStorage.setItem('token', response.data.token);
|
|
27
|
+
localStorage.setItem('user', JSON.stringify(response.data.user));
|
|
28
|
+
setMessage('Account created successfully');
|
|
29
|
+
navigate('/dashboard');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
setError(err.response?.data?.message || 'Registration failed');
|
|
32
|
+
console.error('Server Error:', err.response?.data || err.message);
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="auth-page">
|
|
40
|
+
<div className="auth-card">
|
|
41
|
+
<p className="text-xs font-semibold uppercase tracking-wider text-brand-600 mb-1">SCMS</p>
|
|
42
|
+
<h1 className="text-xl font-semibold text-brand-900 mb-6">Create account</h1>
|
|
43
|
+
|
|
44
|
+
{message && <p className="alert-success">{message}</p>}
|
|
45
|
+
{error && <p className="alert-error">{error}</p>}
|
|
46
|
+
|
|
47
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
48
|
+
<div>
|
|
49
|
+
<label htmlFor="name" className="block text-sm font-medium text-slate-700 mb-1">
|
|
50
|
+
Name
|
|
51
|
+
</label>
|
|
52
|
+
<input
|
|
53
|
+
id="name"
|
|
54
|
+
name="name"
|
|
55
|
+
type="text"
|
|
56
|
+
required
|
|
57
|
+
value={name}
|
|
58
|
+
onChange={(e) => setName(e.target.value)}
|
|
59
|
+
className="input"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div>
|
|
64
|
+
<label htmlFor="email" className="block text-sm font-medium text-slate-700 mb-1">
|
|
65
|
+
Email
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
id="email"
|
|
69
|
+
name="email"
|
|
70
|
+
type="email"
|
|
71
|
+
required
|
|
72
|
+
value={email}
|
|
73
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
74
|
+
className="input"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div>
|
|
79
|
+
<label htmlFor="password" className="block text-sm font-medium text-slate-700 mb-1">
|
|
80
|
+
Password
|
|
81
|
+
</label>
|
|
82
|
+
<input
|
|
83
|
+
id="password"
|
|
84
|
+
name="password"
|
|
85
|
+
type="password"
|
|
86
|
+
required
|
|
87
|
+
minLength={6}
|
|
88
|
+
value={password}
|
|
89
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
90
|
+
className="input"
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<button type="submit" disabled={loading} className="btn-primary-full">
|
|
95
|
+
{loading ? 'Creating account...' : 'Register'}
|
|
96
|
+
</button>
|
|
97
|
+
</form>
|
|
98
|
+
|
|
99
|
+
<p className="mt-5 text-sm text-muted text-center">
|
|
100
|
+
Already have an account?{' '}
|
|
101
|
+
<Link to="/login" className="text-brand-700 font-medium hover:text-brand-800">
|
|
102
|
+
Sign in
|
|
103
|
+
</Link>
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default Register;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Reports() {
|
|
5
|
+
const [report, setReport] = useState(null);
|
|
6
|
+
const [error, setError] = useState('');
|
|
7
|
+
|
|
8
|
+
const headers = {
|
|
9
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
axios
|
|
14
|
+
.get('http://localhost:5001/api/reports/summary', { headers })
|
|
15
|
+
.then((res) => setReport(res.data))
|
|
16
|
+
.catch((err) => {
|
|
17
|
+
console.error(err);
|
|
18
|
+
setError('Failed to load reports');
|
|
19
|
+
});
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="page-content">
|
|
24
|
+
<h1 className="page-title">Reports</h1>
|
|
25
|
+
<p className="page-subtitle">Summary of your supply chain activity</p>
|
|
26
|
+
|
|
27
|
+
{error && <p className="alert-error">{error}</p>}
|
|
28
|
+
|
|
29
|
+
{!report ? (
|
|
30
|
+
<p className="text-sm text-muted">Loading report summary...</p>
|
|
31
|
+
) : (
|
|
32
|
+
<>
|
|
33
|
+
<div className="grid gap-4 md:grid-cols-3 mb-6">
|
|
34
|
+
<div className="stat-card">
|
|
35
|
+
<h2>Suppliers</h2>
|
|
36
|
+
<p>Total: {report.suppliers.total}</p>
|
|
37
|
+
<p>Daily: {report.suppliers.daily}</p>
|
|
38
|
+
<p>Weekly: {report.suppliers.weekly}</p>
|
|
39
|
+
<p>Monthly: {report.suppliers.monthly}</p>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="stat-card">
|
|
42
|
+
<h2>Shipments</h2>
|
|
43
|
+
<p>Total: {report.shipments.total}</p>
|
|
44
|
+
<p>Daily: {report.shipments.daily}</p>
|
|
45
|
+
<p>Weekly: {report.shipments.weekly}</p>
|
|
46
|
+
<p>Monthly: {report.shipments.monthly}</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="stat-card">
|
|
49
|
+
<h2>Deliveries</h2>
|
|
50
|
+
<p>Total: {report.deliveries.total}</p>
|
|
51
|
+
<p>Daily: {report.deliveries.daily}</p>
|
|
52
|
+
<p>Weekly: {report.deliveries.weekly}</p>
|
|
53
|
+
<p>Monthly: {report.deliveries.monthly}</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
58
|
+
<div className="stat-card">
|
|
59
|
+
<h2>Recent Shipments</h2>
|
|
60
|
+
{report.recentShipments.length === 0 ? (
|
|
61
|
+
<p className="text-sm text-muted">No recent shipments.</p>
|
|
62
|
+
) : (
|
|
63
|
+
report.recentShipments.map((shipment) => (
|
|
64
|
+
<div key={shipment._id} className="mb-3 pb-3 border-b border-slate-100 last:border-0 last:mb-0 last:pb-0">
|
|
65
|
+
<p className="text-sm font-medium text-brand-800">{shipment.shipmentNumber}</p>
|
|
66
|
+
<p className="text-sm text-muted">Destination: {shipment.destination}</p>
|
|
67
|
+
<p className="text-sm text-muted">Status: {shipment.shipmentStatus}</p>
|
|
68
|
+
</div>
|
|
69
|
+
))
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div className="stat-card">
|
|
74
|
+
<h2>Recent Deliveries</h2>
|
|
75
|
+
{report.recentDeliveries.length === 0 ? (
|
|
76
|
+
<p className="text-sm text-muted">No recent deliveries.</p>
|
|
77
|
+
) : (
|
|
78
|
+
report.recentDeliveries.map((delivery) => (
|
|
79
|
+
<div key={delivery._id} className="mb-3 pb-3 border-b border-slate-100 last:border-0 last:mb-0 last:pb-0">
|
|
80
|
+
<p className="text-sm font-medium text-brand-800">{delivery.deliveryCode}</p>
|
|
81
|
+
<p className="text-sm text-muted">Quantity: {delivery.quantityDelivered}</p>
|
|
82
|
+
<p className="text-sm text-muted">Status: {delivery.deliveryStatus}</p>
|
|
83
|
+
</div>
|
|
84
|
+
))
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default Reports;
|