@20052507/dab-enterprise-bws 1.0.1
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/ERD_DFD_Documentation.md +330 -0
- package/README.md +307 -0
- package/backend-project/config/database.js +13 -0
- package/backend-project/controllers/authController.js +67 -0
- package/backend-project/controllers/productController.js +104 -0
- package/backend-project/controllers/salesController.js +208 -0
- package/backend-project/controllers/stockController.js +76 -0
- package/backend-project/middleware/authMiddleware.js +9 -0
- package/backend-project/package.json +23 -0
- package/backend-project/routes/authRoutes.js +9 -0
- package/backend-project/routes/productRoutes.js +18 -0
- package/backend-project/routes/salesRoutes.js +22 -0
- package/backend-project/routes/stockRoutes.js +14 -0
- package/backend-project/server.js +133 -0
- package/frontend-project/package.json +41 -0
- package/frontend-project/postcss.config.js +6 -0
- package/frontend-project/public/index.html +14 -0
- package/frontend-project/src/App.js +77 -0
- package/frontend-project/src/components/Navbar.js +47 -0
- package/frontend-project/src/index.css +12 -0
- package/frontend-project/src/index.js +11 -0
- package/frontend-project/src/pages/Dashboard.js +122 -0
- package/frontend-project/src/pages/Login.js +88 -0
- package/frontend-project/src/pages/Products.js +208 -0
- package/frontend-project/src/pages/Reports.js +226 -0
- package/frontend-project/src/pages/Sales.js +239 -0
- package/frontend-project/src/pages/Stock.js +99 -0
- package/frontend-project/tailwind.config.js +10 -0
- package/package.json +17 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
const Reports = () => {
|
|
5
|
+
const [activeTab, setActiveTab] = useState('sales');
|
|
6
|
+
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
|
7
|
+
const [salesReport, setSalesReport] = useState([]);
|
|
8
|
+
const [salesDetails, setSalesDetails] = useState([]);
|
|
9
|
+
const [stockReport, setStockReport] = useState([]);
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (activeTab === 'sales') {
|
|
14
|
+
fetchSalesReport();
|
|
15
|
+
} else {
|
|
16
|
+
fetchStockReport();
|
|
17
|
+
}
|
|
18
|
+
}, [activeTab, selectedDate]);
|
|
19
|
+
|
|
20
|
+
const fetchSalesReport = async () => {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
try {
|
|
23
|
+
const [reportRes, detailsRes] = await Promise.all([
|
|
24
|
+
axios.get(`http://localhost:5000/api/sales/report/daily?date=${selectedDate}`),
|
|
25
|
+
axios.get(`http://localhost:5000/api/sales/report/daily/details?date=${selectedDate}`)
|
|
26
|
+
]);
|
|
27
|
+
setSalesReport(reportRes.data);
|
|
28
|
+
setSalesDetails(detailsRes.data);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('Error fetching sales report:', error);
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const fetchStockReport = async () => {
|
|
37
|
+
setLoading(true);
|
|
38
|
+
try {
|
|
39
|
+
const response = await axios.get('http://localhost:5000/api/stock/report/status');
|
|
40
|
+
setStockReport(response.data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error fetching stock report:', error);
|
|
43
|
+
} finally {
|
|
44
|
+
setLoading(false);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleDateChange = (e) => {
|
|
49
|
+
setSelectedDate(e.target.value);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const printReport = () => {
|
|
53
|
+
window.print();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<h1 className="text-3xl font-bold text-gray-800 mb-8">Reports</h1>
|
|
59
|
+
|
|
60
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
61
|
+
<div className="flex space-x-4 mb-6">
|
|
62
|
+
<button
|
|
63
|
+
onClick={() => setActiveTab('sales')}
|
|
64
|
+
className={`px-6 py-2 rounded-lg transition ${
|
|
65
|
+
activeTab === 'sales' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
|
66
|
+
}`}
|
|
67
|
+
>
|
|
68
|
+
Daily Sales Report
|
|
69
|
+
</button>
|
|
70
|
+
<button
|
|
71
|
+
onClick={() => setActiveTab('stock')}
|
|
72
|
+
className={`px-6 py-2 rounded-lg transition ${
|
|
73
|
+
activeTab === 'stock' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
|
|
74
|
+
}`}
|
|
75
|
+
>
|
|
76
|
+
Stock Status Report
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{activeTab === 'sales' && (
|
|
81
|
+
<div>
|
|
82
|
+
<div className="flex items-center space-x-4 mb-6">
|
|
83
|
+
<div>
|
|
84
|
+
<label className="block text-gray-700 font-medium mb-2">Select Date</label>
|
|
85
|
+
<input
|
|
86
|
+
type="date"
|
|
87
|
+
value={selectedDate}
|
|
88
|
+
onChange={handleDateChange}
|
|
89
|
+
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
<button
|
|
93
|
+
onClick={printReport}
|
|
94
|
+
className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition"
|
|
95
|
+
>
|
|
96
|
+
Print Report
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
{loading ? (
|
|
101
|
+
<div className="text-center">Loading...</div>
|
|
102
|
+
) : (
|
|
103
|
+
<div>
|
|
104
|
+
<h2 className="text-xl font-bold text-gray-800 mb-4">Daily Sales Summary - {selectedDate}</h2>
|
|
105
|
+
|
|
106
|
+
{salesReport.length > 0 ? (
|
|
107
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
|
108
|
+
<div className="bg-blue-50 rounded-lg p-4">
|
|
109
|
+
<p className="text-gray-600">Total Sales</p>
|
|
110
|
+
<p className="text-2xl font-bold text-blue-600">{salesReport[0].TotalSales}</p>
|
|
111
|
+
</div>
|
|
112
|
+
<div className="bg-green-50 rounded-lg p-4">
|
|
113
|
+
<p className="text-gray-600">Total Quantity</p>
|
|
114
|
+
<p className="text-2xl font-bold text-green-600">{salesReport[0].TotalQuantity}</p>
|
|
115
|
+
</div>
|
|
116
|
+
<div className="bg-purple-50 rounded-lg p-4">
|
|
117
|
+
<p className="text-gray-600">Total Revenue</p>
|
|
118
|
+
<p className="text-2xl font-bold text-purple-600">RWF {parseFloat(salesReport[0].TotalRevenue).toFixed(2)}</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
) : (
|
|
122
|
+
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
|
|
123
|
+
<p className="text-yellow-800">No sales data available for the selected date.</p>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<h3 className="text-lg font-bold text-gray-800 mb-4">Sales Details</h3>
|
|
128
|
+
<div className="overflow-x-auto">
|
|
129
|
+
<table className="w-full">
|
|
130
|
+
<thead>
|
|
131
|
+
<tr className="bg-gray-100">
|
|
132
|
+
<th className="px-4 py-3 text-left">Product</th>
|
|
133
|
+
<th className="px-4 py-3 text-left">Category</th>
|
|
134
|
+
<th className="px-4 py-3 text-left">Qty Sold</th>
|
|
135
|
+
<th className="px-4 py-3 text-left">Unit Price</th>
|
|
136
|
+
<th className="px-4 py-3 text-left">Total</th>
|
|
137
|
+
<th className="px-4 py-3 text-left">Date</th>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
{salesDetails.map((sale) => (
|
|
142
|
+
<tr key={sale.SalesID} className="border-b">
|
|
143
|
+
<td className="px-4 py-3">{sale.ProductName}</td>
|
|
144
|
+
<td className="px-4 py-3">{sale.Category}</td>
|
|
145
|
+
<td className="px-4 py-3">{sale.SoldQuantity}</td>
|
|
146
|
+
<td className="px-4 py-3">{parseFloat(sale.SoldUnitPrice).toFixed(2)}</td>
|
|
147
|
+
<td className="px-4 py-3">{parseFloat(sale.SoldTotalPrice).toFixed(2)}</td>
|
|
148
|
+
<td className="px-4 py-3">{sale.SalesDate}</td>
|
|
149
|
+
</tr>
|
|
150
|
+
))}
|
|
151
|
+
</tbody>
|
|
152
|
+
</table>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
{salesDetails.length === 0 && (
|
|
156
|
+
<div className="text-center py-8 text-gray-500">
|
|
157
|
+
No sales details available for the selected date.
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
{activeTab === 'stock' && (
|
|
166
|
+
<div>
|
|
167
|
+
<div className="flex items-center space-x-4 mb-6">
|
|
168
|
+
<h2 className="text-xl font-bold text-gray-800">Daily Stock Status Report</h2>
|
|
169
|
+
<button
|
|
170
|
+
onClick={printReport}
|
|
171
|
+
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition"
|
|
172
|
+
>
|
|
173
|
+
Print Report
|
|
174
|
+
</button>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
{loading ? (
|
|
178
|
+
<div className="text-center">Loading...</div>
|
|
179
|
+
) : (
|
|
180
|
+
<div>
|
|
181
|
+
<div className="overflow-x-auto">
|
|
182
|
+
<table className="w-full">
|
|
183
|
+
<thead>
|
|
184
|
+
<tr className="bg-gray-100">
|
|
185
|
+
<th className="px-4 py-3 text-left">Product Name</th>
|
|
186
|
+
<th className="px-4 py-3 text-left">Category</th>
|
|
187
|
+
<th className="px-4 py-3 text-left">Stored Quantity</th>
|
|
188
|
+
<th className="px-4 py-3 text-left">Sold Quantity</th>
|
|
189
|
+
<th className="px-4 py-3 text-left">Remaining Quantity</th>
|
|
190
|
+
<th className="px-4 py-3 text-left">Unit Price</th>
|
|
191
|
+
<th className="px-4 py-3 text-left">Total Value</th>
|
|
192
|
+
<th className="px-4 py-3 text-left">Last Updated</th>
|
|
193
|
+
</tr>
|
|
194
|
+
</thead>
|
|
195
|
+
<tbody>
|
|
196
|
+
{stockReport.map((stock) => (
|
|
197
|
+
<tr key={stock.StockID} className="border-b">
|
|
198
|
+
<td className="px-4 py-3 font-medium">{stock.ProductName}</td>
|
|
199
|
+
<td className="px-4 py-3">{stock.Category}</td>
|
|
200
|
+
<td className="px-4 py-3">{stock.StoredQuantity}</td>
|
|
201
|
+
<td className="px-4 py-3">{stock.SoldQuantity}</td>
|
|
202
|
+
<td className="px-4 py-3 font-bold">{stock.RemainingQuantity}</td>
|
|
203
|
+
<td className="px-4 py-3">{parseFloat(stock.UnitPrice).toFixed(2)}</td>
|
|
204
|
+
<td className="px-4 py-3">{parseFloat(stock.TotalPrice).toFixed(2)}</td>
|
|
205
|
+
<td className="px-4 py-3">{new Date(stock.LastUpdated).toLocaleString()}</td>
|
|
206
|
+
</tr>
|
|
207
|
+
))}
|
|
208
|
+
</tbody>
|
|
209
|
+
</table>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{stockReport.length === 0 && (
|
|
213
|
+
<div className="text-center py-8 text-gray-500">
|
|
214
|
+
No stock data available. Add products to generate stock report.
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export default Reports;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
const Sales = () => {
|
|
5
|
+
const [sales, setSales] = useState([]);
|
|
6
|
+
const [products, setProducts] = useState([]);
|
|
7
|
+
const [formData, setFormData] = useState({
|
|
8
|
+
ProductID: '',
|
|
9
|
+
SoldQuantity: '',
|
|
10
|
+
SoldUnitPrice: '',
|
|
11
|
+
SalesDate: new Date().toISOString().split('T')[0]
|
|
12
|
+
});
|
|
13
|
+
const [editingSale, setEditingSale] = useState(null);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
fetchData();
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
const fetchData = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const [salesRes, productsRes] = await Promise.all([
|
|
23
|
+
axios.get('http://localhost:5000/api/sales'),
|
|
24
|
+
axios.get('http://localhost:5000/api/products')
|
|
25
|
+
]);
|
|
26
|
+
setSales(salesRes.data);
|
|
27
|
+
setProducts(productsRes.data);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Error fetching data:', error);
|
|
30
|
+
} finally {
|
|
31
|
+
setLoading(false);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleSubmit = async (e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
try {
|
|
38
|
+
if (editingSale) {
|
|
39
|
+
await axios.put(`http://localhost:5000/api/sales/${editingSale.SalesID}`, formData);
|
|
40
|
+
} else {
|
|
41
|
+
await axios.post('http://localhost:5000/api/sales', formData);
|
|
42
|
+
}
|
|
43
|
+
setFormData({
|
|
44
|
+
ProductID: '',
|
|
45
|
+
SoldQuantity: '',
|
|
46
|
+
SoldUnitPrice: '',
|
|
47
|
+
SalesDate: new Date().toISOString().split('T')[0]
|
|
48
|
+
});
|
|
49
|
+
setEditingSale(null);
|
|
50
|
+
fetchData();
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('Error saving sale:', error);
|
|
53
|
+
alert(error.response?.data?.message || 'Error saving sale');
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleEdit = (sale) => {
|
|
58
|
+
setEditingSale(sale);
|
|
59
|
+
setFormData({
|
|
60
|
+
ProductID: sale.ProductID,
|
|
61
|
+
SoldQuantity: sale.SoldQuantity,
|
|
62
|
+
SoldUnitPrice: sale.SoldUnitPrice,
|
|
63
|
+
SalesDate: sale.SalesDate
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleDelete = async (id) => {
|
|
68
|
+
if (window.confirm('Are you sure you want to delete this sale?')) {
|
|
69
|
+
try {
|
|
70
|
+
await axios.delete(`http://localhost:5000/api/sales/${id}`);
|
|
71
|
+
fetchData();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error('Error deleting sale:', error);
|
|
74
|
+
alert('Error deleting sale');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleChange = (e) => {
|
|
80
|
+
setFormData({ ...formData, [e.target.name]: e.target.value });
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleProductChange = (e) => {
|
|
84
|
+
const productId = e.target.value;
|
|
85
|
+
const product = products.find(p => p.ProductID === parseInt(productId));
|
|
86
|
+
setFormData({
|
|
87
|
+
...formData,
|
|
88
|
+
ProductID: productId,
|
|
89
|
+
SoldUnitPrice: product ? product.UnitPrice : ''
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (loading) {
|
|
94
|
+
return <div className="text-center">Loading...</div>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div>
|
|
99
|
+
<h1 className="text-3xl font-bold text-gray-800 mb-8">Sales Management</h1>
|
|
100
|
+
|
|
101
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
102
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
103
|
+
<h2 className="text-xl font-bold text-gray-800 mb-4">
|
|
104
|
+
{editingSale ? 'Edit Sale' : 'Record New Sale'}
|
|
105
|
+
</h2>
|
|
106
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
107
|
+
<div>
|
|
108
|
+
<label className="block text-gray-700 font-medium mb-2">Product</label>
|
|
109
|
+
<select
|
|
110
|
+
name="ProductID"
|
|
111
|
+
value={formData.ProductID}
|
|
112
|
+
onChange={handleProductChange}
|
|
113
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
114
|
+
required
|
|
115
|
+
>
|
|
116
|
+
<option value="">Select a product</option>
|
|
117
|
+
{products.map((product) => (
|
|
118
|
+
<option key={product.ProductID} value={product.ProductID}>
|
|
119
|
+
{product.ProductName} - {product.Category} (Stock: {product.Quantity})
|
|
120
|
+
</option>
|
|
121
|
+
))}
|
|
122
|
+
</select>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div>
|
|
126
|
+
<label className="block text-gray-700 font-medium mb-2">Sold Quantity</label>
|
|
127
|
+
<input
|
|
128
|
+
type="number"
|
|
129
|
+
name="SoldQuantity"
|
|
130
|
+
value={formData.SoldQuantity}
|
|
131
|
+
onChange={handleChange}
|
|
132
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
133
|
+
required
|
|
134
|
+
min="1"
|
|
135
|
+
/>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<div>
|
|
139
|
+
<label className="block text-gray-700 font-medium mb-2">Sold Unit Price (RWF)</label>
|
|
140
|
+
<input
|
|
141
|
+
type="number"
|
|
142
|
+
name="SoldUnitPrice"
|
|
143
|
+
value={formData.SoldUnitPrice}
|
|
144
|
+
onChange={handleChange}
|
|
145
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
146
|
+
required
|
|
147
|
+
min="0"
|
|
148
|
+
step="0.01"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<div>
|
|
153
|
+
<label className="block text-gray-700 font-medium mb-2">Sales Date</label>
|
|
154
|
+
<input
|
|
155
|
+
type="date"
|
|
156
|
+
name="SalesDate"
|
|
157
|
+
value={formData.SalesDate}
|
|
158
|
+
onChange={handleChange}
|
|
159
|
+
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
160
|
+
required
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<div className="flex space-x-4">
|
|
165
|
+
<button
|
|
166
|
+
type="submit"
|
|
167
|
+
className="flex-1 bg-green-600 text-white py-2 rounded-lg hover:bg-green-700 transition"
|
|
168
|
+
>
|
|
169
|
+
{editingSale ? 'Update Sale' : 'Record Sale'}
|
|
170
|
+
</button>
|
|
171
|
+
{editingSale && (
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
onClick={() => {
|
|
175
|
+
setEditingSale(null);
|
|
176
|
+
setFormData({
|
|
177
|
+
ProductID: '',
|
|
178
|
+
SoldQuantity: '',
|
|
179
|
+
SoldUnitPrice: '',
|
|
180
|
+
SalesDate: new Date().toISOString().split('T')[0]
|
|
181
|
+
});
|
|
182
|
+
}}
|
|
183
|
+
className="flex-1 bg-gray-400 text-white py-2 rounded-lg hover:bg-gray-500 transition"
|
|
184
|
+
>
|
|
185
|
+
Cancel
|
|
186
|
+
</button>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</form>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
193
|
+
<h2 className="text-xl font-bold text-gray-800 mb-4">Sales History</h2>
|
|
194
|
+
<div className="overflow-x-auto max-h-96 overflow-y-auto">
|
|
195
|
+
<table className="w-full">
|
|
196
|
+
<thead className="sticky top-0 bg-white">
|
|
197
|
+
<tr className="bg-gray-100">
|
|
198
|
+
<th className="px-4 py-3 text-left">Product</th>
|
|
199
|
+
<th className="px-4 py-3 text-left">Qty</th>
|
|
200
|
+
<th className="px-4 py-3 text-left">Price</th>
|
|
201
|
+
<th className="px-4 py-3 text-left">Total</th>
|
|
202
|
+
<th className="px-4 py-3 text-left">Date</th>
|
|
203
|
+
<th className="px-4 py-3 text-left">Actions</th>
|
|
204
|
+
</tr>
|
|
205
|
+
</thead>
|
|
206
|
+
<tbody>
|
|
207
|
+
{sales.map((sale) => (
|
|
208
|
+
<tr key={sale.SalesID} className="border-b">
|
|
209
|
+
<td className="px-4 py-3">{sale.ProductName}</td>
|
|
210
|
+
<td className="px-4 py-3">{sale.SoldQuantity}</td>
|
|
211
|
+
<td className="px-4 py-3">{parseFloat(sale.SoldUnitPrice).toFixed(2)}</td>
|
|
212
|
+
<td className="px-4 py-3">{parseFloat(sale.SoldTotalPrice).toFixed(2)}</td>
|
|
213
|
+
<td className="px-4 py-3">{sale.SalesDate}</td>
|
|
214
|
+
<td className="px-4 py-3">
|
|
215
|
+
<button
|
|
216
|
+
onClick={() => handleEdit(sale)}
|
|
217
|
+
className="text-blue-600 hover:text-blue-800 mr-2"
|
|
218
|
+
>
|
|
219
|
+
Edit
|
|
220
|
+
</button>
|
|
221
|
+
<button
|
|
222
|
+
onClick={() => handleDelete(sale.SalesID)}
|
|
223
|
+
className="text-red-600 hover:text-red-800"
|
|
224
|
+
>
|
|
225
|
+
Delete
|
|
226
|
+
</button>
|
|
227
|
+
</td>
|
|
228
|
+
</tr>
|
|
229
|
+
))}
|
|
230
|
+
</tbody>
|
|
231
|
+
</table>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export default Sales;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
const Stock = () => {
|
|
5
|
+
const [stockStatus, setStockStatus] = useState([]);
|
|
6
|
+
const [loading, setLoading] = useState(true);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
fetchStockStatus();
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
const fetchStockStatus = async () => {
|
|
13
|
+
try {
|
|
14
|
+
const response = await axios.get('http://localhost:5000/api/stock');
|
|
15
|
+
setStockStatus(response.data);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error('Error fetching stock status:', error);
|
|
18
|
+
} finally {
|
|
19
|
+
setLoading(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (loading) {
|
|
24
|
+
return <div className="text-center">Loading...</div>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
<h1 className="text-3xl font-bold text-gray-800 mb-8">Stock Status</h1>
|
|
30
|
+
|
|
31
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
32
|
+
<h2 className="text-xl font-bold text-gray-800 mb-4">Current Stock Overview</h2>
|
|
33
|
+
<div className="overflow-x-auto">
|
|
34
|
+
<table className="w-full">
|
|
35
|
+
<thead>
|
|
36
|
+
<tr className="bg-gray-100">
|
|
37
|
+
<th className="px-4 py-3 text-left">Product Name</th>
|
|
38
|
+
<th className="px-4 py-3 text-left">Category</th>
|
|
39
|
+
<th className="px-4 py-3 text-left">Available Qty</th>
|
|
40
|
+
<th className="px-4 py-3 text-left">Sold Qty</th>
|
|
41
|
+
<th className="px-4 py-3 text-left">Remaining Qty</th>
|
|
42
|
+
<th className="px-4 py-3 text-left">Status</th>
|
|
43
|
+
</tr>
|
|
44
|
+
</thead>
|
|
45
|
+
<tbody>
|
|
46
|
+
{stockStatus.map((stock) => (
|
|
47
|
+
<tr key={stock.StockID} className="border-b">
|
|
48
|
+
<td className="px-4 py-3 font-medium">{stock.ProductName}</td>
|
|
49
|
+
<td className="px-4 py-3">{stock.Category}</td>
|
|
50
|
+
<td className="px-4 py-3">{stock.AvailableQuantity}</td>
|
|
51
|
+
<td className="px-4 py-3">{stock.SoldQuantity}</td>
|
|
52
|
+
<td className="px-4 py-3 font-bold">{stock.RemainingQuantity}</td>
|
|
53
|
+
<td className="px-4 py-3">
|
|
54
|
+
{stock.RemainingQuantity === 0 ? (
|
|
55
|
+
<span className="px-3 py-1 bg-red-100 text-red-800 rounded-full text-sm">Out of Stock</span>
|
|
56
|
+
) : stock.RemainingQuantity < 10 ? (
|
|
57
|
+
<span className="px-3 py-1 bg-yellow-100 text-yellow-800 rounded-full text-sm">Low Stock</span>
|
|
58
|
+
) : (
|
|
59
|
+
<span className="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm">In Stock</span>
|
|
60
|
+
)}
|
|
61
|
+
</td>
|
|
62
|
+
</tr>
|
|
63
|
+
))}
|
|
64
|
+
</tbody>
|
|
65
|
+
</table>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{stockStatus.length === 0 && (
|
|
69
|
+
<div className="text-center py-8 text-gray-500">
|
|
70
|
+
No stock data available. Add products to see stock status.
|
|
71
|
+
</div>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
|
76
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
77
|
+
<h3 className="text-lg font-bold text-gray-800 mb-2">Total Products</h3>
|
|
78
|
+
<p className="text-3xl font-bold text-blue-600">{stockStatus.length}</p>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
82
|
+
<h3 className="text-lg font-bold text-gray-800 mb-2">Low Stock Items</h3>
|
|
83
|
+
<p className="text-3xl font-bold text-yellow-600">
|
|
84
|
+
{stockStatus.filter(s => s.RemainingQuantity < 10 && s.RemainingQuantity > 0).length}
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div className="bg-white rounded-lg shadow-md p-6">
|
|
89
|
+
<h3 className="text-lg font-bold text-gray-800 mb-2">Out of Stock</h3>
|
|
90
|
+
<p className="text-3xl font-bold text-red-600">
|
|
91
|
+
{stockStatus.filter(s => s.RemainingQuantity === 0).length}
|
|
92
|
+
</p>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default Stock;
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@20052507/dab-enterprise-bws",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "DAB Enterprise Ltd - Business Web Solution (BWS)",
|
|
5
|
+
"main": "backend-project/server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node backend-project/server.js",
|
|
8
|
+
"start-frontend": "cd frontend-project && npm start",
|
|
9
|
+
"install-all": "cd backend-project && npm install && cd ../frontend-project && npm install"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"dab-enterprise",
|
|
13
|
+
"bws",
|
|
14
|
+
"sales-management"
|
|
15
|
+
],
|
|
16
|
+
"license": "ISC"
|
|
17
|
+
}
|