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.
Files changed (47) hide show
  1. package/package.json +1 -1
  2. package/projects/SCMS/backend/.env +2 -2
  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 -778
  17. package/projects/SCMS/backend/package.json +23 -23
  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 +28 -8
  26. package/projects/SCMS/frontend/index.html +1 -1
  27. package/projects/SCMS/frontend/package-lock.json +781 -152
  28. package/projects/SCMS/frontend/package.json +5 -1
  29. package/projects/SCMS/frontend/src/App.jsx +28 -115
  30. package/projects/SCMS/frontend/src/components/DashboardLayout.jsx +103 -0
  31. package/projects/SCMS/frontend/src/components/ProtectedRoute.jsx +30 -0
  32. package/projects/SCMS/frontend/src/index.css +110 -107
  33. package/projects/SCMS/frontend/src/pages/DashboardHome.jsx +34 -0
  34. package/projects/SCMS/frontend/src/pages/Delivery.jsx +183 -0
  35. package/projects/SCMS/frontend/src/pages/Login.jsx +81 -0
  36. package/projects/SCMS/frontend/src/pages/Profile.jsx +62 -0
  37. package/projects/SCMS/frontend/src/pages/Register.jsx +110 -0
  38. package/projects/SCMS/frontend/src/pages/Reports.jsx +94 -0
  39. package/projects/SCMS/frontend/src/pages/Shipment.jsx +182 -0
  40. package/projects/SCMS/frontend/src/pages/Supplier.jsx +165 -0
  41. package/projects/SCMS/frontend/vite.config.js +2 -2
  42. package/projects/SCMS/backend/db/connectDB.js +0 -11
  43. package/projects/SCMS/frontend/public/icons.svg +0 -24
  44. package/projects/SCMS/frontend/src/App.css +0 -184
  45. package/projects/SCMS/frontend/src/assets/hero.png +0 -0
  46. package/projects/SCMS/frontend/src/assets/react.svg +0 -1
  47. 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;