react-node-app 1.0.0
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/.gitignore +6 -0
- package/README.md +0 -0
- package/backend-project/config/db.js +13 -0
- package/backend-project/controllers/authController.js +63 -0
- package/backend-project/controllers/productController.js +113 -0
- package/backend-project/controllers/reportController.js +93 -0
- package/backend-project/controllers/saleController.js +91 -0
- package/backend-project/controllers/stockController.js +126 -0
- package/backend-project/middleware/auth.js +8 -0
- package/backend-project/models/Product.js +40 -0
- package/backend-project/models/Sale.js +38 -0
- package/backend-project/models/StockStatus.js +35 -0
- package/backend-project/models/User.js +24 -0
- package/backend-project/package.json +24 -0
- package/backend-project/routes/authRoutes.js +9 -0
- package/backend-project/routes/productRoutes.js +9 -0
- package/backend-project/routes/reportRoutes.js +14 -0
- package/backend-project/routes/saleRoutes.js +8 -0
- package/backend-project/routes/stockRoutes.js +18 -0
- package/backend-project/server.js +55 -0
- package/backend-project/utils/seed.js +44 -0
- package/frontend-project/index.html +13 -0
- package/frontend-project/package.json +32 -0
- package/frontend-project/postcss.config.js +6 -0
- package/frontend-project/src/App.jsx +7 -0
- package/frontend-project/src/components/LoadingSpinner.jsx +8 -0
- package/frontend-project/src/components/Pagination.jsx +74 -0
- package/frontend-project/src/components/ProtectedRoute.jsx +22 -0
- package/frontend-project/src/components/SearchBar.jsx +16 -0
- package/frontend-project/src/components/StatCard.jsx +20 -0
- package/frontend-project/src/context/AuthContext.jsx +60 -0
- package/frontend-project/src/index.css +3 -0
- package/frontend-project/src/layouts/MainLayout.jsx +12 -0
- package/frontend-project/src/layouts/Sidebar.jsx +96 -0
- package/frontend-project/src/main.jsx +16 -0
- package/frontend-project/src/pages/Dashboard.jsx +184 -0
- package/frontend-project/src/pages/Login.jsx +111 -0
- package/frontend-project/src/pages/Products.jsx +192 -0
- package/frontend-project/src/pages/Reports.jsx +320 -0
- package/frontend-project/src/pages/Sales.jsx +150 -0
- package/frontend-project/src/pages/StockStatus.jsx +306 -0
- package/frontend-project/src/routes/AppRoutes.jsx +66 -0
- package/frontend-project/src/services/api.js +52 -0
- package/frontend-project/src/services/authService.js +5 -0
- package/frontend-project/src/services/productService.js +4 -0
- package/frontend-project/src/services/reportService.js +5 -0
- package/frontend-project/src/services/saleService.js +3 -0
- package/frontend-project/src/services/stockService.js +7 -0
- package/frontend-project/tailwind.config.js +8 -0
- package/frontend-project/vite.config.js +9 -0
- package/package.json +59 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
import Swal from 'sweetalert2';
|
|
4
|
+
import {
|
|
5
|
+
HiOutlinePlus,
|
|
6
|
+
HiOutlinePencil,
|
|
7
|
+
HiOutlineTrash,
|
|
8
|
+
} from 'react-icons/hi2';
|
|
9
|
+
import { getProducts } from '../services/productService';
|
|
10
|
+
import {
|
|
11
|
+
createStockStatus,
|
|
12
|
+
getStockStatuses,
|
|
13
|
+
updateStockStatus,
|
|
14
|
+
deleteStockStatus,
|
|
15
|
+
} from '../services/stockService';
|
|
16
|
+
import SearchBar from '../components/SearchBar';
|
|
17
|
+
import Pagination from '../components/Pagination';
|
|
18
|
+
import LoadingSpinner from '../components/LoadingSpinner';
|
|
19
|
+
|
|
20
|
+
export default function StockStatus() {
|
|
21
|
+
const [stockStatuses, setStockStatuses] = useState([]);
|
|
22
|
+
const [products, setProducts] = useState([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [search, setSearch] = useState('');
|
|
25
|
+
const [page, setPage] = useState(1);
|
|
26
|
+
const [pages, setPages] = useState(1);
|
|
27
|
+
const [showForm, setShowForm] = useState(false);
|
|
28
|
+
const [editing, setEditing] = useState(null);
|
|
29
|
+
const [submitting, setSubmitting] = useState(false);
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
register,
|
|
33
|
+
handleSubmit,
|
|
34
|
+
reset,
|
|
35
|
+
setValue,
|
|
36
|
+
watch,
|
|
37
|
+
formState: { errors },
|
|
38
|
+
} = useForm();
|
|
39
|
+
|
|
40
|
+
const availableQuantity = watch('availableQuantity');
|
|
41
|
+
const soldQuantity = watch('soldQuantity');
|
|
42
|
+
const remainingQuantity =
|
|
43
|
+
parseInt(availableQuantity || 0) - parseInt(soldQuantity || 0);
|
|
44
|
+
|
|
45
|
+
const fetchData = async () => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
try {
|
|
48
|
+
const [stockRes, prodRes] = await Promise.all([
|
|
49
|
+
getStockStatuses({ search, page, limit: 10 }),
|
|
50
|
+
getProducts({ limit: 100 }),
|
|
51
|
+
]);
|
|
52
|
+
setStockStatuses(stockRes.data.stockStatuses);
|
|
53
|
+
setPages(stockRes.data.pages);
|
|
54
|
+
setProducts(prodRes.data.products);
|
|
55
|
+
} catch {
|
|
56
|
+
// handled by interceptor
|
|
57
|
+
} finally {
|
|
58
|
+
setLoading(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
fetchData();
|
|
64
|
+
}, [page, search]);
|
|
65
|
+
|
|
66
|
+
const onSubmit = async (data) => {
|
|
67
|
+
setSubmitting(true);
|
|
68
|
+
try {
|
|
69
|
+
if (editing) {
|
|
70
|
+
await updateStockStatus(editing._id, data);
|
|
71
|
+
Swal.fire({ icon: 'success', title: 'Updated!', timer: 1500, showConfirmButton: false, toast: true, position: 'top-end' });
|
|
72
|
+
} else {
|
|
73
|
+
await createStockStatus(data);
|
|
74
|
+
Swal.fire({ icon: 'success', title: 'Created!', timer: 1500, showConfirmButton: false, toast: true, position: 'top-end' });
|
|
75
|
+
}
|
|
76
|
+
reset();
|
|
77
|
+
setShowForm(false);
|
|
78
|
+
setEditing(null);
|
|
79
|
+
fetchData();
|
|
80
|
+
} catch {
|
|
81
|
+
// handled by interceptor
|
|
82
|
+
} finally {
|
|
83
|
+
setSubmitting(false);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const handleEdit = (item) => {
|
|
88
|
+
setEditing(item);
|
|
89
|
+
setValue('productId', item.productId?._id || item.productId);
|
|
90
|
+
setValue('availableQuantity', item.availableQuantity);
|
|
91
|
+
setValue('soldQuantity', item.soldQuantity);
|
|
92
|
+
setValue('generatedAt', item.generatedAt ? item.generatedAt.split('T')[0] : '');
|
|
93
|
+
setShowForm(true);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleDelete = async (id) => {
|
|
97
|
+
const result = await Swal.fire({
|
|
98
|
+
title: 'Are you sure?',
|
|
99
|
+
text: "You won't be able to revert this!",
|
|
100
|
+
icon: 'warning',
|
|
101
|
+
showCancelButton: true,
|
|
102
|
+
confirmButtonColor: '#1e293b',
|
|
103
|
+
cancelButtonColor: '#dc2626',
|
|
104
|
+
confirmButtonText: 'Yes, delete it!',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (result.isConfirmed) {
|
|
108
|
+
try {
|
|
109
|
+
await deleteStockStatus(id);
|
|
110
|
+
Swal.fire({ icon: 'success', title: 'Deleted!', timer: 1500, showConfirmButton: false, toast: true, position: 'top-end' });
|
|
111
|
+
fetchData();
|
|
112
|
+
} catch {
|
|
113
|
+
// handled by interceptor
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const cancelForm = () => {
|
|
119
|
+
reset();
|
|
120
|
+
setShowForm(false);
|
|
121
|
+
setEditing(null);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="space-y-6">
|
|
126
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
127
|
+
<div>
|
|
128
|
+
<h1 className="text-2xl font-bold text-slate-800">Stock Status</h1>
|
|
129
|
+
<p className="text-slate-500 text-sm mt-1">Monitor stock levels and inventory</p>
|
|
130
|
+
</div>
|
|
131
|
+
<button
|
|
132
|
+
onClick={() => setShowForm(true)}
|
|
133
|
+
className="flex items-center gap-2 px-4 py-2.5 bg-slate-800 text-white rounded-lg text-sm font-medium hover:bg-slate-700 transition-colors"
|
|
134
|
+
>
|
|
135
|
+
<HiOutlinePlus className="w-5 h-5" />
|
|
136
|
+
Add Stock Status
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{showForm && (
|
|
141
|
+
<div className="bg-white rounded-xl border border-gray-200 p-6 shadow-sm">
|
|
142
|
+
<h3 className="text-sm font-semibold text-slate-700 mb-4">
|
|
143
|
+
{editing ? 'Edit Stock Status' : 'New Stock Status'}
|
|
144
|
+
</h3>
|
|
145
|
+
<form onSubmit={handleSubmit(onSubmit)} className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
146
|
+
<div>
|
|
147
|
+
<label className="block text-sm font-medium text-slate-700 mb-1">Product</label>
|
|
148
|
+
<select
|
|
149
|
+
{...register('productId', { required: 'Product is required' })}
|
|
150
|
+
className="w-full px-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-800 focus:border-transparent bg-white"
|
|
151
|
+
disabled={!!editing}
|
|
152
|
+
>
|
|
153
|
+
<option value="">Select product</option>
|
|
154
|
+
{products.map((p) => (
|
|
155
|
+
<option key={p._id} value={p._id}>
|
|
156
|
+
{p.productName}
|
|
157
|
+
</option>
|
|
158
|
+
))}
|
|
159
|
+
</select>
|
|
160
|
+
{errors.productId && <p className="text-red-500 text-xs mt-1">{errors.productId.message}</p>}
|
|
161
|
+
</div>
|
|
162
|
+
<div>
|
|
163
|
+
<label className="block text-sm font-medium text-slate-700 mb-1">Available Quantity</label>
|
|
164
|
+
<input
|
|
165
|
+
type="number"
|
|
166
|
+
step="1"
|
|
167
|
+
{...register('availableQuantity', {
|
|
168
|
+
required: 'Required',
|
|
169
|
+
min: { value: 0, message: 'Cannot be negative' },
|
|
170
|
+
})}
|
|
171
|
+
className="w-full px-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-800 focus:border-transparent"
|
|
172
|
+
/>
|
|
173
|
+
{errors.availableQuantity && <p className="text-red-500 text-xs mt-1">{errors.availableQuantity.message}</p>}
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<label className="block text-sm font-medium text-slate-700 mb-1">Sold Quantity</label>
|
|
177
|
+
<input
|
|
178
|
+
type="number"
|
|
179
|
+
step="1"
|
|
180
|
+
{...register('soldQuantity', {
|
|
181
|
+
required: 'Required',
|
|
182
|
+
min: { value: 0, message: 'Cannot be negative' },
|
|
183
|
+
})}
|
|
184
|
+
className="w-full px-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-800 focus:border-transparent"
|
|
185
|
+
/>
|
|
186
|
+
{errors.soldQuantity && <p className="text-red-500 text-xs mt-1">{errors.soldQuantity.message}</p>}
|
|
187
|
+
</div>
|
|
188
|
+
<div>
|
|
189
|
+
<label className="block text-sm font-medium text-slate-700 mb-1">Remaining Quantity</label>
|
|
190
|
+
<input
|
|
191
|
+
value={remainingQuantity}
|
|
192
|
+
readOnly
|
|
193
|
+
className="w-full px-4 py-2.5 border border-gray-200 rounded-lg text-sm bg-slate-50 text-slate-500"
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
<div>
|
|
197
|
+
<label className="block text-sm font-medium text-slate-700 mb-1">Date</label>
|
|
198
|
+
<input
|
|
199
|
+
type="date"
|
|
200
|
+
{...register('generatedAt')}
|
|
201
|
+
className="w-full px-4 py-2.5 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-800 focus:border-transparent"
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
<div className="flex items-end gap-2">
|
|
205
|
+
<button
|
|
206
|
+
type="submit"
|
|
207
|
+
disabled={submitting}
|
|
208
|
+
className="px-6 py-2.5 bg-slate-800 text-white rounded-lg text-sm font-medium hover:bg-slate-700 transition-colors disabled:opacity-50"
|
|
209
|
+
>
|
|
210
|
+
{submitting ? 'Saving...' : editing ? 'Update' : 'Save'}
|
|
211
|
+
</button>
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
onClick={cancelForm}
|
|
215
|
+
className="px-6 py-2.5 border border-gray-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors"
|
|
216
|
+
>
|
|
217
|
+
Cancel
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
</form>
|
|
221
|
+
</div>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
<div className="bg-white rounded-xl border border-gray-200 shadow-sm">
|
|
225
|
+
<div className="p-4 border-b border-gray-200">
|
|
226
|
+
<SearchBar value={search} onChange={(v) => { setSearch(v); setPage(1); }} placeholder="Search by product name..." />
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{loading ? (
|
|
230
|
+
<LoadingSpinner message="Loading stock status..." />
|
|
231
|
+
) : (
|
|
232
|
+
<>
|
|
233
|
+
<div className="overflow-x-auto">
|
|
234
|
+
<table className="w-full text-sm">
|
|
235
|
+
<thead>
|
|
236
|
+
<tr className="border-b border-gray-200 bg-slate-50">
|
|
237
|
+
<th className="text-left p-4 text-slate-500 font-medium">Product</th>
|
|
238
|
+
<th className="text-left p-4 text-slate-500 font-medium">Stored Qty</th>
|
|
239
|
+
<th className="text-left p-4 text-slate-500 font-medium">Sold Qty</th>
|
|
240
|
+
<th className="text-left p-4 text-slate-500 font-medium">Remaining</th>
|
|
241
|
+
<th className="text-left p-4 text-slate-500 font-medium">Date</th>
|
|
242
|
+
<th className="text-left p-4 text-slate-500 font-medium">Actions</th>
|
|
243
|
+
</tr>
|
|
244
|
+
</thead>
|
|
245
|
+
<tbody>
|
|
246
|
+
{stockStatuses.length > 0 ? (
|
|
247
|
+
stockStatuses.map((item) => (
|
|
248
|
+
<tr key={item._id} className="border-b border-gray-100 hover:bg-slate-50">
|
|
249
|
+
<td className="p-4 font-medium text-slate-700">
|
|
250
|
+
{item.productId?.productName || 'N/A'}
|
|
251
|
+
</td>
|
|
252
|
+
<td className="p-4 text-slate-600">{item.availableQuantity}</td>
|
|
253
|
+
<td className="p-4 text-slate-600">{item.soldQuantity}</td>
|
|
254
|
+
<td className="p-4">
|
|
255
|
+
<span
|
|
256
|
+
className={`font-medium ${
|
|
257
|
+
item.remainingQuantity <= 0
|
|
258
|
+
? 'text-red-600'
|
|
259
|
+
: item.remainingQuantity < 10
|
|
260
|
+
? 'text-amber-600'
|
|
261
|
+
: 'text-green-600'
|
|
262
|
+
}`}
|
|
263
|
+
>
|
|
264
|
+
{item.remainingQuantity}
|
|
265
|
+
</span>
|
|
266
|
+
</td>
|
|
267
|
+
<td className="p-4 text-slate-500">
|
|
268
|
+
{new Date(item.generatedAt).toLocaleDateString()}
|
|
269
|
+
</td>
|
|
270
|
+
<td className="p-4">
|
|
271
|
+
<div className="flex items-center gap-2">
|
|
272
|
+
<button
|
|
273
|
+
onClick={() => handleEdit(item)}
|
|
274
|
+
className="p-1.5 text-slate-500 hover:text-slate-800 hover:bg-slate-100 rounded-lg transition-colors"
|
|
275
|
+
>
|
|
276
|
+
<HiOutlinePencil className="w-4 h-4" />
|
|
277
|
+
</button>
|
|
278
|
+
<button
|
|
279
|
+
onClick={() => handleDelete(item._id)}
|
|
280
|
+
className="p-1.5 text-red-500 hover:text-red-700 hover:bg-red-50 rounded-lg transition-colors"
|
|
281
|
+
>
|
|
282
|
+
<HiOutlineTrash className="w-4 h-4" />
|
|
283
|
+
</button>
|
|
284
|
+
</div>
|
|
285
|
+
</td>
|
|
286
|
+
</tr>
|
|
287
|
+
))
|
|
288
|
+
) : (
|
|
289
|
+
<tr>
|
|
290
|
+
<td colSpan={6} className="p-8 text-center text-slate-400">
|
|
291
|
+
No stock status records found
|
|
292
|
+
</td>
|
|
293
|
+
</tr>
|
|
294
|
+
)}
|
|
295
|
+
</tbody>
|
|
296
|
+
</table>
|
|
297
|
+
</div>
|
|
298
|
+
<div className="p-4">
|
|
299
|
+
<Pagination page={page} pages={pages} onPageChange={setPage} />
|
|
300
|
+
</div>
|
|
301
|
+
</>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
2
|
+
import { useAuth } from '../context/AuthContext';
|
|
3
|
+
import ProtectedRoute from '../components/ProtectedRoute';
|
|
4
|
+
import Login from '../pages/Login';
|
|
5
|
+
import Dashboard from '../pages/Dashboard';
|
|
6
|
+
import Products from '../pages/Products';
|
|
7
|
+
import Sales from '../pages/Sales';
|
|
8
|
+
import StockStatus from '../pages/StockStatus';
|
|
9
|
+
import Reports from '../pages/Reports';
|
|
10
|
+
|
|
11
|
+
export default function AppRoutes() {
|
|
12
|
+
const { user, loading } = useAuth();
|
|
13
|
+
|
|
14
|
+
if (loading) return null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Routes>
|
|
18
|
+
<Route
|
|
19
|
+
path="/login"
|
|
20
|
+
element={user ? <Navigate to="/dashboard" replace /> : <Login />}
|
|
21
|
+
/>
|
|
22
|
+
<Route
|
|
23
|
+
path="/dashboard"
|
|
24
|
+
element={
|
|
25
|
+
<ProtectedRoute>
|
|
26
|
+
<Dashboard />
|
|
27
|
+
</ProtectedRoute>
|
|
28
|
+
}
|
|
29
|
+
/>
|
|
30
|
+
<Route
|
|
31
|
+
path="/products"
|
|
32
|
+
element={
|
|
33
|
+
<ProtectedRoute>
|
|
34
|
+
<Products />
|
|
35
|
+
</ProtectedRoute>
|
|
36
|
+
}
|
|
37
|
+
/>
|
|
38
|
+
<Route
|
|
39
|
+
path="/sales"
|
|
40
|
+
element={
|
|
41
|
+
<ProtectedRoute>
|
|
42
|
+
<Sales />
|
|
43
|
+
</ProtectedRoute>
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
<Route
|
|
47
|
+
path="/stock-status"
|
|
48
|
+
element={
|
|
49
|
+
<ProtectedRoute>
|
|
50
|
+
<StockStatus />
|
|
51
|
+
</ProtectedRoute>
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
<Route
|
|
55
|
+
path="/reports"
|
|
56
|
+
element={
|
|
57
|
+
<ProtectedRoute>
|
|
58
|
+
<Reports />
|
|
59
|
+
</ProtectedRoute>
|
|
60
|
+
}
|
|
61
|
+
/>
|
|
62
|
+
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
|
63
|
+
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
|
64
|
+
</Routes>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import Swal from 'sweetalert2';
|
|
3
|
+
|
|
4
|
+
const api = axios.create({
|
|
5
|
+
baseURL: 'http://localhost:5000/api',
|
|
6
|
+
withCredentials: true,
|
|
7
|
+
headers: {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
api.interceptors.response.use(
|
|
13
|
+
(response) => response,
|
|
14
|
+
(error) => {
|
|
15
|
+
if (error.response) {
|
|
16
|
+
if (error.response.status === 401) {
|
|
17
|
+
const isAuthPage = window.location.pathname === '/login';
|
|
18
|
+
if (!isAuthPage) {
|
|
19
|
+
window.location.href = '/login';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const message =
|
|
24
|
+
error.response.data?.message || 'An unexpected error occurred';
|
|
25
|
+
|
|
26
|
+
if (error.response.status !== 401) {
|
|
27
|
+
Swal.fire({
|
|
28
|
+
icon: 'error',
|
|
29
|
+
title: 'Error',
|
|
30
|
+
text: message,
|
|
31
|
+
toast: true,
|
|
32
|
+
position: 'top-end',
|
|
33
|
+
timer: 3000,
|
|
34
|
+
showConfirmButton: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
} else if (error.request) {
|
|
38
|
+
Swal.fire({
|
|
39
|
+
icon: 'error',
|
|
40
|
+
title: 'Connection Error',
|
|
41
|
+
text: 'Unable to connect to the server. Please check your connection.',
|
|
42
|
+
toast: true,
|
|
43
|
+
position: 'top-end',
|
|
44
|
+
timer: 3000,
|
|
45
|
+
showConfirmButton: false,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return Promise.reject(error);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export default api;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import api from './api';
|
|
2
|
+
|
|
3
|
+
export const getDashboardData = () => api.get('/reports/dashboard');
|
|
4
|
+
export const getDailySalesReport = (params) => api.get('/reports/daily-sales', { params });
|
|
5
|
+
export const getStockStatusReport = (params) => api.get('/reports/stock-status', { params });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import api from './api';
|
|
2
|
+
|
|
3
|
+
export const createStockStatus = (data) => api.post('/stock', data);
|
|
4
|
+
export const getStockStatuses = (params) => api.get('/stock', { params });
|
|
5
|
+
export const getStockStatus = (id) => api.get(`/stock/${id}`);
|
|
6
|
+
export const updateStockStatus = (id, data) => api.put(`/stock/${id}`, data);
|
|
7
|
+
export const deleteStockStatus = (id) => api.delete(`/stock/${id}`);
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-node-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Business Web Solution (BWS) for DAB Enterprise Ltd - Full-stack inventory, sales, and stock management system with React frontend and Express/MongoDB backend",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bws",
|
|
7
|
+
"dab-enterprise",
|
|
8
|
+
"inventory",
|
|
9
|
+
"sales",
|
|
10
|
+
"stock-management",
|
|
11
|
+
"business-solution",
|
|
12
|
+
"react",
|
|
13
|
+
"express",
|
|
14
|
+
"mongodb"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "DAB Enterprise Ltd",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/ishconnlab/DABEnterprise.git"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"backend-project/server.js",
|
|
27
|
+
"backend-project/package.json",
|
|
28
|
+
"backend-project/config/",
|
|
29
|
+
"backend-project/controllers/",
|
|
30
|
+
"backend-project/middleware/",
|
|
31
|
+
"backend-project/models/",
|
|
32
|
+
"backend-project/routes/",
|
|
33
|
+
"backend-project/utils/",
|
|
34
|
+
"frontend-project/src/",
|
|
35
|
+
"frontend-project/public/",
|
|
36
|
+
"frontend-project/index.html",
|
|
37
|
+
"frontend-project/package.json",
|
|
38
|
+
"frontend-project/vite.config.js",
|
|
39
|
+
"frontend-project/postcss.config.js",
|
|
40
|
+
"frontend-project/tailwind.config.js",
|
|
41
|
+
"README.md",
|
|
42
|
+
".gitignore"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"install:backend": "cd backend-project && npm install",
|
|
46
|
+
"install:frontend": "cd frontend-project && npm install",
|
|
47
|
+
"postinstall": "",
|
|
48
|
+
"dev:backend": "cd backend-project && npm run dev",
|
|
49
|
+
"dev:frontend": "cd frontend-project && npm run dev",
|
|
50
|
+
"dev": "concurrently \"npm run dev:backend\" \"npm run dev:frontend\"",
|
|
51
|
+
"build": "cd frontend-project && npm run build",
|
|
52
|
+
"start:backend": "cd backend-project && npm start",
|
|
53
|
+
"seed": "cd backend-project && npm run seed",
|
|
54
|
+
"start": "npm run seed && npm run start:backend"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"concurrently": "^8.2.2"
|
|
58
|
+
}
|
|
59
|
+
}
|