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.
- package/package.json +1 -1
- package/projects/SCMS/backend/.env +5 -0
- 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 -0
- package/projects/SCMS/backend/package.json +23 -0
- 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 +35 -0
- package/projects/SCMS/frontend/README.md +16 -0
- package/projects/SCMS/frontend/eslint.config.js +21 -0
- package/projects/SCMS/frontend/index.html +13 -0
- package/projects/SCMS/frontend/package-lock.json +3053 -0
- package/projects/SCMS/frontend/package.json +31 -0
- package/projects/SCMS/frontend/public/favicon.svg +1 -0
- package/projects/SCMS/frontend/src/App.jsx +35 -0
- 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 +114 -0
- package/projects/SCMS/frontend/src/main.jsx +10 -0
- 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 +7 -0
|
@@ -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;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Shipment() {
|
|
5
|
+
const [shipments, setShipments] = useState([]);
|
|
6
|
+
const [suppliers, setSuppliers] = useState([]);
|
|
7
|
+
const [shipmentNumber, setShipmentNumber] = useState('');
|
|
8
|
+
const [shipmentDate, setShipmentDate] = useState('');
|
|
9
|
+
const [shipmentStatus, setShipmentStatus] = useState('Pending');
|
|
10
|
+
const [destination, setDestination] = useState('');
|
|
11
|
+
const [supplierId, setSupplierId] = 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
|
+
setShipmentNumber('');
|
|
21
|
+
setShipmentDate('');
|
|
22
|
+
setShipmentStatus('Pending');
|
|
23
|
+
setDestination('');
|
|
24
|
+
setSupplierId('');
|
|
25
|
+
setEditId(null);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const loadSuppliers = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const res = await axios.get('http://localhost:5001/api/supplier/getsupplier', { headers });
|
|
31
|
+
setSuppliers(res.data);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const loadShipments = async () => {
|
|
38
|
+
try {
|
|
39
|
+
const res = await axios.get('http://localhost:5001/api/shipment/getshipments', { headers });
|
|
40
|
+
setShipments(res.data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
loadSuppliers();
|
|
48
|
+
loadShipments();
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const handleSubmit = async (e) => {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
try {
|
|
54
|
+
if (editId) {
|
|
55
|
+
await axios.put(
|
|
56
|
+
`http://localhost:5001/api/shipment/updateshipment/${editId}`,
|
|
57
|
+
{ shipmentNumber, shipmentDate, shipmentStatus, destination, supplierId },
|
|
58
|
+
{ headers }
|
|
59
|
+
);
|
|
60
|
+
setMessage('Shipment updated successfully');
|
|
61
|
+
} else {
|
|
62
|
+
await axios.post(
|
|
63
|
+
'http://localhost:5001/api/shipment/addshipment',
|
|
64
|
+
{ shipmentNumber, shipmentDate, shipmentStatus, destination, supplierId },
|
|
65
|
+
{ headers }
|
|
66
|
+
);
|
|
67
|
+
setMessage('Shipment added successfully');
|
|
68
|
+
}
|
|
69
|
+
clearForm();
|
|
70
|
+
loadShipments();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(error);
|
|
73
|
+
setMessage('Failed to save shipment');
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleEdit = (shipment) => {
|
|
78
|
+
setShipmentNumber(shipment.shipmentNumber);
|
|
79
|
+
setShipmentDate(shipment.shipmentDate?.split('T')[0] || '');
|
|
80
|
+
setShipmentStatus(shipment.shipmentStatus);
|
|
81
|
+
setDestination(shipment.destination);
|
|
82
|
+
setSupplierId(shipment.supplier?._id || '');
|
|
83
|
+
setEditId(shipment._id);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleDelete = async (id) => {
|
|
87
|
+
try {
|
|
88
|
+
await axios.delete(`http://localhost:5001/api/shipment/deleteshipment/${id}`, { headers });
|
|
89
|
+
loadShipments();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(error);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="page-content">
|
|
97
|
+
<h1 className="page-title">Shipment Management</h1>
|
|
98
|
+
<p className="page-subtitle">Track and manage shipments</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="Shipment Number"
|
|
106
|
+
value={shipmentNumber}
|
|
107
|
+
onChange={(e) => setShipmentNumber(e.target.value)}
|
|
108
|
+
className="input"
|
|
109
|
+
required
|
|
110
|
+
/>
|
|
111
|
+
<input
|
|
112
|
+
type="date"
|
|
113
|
+
value={shipmentDate}
|
|
114
|
+
onChange={(e) => setShipmentDate(e.target.value)}
|
|
115
|
+
className="input"
|
|
116
|
+
required
|
|
117
|
+
/>
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
placeholder="Destination"
|
|
121
|
+
value={destination}
|
|
122
|
+
onChange={(e) => setDestination(e.target.value)}
|
|
123
|
+
className="input"
|
|
124
|
+
required
|
|
125
|
+
/>
|
|
126
|
+
<select
|
|
127
|
+
value={shipmentStatus}
|
|
128
|
+
onChange={(e) => setShipmentStatus(e.target.value)}
|
|
129
|
+
className="input"
|
|
130
|
+
required
|
|
131
|
+
>
|
|
132
|
+
<option value="Pending">Pending</option>
|
|
133
|
+
<option value="In Transit">In Transit</option>
|
|
134
|
+
<option value="Delivered">Delivered</option>
|
|
135
|
+
</select>
|
|
136
|
+
<select
|
|
137
|
+
value={supplierId}
|
|
138
|
+
onChange={(e) => setSupplierId(e.target.value)}
|
|
139
|
+
className="input"
|
|
140
|
+
>
|
|
141
|
+
<option value="">Select supplier (optional)</option>
|
|
142
|
+
{suppliers.map((supplier) => (
|
|
143
|
+
<option key={supplier._id} value={supplier._id}>
|
|
144
|
+
{supplier.supplierCode} - {supplier.supplierName}
|
|
145
|
+
</option>
|
|
146
|
+
))}
|
|
147
|
+
</select>
|
|
148
|
+
<button type="submit" className="btn-primary">
|
|
149
|
+
{editId ? 'Update Shipment' : 'Add Shipment'}
|
|
150
|
+
</button>
|
|
151
|
+
</form>
|
|
152
|
+
|
|
153
|
+
<div className="space-y-3">
|
|
154
|
+
{shipments.length === 0 ? (
|
|
155
|
+
<p className="text-sm text-muted">No shipment records available.</p>
|
|
156
|
+
) : (
|
|
157
|
+
shipments.map((shipment) => (
|
|
158
|
+
<div key={shipment._id} className="list-card">
|
|
159
|
+
<p className="text-sm font-medium text-brand-800">{shipment.shipmentNumber}</p>
|
|
160
|
+
<p className="text-sm text-muted">Date: {shipment.shipmentDate?.split('T')[0]}</p>
|
|
161
|
+
<p className="text-sm text-muted">Destination: {shipment.destination}</p>
|
|
162
|
+
<p className="text-sm text-muted">Status: {shipment.shipmentStatus}</p>
|
|
163
|
+
<p className="text-sm text-muted">
|
|
164
|
+
Supplier: {shipment.supplier ? shipment.supplier.supplierName : 'Not assigned'}
|
|
165
|
+
</p>
|
|
166
|
+
<div className="flex gap-4 pt-2">
|
|
167
|
+
<button type="button" onClick={() => handleEdit(shipment)} className="btn-ghost">
|
|
168
|
+
Edit
|
|
169
|
+
</button>
|
|
170
|
+
<button type="button" onClick={() => handleDelete(shipment._id)} className="btn-danger">
|
|
171
|
+
Delete
|
|
172
|
+
</button>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
))
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export default Shipment;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
function Supplier() {
|
|
5
|
+
const [suppliers, setSuppliers] = useState([]);
|
|
6
|
+
const [supplierCode, setSupplierCode] = useState('');
|
|
7
|
+
const [supplierName, setSupplierName] = useState('');
|
|
8
|
+
const [address, setAddress] = useState('');
|
|
9
|
+
const [telephone, setTelephone] = useState('');
|
|
10
|
+
const [email, setEmail] = useState('');
|
|
11
|
+
const [editId, setEditId] = useState(null);
|
|
12
|
+
const [message, setMessage] = useState('');
|
|
13
|
+
|
|
14
|
+
const headers = {
|
|
15
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const clearForm = () => {
|
|
19
|
+
setSupplierCode('');
|
|
20
|
+
setSupplierName('');
|
|
21
|
+
setAddress('');
|
|
22
|
+
setTelephone('');
|
|
23
|
+
setEmail('');
|
|
24
|
+
setEditId(null);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleGet = async () => {
|
|
28
|
+
try {
|
|
29
|
+
const res = await axios.get('http://localhost:5001/api/supplier/getsupplier', { headers });
|
|
30
|
+
setSuppliers(res.data);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.log(error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
handleGet();
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
const handleSubmit = async (e) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (editId) {
|
|
45
|
+
await axios.put(
|
|
46
|
+
`http://localhost:5001/api/supplier/updatesupplier/${editId}`,
|
|
47
|
+
{ supplierCode, supplierName, address, telephone, email },
|
|
48
|
+
{ headers }
|
|
49
|
+
);
|
|
50
|
+
setMessage('Updated successfully');
|
|
51
|
+
} else {
|
|
52
|
+
await axios.post(
|
|
53
|
+
'http://localhost:5001/api/supplier/addsupplier',
|
|
54
|
+
{ supplierCode, supplierName, address, telephone, email },
|
|
55
|
+
{ headers }
|
|
56
|
+
);
|
|
57
|
+
setMessage('Added successfully');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clearForm();
|
|
61
|
+
handleGet();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.log(error);
|
|
64
|
+
setMessage('Something went wrong');
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleDelete = async (id) => {
|
|
69
|
+
try {
|
|
70
|
+
await axios.delete(`http://localhost:5001/api/supplier/deletesupplier/${id}`, { headers });
|
|
71
|
+
handleGet();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.log(error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleEdit = (supplier) => {
|
|
78
|
+
setSupplierCode(supplier.supplierCode);
|
|
79
|
+
setSupplierName(supplier.supplierName);
|
|
80
|
+
setAddress(supplier.address);
|
|
81
|
+
setTelephone(supplier.telephone);
|
|
82
|
+
setEmail(supplier.email);
|
|
83
|
+
setEditId(supplier._id);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="page-content">
|
|
88
|
+
<h1 className="page-title">Supplier Management</h1>
|
|
89
|
+
<p className="page-subtitle">Add and manage supplier records</p>
|
|
90
|
+
|
|
91
|
+
{message && <p className="alert-success">{message}</p>}
|
|
92
|
+
|
|
93
|
+
<form onSubmit={handleSubmit} className="form-card">
|
|
94
|
+
<input
|
|
95
|
+
type="text"
|
|
96
|
+
placeholder="Supplier Code"
|
|
97
|
+
value={supplierCode}
|
|
98
|
+
onChange={(e) => setSupplierCode(e.target.value)}
|
|
99
|
+
className="input"
|
|
100
|
+
required
|
|
101
|
+
/>
|
|
102
|
+
<input
|
|
103
|
+
type="text"
|
|
104
|
+
placeholder="Supplier Name"
|
|
105
|
+
value={supplierName}
|
|
106
|
+
onChange={(e) => setSupplierName(e.target.value)}
|
|
107
|
+
className="input"
|
|
108
|
+
required
|
|
109
|
+
/>
|
|
110
|
+
<input
|
|
111
|
+
type="text"
|
|
112
|
+
placeholder="Address"
|
|
113
|
+
value={address}
|
|
114
|
+
onChange={(e) => setAddress(e.target.value)}
|
|
115
|
+
className="input"
|
|
116
|
+
required
|
|
117
|
+
/>
|
|
118
|
+
<input
|
|
119
|
+
type="text"
|
|
120
|
+
placeholder="Telephone"
|
|
121
|
+
value={telephone}
|
|
122
|
+
onChange={(e) => setTelephone(e.target.value)}
|
|
123
|
+
className="input"
|
|
124
|
+
required
|
|
125
|
+
/>
|
|
126
|
+
<input
|
|
127
|
+
type="email"
|
|
128
|
+
placeholder="Email"
|
|
129
|
+
value={email}
|
|
130
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
131
|
+
className="input"
|
|
132
|
+
required
|
|
133
|
+
/>
|
|
134
|
+
<button type="submit" className="btn-primary">
|
|
135
|
+
{editId ? 'Update' : 'Add Supplier'}
|
|
136
|
+
</button>
|
|
137
|
+
</form>
|
|
138
|
+
|
|
139
|
+
<div className="space-y-3">
|
|
140
|
+
{suppliers.length === 0 ? (
|
|
141
|
+
<p className="text-sm text-muted">No suppliers yet.</p>
|
|
142
|
+
) : (
|
|
143
|
+
suppliers.map((supplier) => (
|
|
144
|
+
<div key={supplier._id} className="list-card">
|
|
145
|
+
<p className="text-sm font-medium text-brand-800">{supplier.supplierName}</p>
|
|
146
|
+
<p className="text-sm text-muted">Code: {supplier.supplierCode}</p>
|
|
147
|
+
<p className="text-sm text-muted">{supplier.address}</p>
|
|
148
|
+
<p className="text-sm text-muted">{supplier.telephone} · {supplier.email}</p>
|
|
149
|
+
<div className="flex gap-4 pt-2">
|
|
150
|
+
<button type="button" onClick={() => handleEdit(supplier)} className="btn-ghost">
|
|
151
|
+
Edit
|
|
152
|
+
</button>
|
|
153
|
+
<button type="button" onClick={() => handleDelete(supplier._id)} className="btn-danger">
|
|
154
|
+
Delete
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
))
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default Supplier;
|