Update the existing Executive Commercial Summary dashboard (do not rebuild or change layout). Keep all current filters and KPIs exactly as they are. Apply the following improvements and adjustments based on leadership feedback to make the dashboard more actionable while maintaining a clean, balanced layout.
Browse filesReplace the Daily Operational Throughput chart with a new chart titled “Cumulative Progress Toward Period Goal.” This should display cumulative totals for Ordered, Shipped, and Produced pounds across the selected time range, along with additional dashed lines for cumulative Forecast and cumulative Budget to show pacing toward goals. The tooltip for each date should show cumulative totals for all five series as well as the variance from Forecast and Budget. Keep a clear legend and consistent line styling (solid for actuals, dashed for targets). This chart should provide a broader, period-level view of operational throughput rather than day-to-day noise.
Next to this, keep the Performance vs Forecast chart but update it so that the Budget line is always included alongside Actual and Forecast. Do not add Budget as a selectable metric—display all three together. The chart should continue allowing drilldown by metric (Ordered, Shipped, Produced). Ensure consistent axes and formatting with the new cumulative chart.
Improve the Inventory vs Order Backlog section by adding a new table next to it titled “Product Balance Watchlist (Next 5 Days).” This table should display products that are projected to face constraints within the next five days. Include columns for Product, FG Inventory (today), Backlog (total next 5 days), Scheduled Production (next 5 days), and Projected Shortfall (Backlog − [FG Inventory + Scheduled Production]). Only show products where the shortfall is greater than zero. Add simple risk-level color cues based on shortfall severity and make each row expandable or hoverable to reveal a 5-day mini-trend for that product. Clicking a product filters the main Inventory vs Backlog chart to that product.
Next to the Order Lifecycle Analysis chart, add a table titled “Customer Order Pipeline Summary.” This table should list each customer with columns for Open, Shipped, Invoiced, Cancelled, and % Open = Open ÷ (Open + Shipped + Invoiced + Cancelled). Sort customers by % Open descending by default. Clicking a customer filters the lifecycle chart to that customer, and clicking chart elements filters the table.
Add a compact “At-Risk Orders” section below these lifecycle visuals. On the left, include a stacked bar chart where the x-axis is Promise Date and each bar shows late open orders segmented into 1–3 days late, 4–7 days late, and 8+ days late using light amber to red. On the right, include a collapsible table of those late orders showing Order #, Customer, Promise Date, Days Late, Current Status, Order Pounds, and Branch. Clicking any bar filters the table; default the table collapsed to save space.
Keep the Detailed Data View at the bottom unchanged, but make it respond to filtering from any of the new tables and charts. Ensure spacing and card proportions remain consistent so the dashboard remains well-balanced on a single scroll view.
- index.html +269 -133
|
@@ -263,23 +263,22 @@
|
|
| 263 |
</div>
|
| 264 |
<!-- First Chart Row -->
|
| 265 |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 266 |
-
<!--
|
| 267 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 268 |
<div class="flex justify-between items-center mb-4">
|
| 269 |
-
<h2 class="text-lg font-semibold text-gray-800">
|
| 270 |
<div class="flex space-x-2">
|
| 271 |
<button class="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">Reset</button>
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
<div class="chart-container">
|
| 275 |
-
<canvas id="
|
| 276 |
</div>
|
| 277 |
</div>
|
| 278 |
-
|
| 279 |
-
<!-- Performance vs Forecast -->
|
| 280 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 281 |
<div class="flex justify-between items-center mb-4">
|
| 282 |
-
<h2 class="text-lg font-semibold text-gray-800">Performance vs Forecast</h2>
|
| 283 |
<div class="flex space-x-2">
|
| 284 |
<select id="metricToggle" class="text-xs border border-gray-300 rounded px-2 py-1">
|
| 285 |
<option>Ordered</option>
|
|
@@ -293,24 +292,57 @@
|
|
| 293 |
<canvas id="forecastChart"></canvas>
|
| 294 |
</div>
|
| 295 |
</div>
|
| 296 |
-
|
| 297 |
|
| 298 |
<!-- Second Chart Row -->
|
| 299 |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 300 |
-
<!-- Inventory
|
| 301 |
-
<div class="
|
| 302 |
-
<div class="
|
| 303 |
-
<
|
| 304 |
-
|
| 305 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
</div>
|
| 307 |
</div>
|
| 308 |
-
<div class="
|
| 309 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
-
|
| 313 |
-
<!-- Financial Performance Breakdown -->
|
| 314 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 315 |
<div class="flex justify-between items-center mb-4">
|
| 316 |
<h2 class="text-lg font-semibold text-gray-800">Financial Performance Breakdown</h2>
|
|
@@ -327,74 +359,121 @@
|
|
| 327 |
</div>
|
| 328 |
</div>
|
| 329 |
</div>
|
| 330 |
-
|
| 331 |
<!-- Order Lifecycle Analysis -->
|
| 332 |
-
<div class="
|
| 333 |
-
<div class="
|
| 334 |
-
<
|
| 335 |
-
|
| 336 |
-
<
|
| 337 |
-
|
| 338 |
-
</div>
|
| 339 |
-
<div class="chart-container">
|
| 340 |
-
<canvas id="lifecycleChart"></canvas>
|
| 341 |
-
</div>
|
| 342 |
-
</div>
|
| 343 |
-
<!-- At-Risk Orders -->
|
| 344 |
-
<div class="bg-white p-4 rounded-lg shadow-md mb-6">
|
| 345 |
-
<h2 class="text-lg font-semibold text-gray-800 mb-4">At-Risk Orders</h2>
|
| 346 |
-
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
| 347 |
-
<!-- Chart -->
|
| 348 |
-
<div>
|
| 349 |
-
<div class="chart-container">
|
| 350 |
-
<canvas id="atRiskChart"></canvas>
|
| 351 |
</div>
|
| 352 |
</div>
|
| 353 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
<div class="overflow-x-auto">
|
| 355 |
<table class="min-w-full divide-y divide-gray-200">
|
| 356 |
<thead class="bg-gray-50">
|
| 357 |
<tr>
|
| 358 |
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order #</th>
|
| 359 |
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
|
| 360 |
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
| 361 |
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
| 362 |
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
| 363 |
-
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
| 364 |
</tr>
|
| 365 |
</thead>
|
| 366 |
<tbody class="bg-white divide-y divide-gray-200">
|
| 367 |
-
<tr>
|
| 368 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1001</td>
|
| 369 |
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Acme Corp</td>
|
| 370 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
| 371 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-
|
| 372 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
| 373 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
|
|
|
| 374 |
</tr>
|
| 375 |
-
<tr>
|
| 376 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1002</td>
|
| 377 |
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Beta LLC</td>
|
| 378 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
| 379 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-
|
| 380 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
| 381 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
| 382 |
-
|
| 383 |
-
<tr>
|
| 384 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1003</td>
|
| 385 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Gamma Inc</td>
|
| 386 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">2023-05-30</td>
|
| 387 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-red-900 font-medium">9</td>
|
| 388 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Open</td>
|
| 389 |
-
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">12,700</td>
|
| 390 |
</tr>
|
| 391 |
</tbody>
|
| 392 |
</table>
|
| 393 |
</div>
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
-
|
| 397 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
| 399 |
<button id="toggleTable" class="w-full px-4 py-3 bg-gray-100 text-left font-medium text-gray-800 hover:bg-gray-200 flex justify-between items-center">
|
| 400 |
<span>Detailed Data View</span>
|
|
@@ -455,84 +534,131 @@
|
|
| 455 |
document.getElementById('endDate').valueAsDate = today;
|
| 456 |
|
| 457 |
feather.replace();
|
| 458 |
-
|
| 459 |
-
// Toggle table visibility
|
| 460 |
const toggleTable = document.getElementById('toggleTable');
|
| 461 |
const tableContent = document.getElementById('tableContent');
|
| 462 |
-
const
|
| 463 |
|
| 464 |
toggleTable.addEventListener('click', function() {
|
| 465 |
tableContent.classList.toggle('expanded');
|
| 466 |
const isExpanded = tableContent.classList.contains('expanded');
|
| 467 |
-
|
| 468 |
});
|
| 469 |
|
| 470 |
-
|
| 471 |
-
const
|
|
|
|
| 472 |
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
}
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
}
|
| 517 |
},
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
display: false
|
| 521 |
-
}
|
| 522 |
}
|
| 523 |
},
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
}
|
|
|
|
| 530 |
}
|
| 531 |
}
|
| 532 |
}
|
| 533 |
}
|
| 534 |
-
}
|
| 535 |
-
|
| 536 |
// Forecast Chart
|
| 537 |
const forecastCtx = document.getElementById('forecastChart').getContext('2d');
|
| 538 |
const forecastChart = new Chart(forecastCtx, {
|
|
@@ -545,6 +671,7 @@ feather.replace();
|
|
| 545 |
data: Array.from({length: 30}, () => Math.floor(Math.random() * 50000) + 30000),
|
| 546 |
borderColor: '#001f3f',
|
| 547 |
backgroundColor: 'transparent',
|
|
|
|
| 548 |
tension: 0.1
|
| 549 |
},
|
| 550 |
{
|
|
@@ -552,12 +679,22 @@ feather.replace();
|
|
| 552 |
data: Array.from({length: 30}, () => Math.floor(Math.random() * 48000) + 32000),
|
| 553 |
borderColor: '#001f3f',
|
| 554 |
backgroundColor: 'transparent',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
borderDash: [5, 5],
|
| 556 |
tension: 0.1
|
| 557 |
}
|
| 558 |
]
|
| 559 |
},
|
| 560 |
-
|
| 561 |
responsive: true,
|
| 562 |
maintainAspectRatio: false,
|
| 563 |
scales: {
|
|
@@ -876,23 +1013,23 @@ const lifecycleChart = new Chart(lifecycleCtx, {
|
|
| 876 |
}
|
| 877 |
}
|
| 878 |
});
|
| 879 |
-
|
| 880 |
// Update forecast chart when metric changes
|
| 881 |
document.getElementById('metricToggle').addEventListener('change', function() {
|
| 882 |
const metric = this.value;
|
| 883 |
|
| 884 |
-
// Update dataset labels and data based on selected metric
|
| 885 |
forecastChart.data.datasets[0].label = 'Actual ' + metric;
|
| 886 |
forecastChart.data.datasets[1].label = 'Forecast ' + metric;
|
|
|
|
| 887 |
|
| 888 |
// Generate new random data for the selected metric
|
| 889 |
forecastChart.data.datasets[0].data = Array.from({length: 30}, () => Math.floor(Math.random() * 50000) + 30000);
|
| 890 |
forecastChart.data.datasets[1].data = Array.from({length: 30}, () => Math.floor(Math.random() * 48000) + 32000);
|
|
|
|
| 891 |
|
| 892 |
forecastChart.update();
|
| 893 |
});
|
| 894 |
-
|
| 895 |
-
// Update financial chart when view changes
|
| 896 |
document.getElementById('financialViewToggle').addEventListener('change', function() {
|
| 897 |
const view = this.value;
|
| 898 |
|
|
@@ -916,7 +1053,6 @@ const lifecycleChart = new Chart(lifecycleCtx, {
|
|
| 916 |
|
| 917 |
financialChart.update();
|
| 918 |
});
|
| 919 |
-
|
| 920 |
// Date range filter handler
|
| 921 |
const dateInputs = document.querySelectorAll('#startDate, #endDate');
|
| 922 |
dateInputs.forEach(input => {
|
|
@@ -929,7 +1065,7 @@ const lifecycleChart = new Chart(lifecycleCtx, {
|
|
| 929 |
console.log(`Date range changed: ${startDate} to ${endDate}`);
|
| 930 |
|
| 931 |
// Update all charts with new date range
|
| 932 |
-
|
| 933 |
forecastChart.update();
|
| 934 |
inventoryChart.update();
|
| 935 |
financialChart.update();
|
|
@@ -938,7 +1074,7 @@ const lifecycleChart = new Chart(lifecycleCtx, {
|
|
| 938 |
}
|
| 939 |
});
|
| 940 |
});
|
| 941 |
-
|
| 942 |
</script>
|
| 943 |
</body>
|
| 944 |
</html>
|
|
|
|
| 263 |
</div>
|
| 264 |
<!-- First Chart Row -->
|
| 265 |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 266 |
+
<!-- Cumulative Progress Toward Period Goal -->
|
| 267 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 268 |
<div class="flex justify-between items-center mb-4">
|
| 269 |
+
<h2 class="text-lg font-semibold text-gray-800">Cumulative Progress Toward Period Goal</h2>
|
| 270 |
<div class="flex space-x-2">
|
| 271 |
<button class="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">Reset</button>
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
<div class="chart-container">
|
| 275 |
+
<canvas id="cumulativeChart"></canvas>
|
| 276 |
</div>
|
| 277 |
</div>
|
| 278 |
+
<!-- Performance vs Forecast & Budget -->
|
|
|
|
| 279 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 280 |
<div class="flex justify-between items-center mb-4">
|
| 281 |
+
<h2 class="text-lg font-semibold text-gray-800">Performance vs Forecast & Budget</h2>
|
| 282 |
<div class="flex space-x-2">
|
| 283 |
<select id="metricToggle" class="text-xs border border-gray-300 rounded px-2 py-1">
|
| 284 |
<option>Ordered</option>
|
|
|
|
| 292 |
<canvas id="forecastChart"></canvas>
|
| 293 |
</div>
|
| 294 |
</div>
|
| 295 |
+
</div>
|
| 296 |
|
| 297 |
<!-- Second Chart Row -->
|
| 298 |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 299 |
+
<!-- Inventory Management -->
|
| 300 |
+
<div class="grid grid-cols-1 gap-4">
|
| 301 |
+
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 302 |
+
<div class="flex justify-between items-center mb-4">
|
| 303 |
+
<h2 class="text-lg font-semibold text-gray-800">Inventory vs Order Backlog</h2>
|
| 304 |
+
<div class="flex space-x-2">
|
| 305 |
+
<button class="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">Reset</button>
|
| 306 |
+
</div>
|
| 307 |
+
</div>
|
| 308 |
+
<div class="chart-container">
|
| 309 |
+
<canvas id="inventoryChart"></canvas>
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
+
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 313 |
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">Product Balance Watchlist (Next 5 Days)</h2>
|
| 314 |
+
<div class="overflow-x-auto">
|
| 315 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 316 |
+
<thead class="bg-gray-50">
|
| 317 |
+
<tr>
|
| 318 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Product</th>
|
| 319 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">FG Inventory</th>
|
| 320 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Backlog</th>
|
| 321 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Scheduled Prod</th>
|
| 322 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Projected Shortfall</th>
|
| 323 |
+
</tr>
|
| 324 |
+
</thead>
|
| 325 |
+
<tbody class="bg-white divide-y divide-gray-200">
|
| 326 |
+
<tr class="hover:bg-gray-50 cursor-pointer" onclick="filterInventoryChart('Premium')">
|
| 327 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Premium</td>
|
| 328 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">12,500</td>
|
| 329 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">18,200</td>
|
| 330 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">4,800</td>
|
| 331 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-red-700">800</td>
|
| 332 |
+
</tr>
|
| 333 |
+
<tr class="hover:bg-gray-50 cursor-pointer" onclick="filterInventoryChart('Custom')">
|
| 334 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Custom</td>
|
| 335 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">8,700</td>
|
| 336 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">11,500</td>
|
| 337 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">3,200</td>
|
| 338 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-amber-600">400</td>
|
| 339 |
+
</tr>
|
| 340 |
+
</tbody>
|
| 341 |
+
</table>
|
| 342 |
+
</div>
|
| 343 |
</div>
|
| 344 |
</div>
|
| 345 |
+
<!-- Financial Performance Breakdown -->
|
|
|
|
| 346 |
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 347 |
<div class="flex justify-between items-center mb-4">
|
| 348 |
<h2 class="text-lg font-semibold text-gray-800">Financial Performance Breakdown</h2>
|
|
|
|
| 359 |
</div>
|
| 360 |
</div>
|
| 361 |
</div>
|
|
|
|
| 362 |
<!-- Order Lifecycle Analysis -->
|
| 363 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
| 364 |
+
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 365 |
+
<div class="flex justify-between items-center mb-4">
|
| 366 |
+
<h2 class="text-lg font-semibold text-gray-800">Order Lifecycle Analysis</h2>
|
| 367 |
+
<div class="flex space-x-2">
|
| 368 |
+
<button class="px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">Reset</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
</div>
|
| 370 |
</div>
|
| 371 |
+
<div class="chart-container">
|
| 372 |
+
<canvas id="lifecycleChart"></canvas>
|
| 373 |
+
</div>
|
| 374 |
+
</div>
|
| 375 |
+
<div class="bg-white p-4 rounded-lg shadow-md">
|
| 376 |
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">Customer Order Pipeline Summary</h2>
|
| 377 |
<div class="overflow-x-auto">
|
| 378 |
<table class="min-w-full divide-y divide-gray-200">
|
| 379 |
<thead class="bg-gray-50">
|
| 380 |
<tr>
|
|
|
|
| 381 |
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
|
| 382 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Open</th>
|
| 383 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Shipped</th>
|
| 384 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoiced</th>
|
| 385 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Cancelled</th>
|
| 386 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">% Open</th>
|
| 387 |
</tr>
|
| 388 |
</thead>
|
| 389 |
<tbody class="bg-white divide-y divide-gray-200">
|
| 390 |
+
<tr class="hover:bg-gray-50 cursor-pointer" onclick="filterLifecycleChart('Acme Corp')">
|
|
|
|
| 391 |
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Acme Corp</td>
|
| 392 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">25</td>
|
| 393 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">18</td>
|
| 394 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">15</td>
|
| 395 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">2</td>
|
| 396 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-amber-600">42%</td>
|
| 397 |
</tr>
|
| 398 |
+
<tr class="hover:bg-gray-50 cursor-pointer" onclick="filterLifecycleChart('Beta LLC')">
|
|
|
|
| 399 |
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Beta LLC</td>
|
| 400 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">12</td>
|
| 401 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">22</td>
|
| 402 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">18</td>
|
| 403 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">1</td>
|
| 404 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-blue-600">23%</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
</tr>
|
| 406 |
</tbody>
|
| 407 |
</table>
|
| 408 |
</div>
|
| 409 |
</div>
|
| 410 |
</div>
|
| 411 |
+
<!-- At-Risk Orders -->
|
| 412 |
+
<div class="bg-white p-4 rounded-lg shadow-md mb-6">
|
| 413 |
+
<h2 class="text-lg font-semibold text-gray-800 mb-4">At-Risk Orders</h2>
|
| 414 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
| 415 |
+
<!-- Chart -->
|
| 416 |
+
<div>
|
| 417 |
+
<div class="chart-container">
|
| 418 |
+
<canvas id="atRiskChart"></canvas>
|
| 419 |
+
</div>
|
| 420 |
+
</div>
|
| 421 |
+
<!-- Collapsible Table -->
|
| 422 |
+
<div>
|
| 423 |
+
<button id="toggleRiskTable" class="w-full px-4 py-2 bg-gray-100 text-left font-medium text-gray-800 hover:bg-gray-200 flex justify-between items-center mb-2">
|
| 424 |
+
<span>Late Orders Detail View</span>
|
| 425 |
+
<i data-feather="chevron-down" class="transform transition-transform duration-200"></i>
|
| 426 |
+
</button>
|
| 427 |
+
<div id="riskTableContent" class="collapsible-content">
|
| 428 |
+
<div class="overflow-x-auto">
|
| 429 |
+
<table class="min-w-full divide-y divide-gray-200">
|
| 430 |
+
<thead class="bg-gray-50">
|
| 431 |
+
<tr>
|
| 432 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order #</th>
|
| 433 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Customer</th>
|
| 434 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Promise Date</th>
|
| 435 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Days Late</th>
|
| 436 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
|
| 437 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Pounds</th>
|
| 438 |
+
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Branch</th>
|
| 439 |
+
</tr>
|
| 440 |
+
</thead>
|
| 441 |
+
<tbody class="bg-white divide-y divide-gray-200">
|
| 442 |
+
<tr>
|
| 443 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1001</td>
|
| 444 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Acme Corp</td>
|
| 445 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">2023-06-05</td>
|
| 446 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-red-600 font-medium">3</td>
|
| 447 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Open</td>
|
| 448 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">5,200</td>
|
| 449 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">North</td>
|
| 450 |
+
</tr>
|
| 451 |
+
<tr>
|
| 452 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1002</td>
|
| 453 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Beta LLC</td>
|
| 454 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">2023-06-03</td>
|
| 455 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-red-800 font-medium">5</td>
|
| 456 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Processing</td>
|
| 457 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">8,400</td>
|
| 458 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">South</td>
|
| 459 |
+
</tr>
|
| 460 |
+
<tr>
|
| 461 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">ORD-1003</td>
|
| 462 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Gamma Inc</td>
|
| 463 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">2023-05-30</td>
|
| 464 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-red-900 font-medium">9</td>
|
| 465 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">Open</td>
|
| 466 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">12,700</td>
|
| 467 |
+
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">West</td>
|
| 468 |
+
</tr>
|
| 469 |
+
</tbody>
|
| 470 |
+
</table>
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
</div>
|
| 474 |
+
</div>
|
| 475 |
+
</div>
|
| 476 |
+
<!-- Detailed Data View -->
|
| 477 |
<div class="bg-white rounded-lg shadow-md overflow-hidden">
|
| 478 |
<button id="toggleTable" class="w-full px-4 py-3 bg-gray-100 text-left font-medium text-gray-800 hover:bg-gray-200 flex justify-between items-center">
|
| 479 |
<span>Detailed Data View</span>
|
|
|
|
| 534 |
document.getElementById('endDate').valueAsDate = today;
|
| 535 |
|
| 536 |
feather.replace();
|
| 537 |
+
// Toggle table visibilities
|
|
|
|
| 538 |
const toggleTable = document.getElementById('toggleTable');
|
| 539 |
const tableContent = document.getElementById('tableContent');
|
| 540 |
+
const tableChevron = toggleTable.querySelector('i');
|
| 541 |
|
| 542 |
toggleTable.addEventListener('click', function() {
|
| 543 |
tableContent.classList.toggle('expanded');
|
| 544 |
const isExpanded = tableContent.classList.contains('expanded');
|
| 545 |
+
tableChevron.style.transform = isExpanded ? 'rotate(180deg)' : 'rotate(0)';
|
| 546 |
});
|
| 547 |
|
| 548 |
+
const toggleRiskTable = document.getElementById('toggleRiskTable');
|
| 549 |
+
const riskTableContent = document.getElementById('riskTableContent');
|
| 550 |
+
const riskChevron = toggleRiskTable.querySelector('i');
|
| 551 |
|
| 552 |
+
toggleRiskTable.addEventListener('click', function() {
|
| 553 |
+
riskTableContent.classList.toggle('expanded');
|
| 554 |
+
const isExpanded = riskTableContent.classList.contains('expanded');
|
| 555 |
+
riskChevron.style.transform = isExpanded ? 'rotate(180deg)' : 'rotate(0)';
|
| 556 |
+
});
|
| 557 |
+
|
| 558 |
+
// Filter functions
|
| 559 |
+
function filterInventoryChart(product) {
|
| 560 |
+
console.log(`Filtering inventory chart for ${product}`);
|
| 561 |
+
// In a real app, this would update the chart data
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
function filterLifecycleChart(customer) {
|
| 565 |
+
console.log(`Filtering lifecycle chart for ${customer}`);
|
| 566 |
+
// In a real app, this would update the chart data
|
| 567 |
+
}
|
| 568 |
+
// Sample data for charts (in a real app, this would come from an API)
|
| 569 |
+
const dates = Array.from({length: 30}, (_, i) => `Day ${i+1}`);
|
| 570 |
+
// Cumulative Progress Chart
|
| 571 |
+
const cumulativeCtx = document.getElementById('cumulativeChart').getContext('2d');
|
| 572 |
+
const cumulativeChart = new Chart(cumulativeCtx, {
|
| 573 |
+
type: 'line',
|
| 574 |
+
data: {
|
| 575 |
+
labels: dates,
|
| 576 |
+
datasets: [
|
| 577 |
+
{
|
| 578 |
+
label: 'Cumulative Ordered',
|
| 579 |
+
data: Array.from({length: 30}, (_, i) => 30000 * (i + 1) + Math.floor(Math.random() * 100000)),
|
| 580 |
+
borderColor: '#001f3f',
|
| 581 |
+
backgroundColor: 'transparent',
|
| 582 |
+
borderWidth: 2,
|
| 583 |
+
tension: 0.1
|
| 584 |
+
},
|
| 585 |
+
{
|
| 586 |
+
label: 'Cumulative Shipped',
|
| 587 |
+
data: Array.from({length: 30}, (_, i) => 28000 * (i + 1) + Math.floor(Math.random() * 90000)),
|
| 588 |
+
borderColor: '#0074D9',
|
| 589 |
+
backgroundColor: 'transparent',
|
| 590 |
+
borderWidth: 2,
|
| 591 |
+
tension: 0.1
|
| 592 |
+
},
|
| 593 |
+
{
|
| 594 |
+
label: 'Cumulative Produced',
|
| 595 |
+
data: Array.from({length: 30}, (_, i) => 29000 * (i + 1) + Math.floor(Math.random() * 95000)),
|
| 596 |
+
borderColor: '#7FDBFF',
|
| 597 |
+
backgroundColor: 'transparent',
|
| 598 |
+
borderWidth: 2,
|
| 599 |
+
tension: 0.1
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
label: 'Cumulative Forecast',
|
| 603 |
+
data: Array.from({length: 30}, (_, i) => 32000 * (i + 1)),
|
| 604 |
+
borderColor: '#001f3f',
|
| 605 |
+
backgroundColor: 'transparent',
|
| 606 |
+
borderWidth: 2,
|
| 607 |
+
borderDash: [5, 5],
|
| 608 |
+
tension: 0.1
|
| 609 |
+
},
|
| 610 |
+
{
|
| 611 |
+
label: 'Cumulative Budget',
|
| 612 |
+
data: Array.from({length: 30}, (_, i) => 30000 * (i + 1)),
|
| 613 |
+
borderColor: '#2ECC40',
|
| 614 |
+
backgroundColor: 'transparent',
|
| 615 |
+
borderWidth: 2,
|
| 616 |
+
borderDash: [5, 5],
|
| 617 |
+
tension: 0.1
|
| 618 |
+
}
|
| 619 |
+
]
|
| 620 |
+
},
|
| 621 |
+
options: {
|
| 622 |
+
responsive: true,
|
| 623 |
+
maintainAspectRatio: false,
|
| 624 |
+
scales: {
|
| 625 |
+
y: {
|
| 626 |
+
beginAtZero: true,
|
| 627 |
+
ticks: {
|
| 628 |
+
callback: function(value) {
|
| 629 |
+
return (value / 1000) + 'k';
|
| 630 |
}
|
| 631 |
},
|
| 632 |
+
grid: {
|
| 633 |
+
color: 'rgba(0, 0, 0, 0.05)'
|
|
|
|
|
|
|
| 634 |
}
|
| 635 |
},
|
| 636 |
+
x: {
|
| 637 |
+
grid: {
|
| 638 |
+
display: false
|
| 639 |
+
}
|
| 640 |
+
}
|
| 641 |
+
},
|
| 642 |
+
plugins: {
|
| 643 |
+
tooltip: {
|
| 644 |
+
callbacks: {
|
| 645 |
+
label: function(context) {
|
| 646 |
+
let label = context.dataset.label || '';
|
| 647 |
+
label += ': ' + context.raw.toLocaleString() + ' lbs';
|
| 648 |
+
if (context.datasetIndex < 3) {
|
| 649 |
+
const variance = context.raw - context.chart.data.datasets[3].data[context.dataIndex];
|
| 650 |
+
label += '\nvs Forecast: ' + (variance >= 0 ? '+' : '') + (variance / 1000).toFixed(1) + 'k';
|
| 651 |
+
|
| 652 |
+
const budgetVariance = context.raw - context.chart.data.datasets[4].data[context.dataIndex];
|
| 653 |
+
label += '\nvs Budget: ' + (budgetVariance >= 0 ? '+' : '') + (budgetVariance / 1000).toFixed(1) + 'k';
|
| 654 |
}
|
| 655 |
+
return label;
|
| 656 |
}
|
| 657 |
}
|
| 658 |
}
|
| 659 |
}
|
| 660 |
+
}
|
| 661 |
+
});
|
| 662 |
// Forecast Chart
|
| 663 |
const forecastCtx = document.getElementById('forecastChart').getContext('2d');
|
| 664 |
const forecastChart = new Chart(forecastCtx, {
|
|
|
|
| 671 |
data: Array.from({length: 30}, () => Math.floor(Math.random() * 50000) + 30000),
|
| 672 |
borderColor: '#001f3f',
|
| 673 |
backgroundColor: 'transparent',
|
| 674 |
+
borderWidth: 2,
|
| 675 |
tension: 0.1
|
| 676 |
},
|
| 677 |
{
|
|
|
|
| 679 |
data: Array.from({length: 30}, () => Math.floor(Math.random() * 48000) + 32000),
|
| 680 |
borderColor: '#001f3f',
|
| 681 |
backgroundColor: 'transparent',
|
| 682 |
+
borderWidth: 2,
|
| 683 |
+
borderDash: [5, 5],
|
| 684 |
+
tension: 0.1
|
| 685 |
+
},
|
| 686 |
+
{
|
| 687 |
+
label: 'Budget Ordered',
|
| 688 |
+
data: Array.from({length: 30}, () => Math.floor(Math.random() * 47000) + 31000),
|
| 689 |
+
borderColor: '#2ECC40',
|
| 690 |
+
backgroundColor: 'transparent',
|
| 691 |
+
borderWidth: 2,
|
| 692 |
borderDash: [5, 5],
|
| 693 |
tension: 0.1
|
| 694 |
}
|
| 695 |
]
|
| 696 |
},
|
| 697 |
+
options: {
|
| 698 |
responsive: true,
|
| 699 |
maintainAspectRatio: false,
|
| 700 |
scales: {
|
|
|
|
| 1013 |
}
|
| 1014 |
}
|
| 1015 |
});
|
|
|
|
| 1016 |
// Update forecast chart when metric changes
|
| 1017 |
document.getElementById('metricToggle').addEventListener('change', function() {
|
| 1018 |
const metric = this.value;
|
| 1019 |
|
| 1020 |
+
// Update all dataset labels and data based on selected metric
|
| 1021 |
forecastChart.data.datasets[0].label = 'Actual ' + metric;
|
| 1022 |
forecastChart.data.datasets[1].label = 'Forecast ' + metric;
|
| 1023 |
+
forecastChart.data.datasets[2].label = 'Budget ' + metric;
|
| 1024 |
|
| 1025 |
// Generate new random data for the selected metric
|
| 1026 |
forecastChart.data.datasets[0].data = Array.from({length: 30}, () => Math.floor(Math.random() * 50000) + 30000);
|
| 1027 |
forecastChart.data.datasets[1].data = Array.from({length: 30}, () => Math.floor(Math.random() * 48000) + 32000);
|
| 1028 |
+
forecastChart.data.datasets[2].data = Array.from({length: 30}, () => Math.floor(Math.random() * 47000) + 31000);
|
| 1029 |
|
| 1030 |
forecastChart.update();
|
| 1031 |
});
|
| 1032 |
+
// Update financial chart when view changes
|
|
|
|
| 1033 |
document.getElementById('financialViewToggle').addEventListener('change', function() {
|
| 1034 |
const view = this.value;
|
| 1035 |
|
|
|
|
| 1053 |
|
| 1054 |
financialChart.update();
|
| 1055 |
});
|
|
|
|
| 1056 |
// Date range filter handler
|
| 1057 |
const dateInputs = document.querySelectorAll('#startDate, #endDate');
|
| 1058 |
dateInputs.forEach(input => {
|
|
|
|
| 1065 |
console.log(`Date range changed: ${startDate} to ${endDate}`);
|
| 1066 |
|
| 1067 |
// Update all charts with new date range
|
| 1068 |
+
cumulativeChart.update();
|
| 1069 |
forecastChart.update();
|
| 1070 |
inventoryChart.update();
|
| 1071 |
financialChart.update();
|
|
|
|
| 1074 |
}
|
| 1075 |
});
|
| 1076 |
});
|
| 1077 |
+
});
|
| 1078 |
</script>
|
| 1079 |
</body>
|
| 1080 |
</html>
|