inventrack 3.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.
Files changed (55) hide show
  1. package/README.md +25 -0
  2. package/api/index.js +13 -0
  3. package/backend/README.md +35 -0
  4. package/backend/data/db.json +1239 -0
  5. package/backend/package-lock.json +532 -0
  6. package/backend/package.json +8 -0
  7. package/frontend/README.md +22 -0
  8. package/frontend/assets/Icon.png +0 -0
  9. package/frontend/assets/IconSort.png +0 -0
  10. package/frontend/assets/activity-1.png +0 -0
  11. package/frontend/assets/activity-2.png +0 -0
  12. package/frontend/assets/activity-3.png +0 -0
  13. package/frontend/assets/activity-4.png +0 -0
  14. package/frontend/assets/card-icon-1.png +0 -0
  15. package/frontend/assets/card-icon-2.png +0 -0
  16. package/frontend/assets/card-icon-3.png +0 -0
  17. package/frontend/assets/card-icon-4.png +0 -0
  18. package/frontend/assets/login.png +0 -0
  19. package/frontend/assets/logo.png +0 -0
  20. package/frontend/categories.html +143 -0
  21. package/frontend/css/all.min.css +9 -0
  22. package/frontend/css/bootstrap.min.css +6 -0
  23. package/frontend/css/categories.css +359 -0
  24. package/frontend/css/dashboard.css +373 -0
  25. package/frontend/css/inventoryInsights.css +308 -0
  26. package/frontend/css/inventoryOverview.css +353 -0
  27. package/frontend/css/orders.css +632 -0
  28. package/frontend/css/products.css +364 -0
  29. package/frontend/css/signin.css +120 -0
  30. package/frontend/css/style.css +282 -0
  31. package/frontend/css/suppliers.css +136 -0
  32. package/frontend/dashboard.html +160 -0
  33. package/frontend/index.html +124 -0
  34. package/frontend/inventoryInsights.html +182 -0
  35. package/frontend/inventoryOverview.html +187 -0
  36. package/frontend/js/api.js +55 -0
  37. package/frontend/js/auth.js +70 -0
  38. package/frontend/js/bootstrap.bundle.min.js +7 -0
  39. package/frontend/js/categories.js +356 -0
  40. package/frontend/js/dashboard.js +341 -0
  41. package/frontend/js/inventoryInsights.js +396 -0
  42. package/frontend/js/inventoryOverview.js +503 -0
  43. package/frontend/js/orders.js +662 -0
  44. package/frontend/js/products.js +650 -0
  45. package/frontend/js/suppliers.js +535 -0
  46. package/frontend/js/utils.js +234 -0
  47. package/frontend/orders.html +216 -0
  48. package/frontend/products.html +152 -0
  49. package/frontend/suppliers.html +175 -0
  50. package/frontend/webfonts/fa-brands-400.woff2 +0 -0
  51. package/frontend/webfonts/fa-regular-400.woff2 +0 -0
  52. package/frontend/webfonts/fa-solid-900.woff2 +0 -0
  53. package/frontend/webfonts/fa-v4compatibility.woff2 +0 -0
  54. package/package.json +38 -0
  55. package/vercel.json +18 -0
@@ -0,0 +1,341 @@
1
+ renderNavbar("Dashboard");
2
+ renderFooter();
3
+
4
+ const tableBody = document.getElementById("tableBody");
5
+ const totalProducts = document.getElementById("totalProducts");
6
+ const lowStockItems = document.getElementById("lowStockItems");
7
+ const totalSuppliers = document.getElementById("totalSuppliers");
8
+ const inventoryValue = document.getElementById("inventoryValue");
9
+
10
+ const summaryCards = document.querySelector(".summary-cards");
11
+ const chartContainer = document.querySelector(".inventory-chart .container");
12
+ const activitiesContainer = document.querySelector(".activities");
13
+ const tableFooter = document.querySelector(".table-footer");
14
+
15
+ const state = {
16
+ page: 1,
17
+ limit: 5,
18
+ totalCount: 0
19
+ };
20
+
21
+ async function renderDashboard() {
22
+ try {
23
+ const productsResponse = await getData("products");
24
+ const categoriesResponse = await getData("categories");
25
+ const suppliersResponse = await getData("suppliers");
26
+ const stockMovementsResponse = await getData("stockMovements");
27
+
28
+ const products = productsResponse.data;
29
+ const categories = categoriesResponse.data;
30
+ const suppliers = suppliersResponse.data;
31
+ const stockMovements = stockMovementsResponse.data;
32
+
33
+ renderSummary(products, suppliers);
34
+ renderChart(products, categories);
35
+ renderRecentActivity(products, suppliers, stockMovements);
36
+ renderLowStockTable(products, categories);
37
+
38
+ } catch (error) {
39
+ console.error("Failed to render dashboard:", error);
40
+ renderDashboardError();
41
+ }
42
+ }
43
+
44
+ function renderSummary(products, suppliers) {
45
+ const totalProductsCount = products.length;
46
+
47
+ const lowStockCount = products.filter(function (product) {
48
+ return product.status === "low_stock" || product.status === "out_of_stock";
49
+ }).length;
50
+
51
+ const totalSuppliersCount = suppliers.length;
52
+
53
+ const totalInventoryValue = products.reduce(function (total, product) {
54
+ return total + (Number(product.price) * Number(product.quantity));
55
+ }, 0);
56
+
57
+ totalProducts.textContent = totalProductsCount;
58
+ lowStockItems.textContent = lowStockCount;
59
+ totalSuppliers.textContent = totalSuppliersCount;
60
+ inventoryValue.innerHTML = `$${formatNumber(totalInventoryValue)}`;
61
+ }
62
+
63
+ function renderChart(products, categories) {
64
+ if (!chartContainer) return;
65
+
66
+ const categoryTotals = categories.map(function (category) {
67
+ const totalQty = products
68
+ .filter(function (product) {
69
+ return String(product.categoryId) === String(category.id);
70
+ })
71
+ .reduce(function (sum, product) {
72
+ return sum + Number(product.quantity);
73
+ }, 0);
74
+
75
+ return {
76
+ name: category.name,
77
+ quantity: totalQty
78
+ };
79
+ });
80
+
81
+ const maxQty = Math.max(
82
+ ...categoryTotals.map(function (item) {
83
+ return item.quantity;
84
+ }),
85
+ 1
86
+ );
87
+
88
+ chartContainer.innerHTML = categoryTotals
89
+ .map(function (item) {
90
+ const height = Math.max((item.quantity / maxQty) * 180, 10);
91
+
92
+ return `
93
+ <div class="chart-col d-flex flex-column justify-content-end align-items-center" style="height: 300px;">
94
+ <div class="level" style="height:${height}px;"></div>
95
+ <small style="margin-top: 8px; text-align: center; line-height: 1.2; min-height: 30px; display: flex; align-items: flex-start; justify-content: center;">
96
+ ${item.name}
97
+ </small>
98
+ </div>
99
+ `;
100
+ })
101
+ .join("");
102
+ }
103
+
104
+ function renderRecentActivity(products, suppliers, stockMovements) {
105
+ if (!activitiesContainer) return;
106
+ if (!stockMovements.length) {
107
+ activitiesContainer.innerHTML = `
108
+ <p>No recent activity available.</p>
109
+ `;
110
+ return;
111
+ }
112
+
113
+ const activities = stockMovements
114
+ .slice(-4)
115
+ .reverse()
116
+ .map(function (movement, index) {
117
+
118
+ const icons = ["fa-box", "fa-truck", "fa-layer-group", "fa-triangle-exclamation"];
119
+ const iconClasses = ["icon-purple", "icon-green", "icon-yellow", "icon-red"];
120
+
121
+ return {
122
+ iconClass: iconClasses[index % iconClasses.length],
123
+ icon: icons[index % icons.length],
124
+ text: movement.name || movement.productName,
125
+ meta: `${movement.createdAt} by ${movement.createdBy}`
126
+ };
127
+ });
128
+
129
+ activitiesContainer.innerHTML = activities
130
+ .map(function (activity) {
131
+ return `
132
+ <div class="container d-flex align-items-start">
133
+ <div class="${activity.iconClass} icon d-flex align-items-center justify-content-center">
134
+ <i class="fa-solid ${activity.icon}"></i>
135
+ </div>
136
+ <div class="activity-caption">
137
+ <p>
138
+ ${activity.text}<br>
139
+ <span>${activity.meta}</span>
140
+ </p>
141
+ </div>
142
+ </div>
143
+ `;
144
+ })
145
+ .join("");
146
+ }
147
+
148
+ function renderLowStockTable(products, categories) {
149
+ const flaggedProducts = products.filter(function (product) {
150
+ return product.status === "low_stock" || product.status === "out_of_stock";
151
+ });
152
+
153
+ state.totalCount = flaggedProducts.length;
154
+
155
+ const start = (state.page - 1) * state.limit;
156
+ const end = start + state.limit;
157
+ const paginatedProducts = flaggedProducts.slice(start, end);
158
+
159
+ if (!paginatedProducts.length) {
160
+ tableBody.innerHTML = `
161
+ <tr>
162
+ <td colspan="6" class="text-center">No low stock items found</td>
163
+ </tr>
164
+ `;
165
+
166
+ renderLowStockFooter(0, flaggedProducts.length);
167
+ return;
168
+ }
169
+
170
+ tableBody.innerHTML = paginatedProducts
171
+ .map(function (product) {
172
+ const category = categories.find(function (cat) {
173
+ return String(cat.id) === String(product.categoryId);
174
+ });
175
+
176
+ const categoryName = category ? category.name : "Unknown";
177
+ const stockClass = getStockClass(product.quantity, product.minStock);
178
+ const statusInfo = getStatusInfo(product.status, product.quantity, product.minStock);
179
+ const productIcon = getProductIcon(product.name, categoryName);
180
+
181
+ return `
182
+ <tr>
183
+ <td>
184
+ <div class="product-cell">
185
+ <div class="product-icon">
186
+ <i class="fa-solid ${productIcon}"></i>
187
+ </div>
188
+ <div class="product-info">
189
+ <h6>${product.name}</h6>
190
+ </div>
191
+ </div>
192
+ </td>
193
+ <td>${categoryName}</td>
194
+ <td class="${stockClass}">${product.quantity}</td>
195
+ <td>${product.minStock}</td>
196
+ <td><span class="status ${statusInfo.className}">${statusInfo.text}</span></td>
197
+ <td><button class="reorder-btn" data-id="${product.id}">Reorder</button></td>
198
+ </tr>
199
+ `;
200
+ })
201
+ .join("");
202
+
203
+ renderLowStockFooter(paginatedProducts.length, flaggedProducts.length);
204
+ attachReorderEvents();
205
+ }
206
+
207
+ function renderLowStockFooter(currentCount, totalCount) {
208
+ if (!tableFooter) return;
209
+
210
+ tableFooter.innerHTML = `
211
+ <span>Showing ${currentCount} of ${totalCount} flagged items</span>
212
+ <div class="pagination" id="pagination"></div>
213
+ `;
214
+
215
+ const paginationContainer = document.getElementById("pagination");
216
+ renderPagination(paginationContainer, state, rerenderLowStockOnly);
217
+ }
218
+
219
+ async function rerenderLowStockOnly() {
220
+ try {
221
+ const productsResponse = await getData("products");
222
+ const categoriesResponse = await getData("categories");
223
+
224
+ renderLowStockTable(productsResponse.data, categoriesResponse.data);
225
+ } catch (error) {
226
+ console.error("Failed to rerender low stock table:", error);
227
+ }
228
+ }
229
+
230
+ function getStatusInfo(status, quantity, minStock) {
231
+ if (status === "out_of_stock" || quantity === 0) {
232
+ return {
233
+ text: "Out of Stock",
234
+ className: "out"
235
+ };
236
+ }
237
+
238
+ if (status === "low_stock" || quantity <= minStock) {
239
+ if (quantity <= Math.ceil(minStock / 2)) {
240
+ return {
241
+ text: "Critical",
242
+ className: "critical"
243
+ };
244
+ }
245
+
246
+ return {
247
+ text: "Low Stock",
248
+ className: "low"
249
+ };
250
+ }
251
+
252
+ return {
253
+ text: "In Stock",
254
+ className: "low"
255
+ };
256
+ }
257
+
258
+ function getProductIcon(productName, categoryName) {
259
+ const name = `${productName} ${categoryName}`.toLowerCase();
260
+
261
+ if (name.includes("keyboard")) return "fa-keyboard";
262
+ if (name.includes("mouse")) return "fa-computer-mouse";
263
+ if (name.includes("headset") || name.includes("headphones")) return "fa-headphones";
264
+ if (name.includes("monitor") || name.includes("display")) return "fa-desktop";
265
+ if (name.includes("laptop")) return "fa-laptop";
266
+ if (name.includes("phone") || name.includes("smartphone")) return "fa-mobile-screen";
267
+ if (name.includes("tablet")) return "fa-tablet-screen-button";
268
+ if (name.includes("camera") || name.includes("webcam")) return "fa-camera";
269
+ if (name.includes("printer")) return "fa-print";
270
+ if (name.includes("router")) return "fa-wifi";
271
+ if (name.includes("switch")) return "fa-network-wired";
272
+ if (name.includes("storage") || name.includes("ssd") || name.includes("hdd") || name.includes("flash")) return "fa-hard-drive";
273
+ if (name.includes("chair")) return "fa-chair";
274
+ if (name.includes("desk") || name.includes("table")) return "fa-table";
275
+ if (name.includes("cable")) return "fa-plug";
276
+ if (name.includes("power")) return "fa-bolt";
277
+ return "fa-box";
278
+ }
279
+
280
+ function attachReorderEvents() {
281
+ const reorderButtons = document.querySelectorAll(".reorder-btn");
282
+ reorderButtons.forEach(function (button) {
283
+ button.addEventListener("click", function () {
284
+ const productId = button.dataset.id;
285
+ alert(`Reorder action for product ID: ${productId}`);
286
+ });
287
+ });
288
+ }
289
+
290
+ // ^ Static Events
291
+ // function attachStaticEvents() {
292
+ // const bulkOrderBtn = document.getElementById("bulkOrderBtn");
293
+ // const viewActivityBtn = document.getElementById("viewActivityBtn");
294
+ // if (bulkOrderBtn) {
295
+ // bulkOrderBtn.addEventListener("click", function () {
296
+ // alert("Bulk order feature will be connected later.");
297
+ // });
298
+ // }
299
+ // if (viewActivityBtn) {
300
+ // viewActivityBtn.addEventListener("click", function () {
301
+ // alert("View all activity feature will be connected later.");
302
+ // });
303
+ // }
304
+ // }
305
+
306
+ function renderDashboardError() {
307
+ if (summaryCards) {
308
+ summaryCards.innerHTML = `
309
+ <div class="card w-100">
310
+ <h4>Failed to load dashboard data</h4>
311
+ <p>Please make sure json-server is running on port 3000.</p>
312
+ </div>
313
+ `;
314
+ }
315
+
316
+ if (chartContainer) {
317
+ chartContainer.innerHTML = `<p class="text-center w-100">Chart unavailable</p>`;
318
+ }
319
+
320
+ if (activitiesContainer) {
321
+ activitiesContainer.innerHTML = `<p>No recent activity available.</p>`;
322
+ }
323
+
324
+ if (tableBody) {
325
+ tableBody.innerHTML = `
326
+ <tr>
327
+ <td colspan="6" class="text-center">Unable to load low stock items</td>
328
+ </tr>
329
+ `;
330
+ }
331
+
332
+ if (tableFooter) {
333
+ tableFooter.innerHTML = "";
334
+ }
335
+ }
336
+
337
+ function formatNumber(number) {
338
+ return Number(number).toLocaleString();
339
+ }
340
+
341
+ renderDashboard();