add helloword.html, improved UI for analytics

This commit is contained in:
ssbg04 committed 2026-04-24 22:40:52 +08:00
1 parent f15f7d0e60
commit 75bdfe843e
4 files changed
+308 -100

No files matched your search

+222 -27
View File
@@ -27,6 +27,7 @@ $(document).ready(function () {
$("#statTotalSpend").text("₱" + parseFloat(data.total_spend).toLocaleString("en-US", { minimumFractionDigits: 2 }));
$("#statTotalItems").text(data.total_items);
$("#statTurnover").text(data.turnover_ratio + "x");
$("#statIssuedItems").text(data.issued_30_days || "0");
// Predictive List
let predHtml = "";
@@ -53,12 +54,22 @@ $(document).ready(function () {
$("#prescriptiveAutoRestockBtn").prop("disabled", true).removeClass("btn-success").addClass("btn-secondary").html('<i class="bi bi-check-all me-2"></i> Stock Optimal');
}
// Charts
let valLabels = []; let valData = [];
data.valuation_data.forEach(row => { valLabels.push(row.supplier_name); valData.push(row.supplier_value); });
// Charts Data Preparation
let valLabels = [];
let valData = [];
data.valuation_data.forEach(row => {
let formattedValue = '₱' + parseFloat(row.supplier_value).toLocaleString("en-US", { minimumFractionDigits: 2 });
// 🔥 Multi-line X-Axis: Puts the Name on top, Number underneath
valLabels.push([row.supplier_name, formattedValue]);
valData.push(row.supplier_value);
});
let spendLabels = []; let spendData = [];
data.spend_by_category.forEach(row => { spendLabels.push(row.category_name); spendData.push(row.category_spend); });
let spendLabels = [];
let spendData = [];
data.spend_by_category.forEach(row => {
spendLabels.push(row.category_name);
spendData.push(row.category_spend);
});
if (window.valuationChartInstance) window.valuationChartInstance.destroy();
if (window.spendChartInstance) window.spendChartInstance.destroy();
@@ -67,20 +78,86 @@ $(document).ready(function () {
if(ctxVal) {
window.valuationChartInstance = new Chart(ctxVal.getContext("2d"), {
type: "bar",
data: { labels: valLabels, datasets: [{ label: "Valuation (₱)", data: valData, backgroundColor: "rgba(13, 110, 253, 0.8)", borderRadius: 4 }] },
plugins: [ChartDataLabels],
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, datalabels: { color: '#ffffff', anchor: 'end', align: 'bottom', font: { weight: 'bold' }, formatter: value => '₱' + parseFloat(value).toLocaleString("en-US") } } }
data: {
labels: valLabels,
datasets: [{
label: "Valuation",
data: valData,
backgroundColor: "rgba(13, 110, 253, 0.8)",
borderRadius: 4
}]
},
// 🔥 Removed ChartDataLabels plugin to clear floating numbers
options: {
layout: { padding: { top: 20, right: 20, bottom: 10, left: 10 } },
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
x: {
ticks: {
// Center aligns the multi-line text beautifully
align: 'center',
color: '#495057',
font: { weight: '500', size: 11 }
}
}
}
}
});
}
let ctxSpend = document.getElementById("spendChart");
if (ctxSpend) {
// Define colors array so we can share it between the chart and the HTML legend
let doughnutColors = ["#198754", "#ffc107", "#0dcaf0", "#d63384", "#6f42c1", "#fd7e14", "#20c997", "#6610f2"];
window.spendChartInstance = new Chart(ctxSpend.getContext("2d"), {
type: "doughnut",
data: { labels: spendLabels, datasets: [{ data: spendData, backgroundColor: ["#198754", "#ffc107", "#0dcaf0", "#d63384", "#6f42c1"], borderWidth: 2, borderColor: '#ffffff' }] },
plugins: [ChartDataLabels],
options: { responsive: true, maintainAspectRatio: false, cutout: "60%", plugins: { legend: { position: "right", labels: { padding: 15, generateLabels: function(chart) { const data = chart.data; if (data.labels.length && data.datasets.length) { return data.labels.map(function(label, i) { const meta = chart.getDatasetMeta(0); const style = meta.controller.getStyle(i); const value = data.datasets[0].data[i]; const formattedVal = '₱' + parseFloat(value).toLocaleString("en-US"); return { text: `${label} (${formattedVal})`, fillStyle: style.backgroundColor, strokeStyle: style.borderColor, lineWidth: style.borderWidth, hidden: !chart.getDataVisibility(i), index: i }; }); } return []; } } }, datalabels: { color: '#ffffff', font: { weight: 'bold', size: 11 }, formatter: value => value == 0 ? '' : '₱' + parseFloat(value).toLocaleString("en-US") } } }
data: {
labels: spendLabels,
datasets: [{
data: spendData,
backgroundColor: doughnutColors,
borderWidth: 2,
borderColor: '#ffffff'
}]
},
options: {
layout: { padding: 10 },
responsive: true,
maintainAspectRatio: false,
cutout: "65%",
plugins: {
// 🔥 Turn off the squished built-in canvas legend completely!
legend: { display: false }
}
}
});
// 🔥 Generate a beautiful, perfectly formatted HTML Legend
let legendHtml = '<div class="row mt-4 text-center justify-content-center">';
spendLabels.forEach((label, i) => {
let value = '₱' + parseFloat(spendData[i]).toLocaleString("en-US", { minimumFractionDigits: 2 });
let color = doughnutColors[i % doughnutColors.length];
legendHtml += `
<div class="col-6 mb-3">
<div class="d-flex align-items-center justify-content-center mb-1">
<span class="shadow-sm" style="width: 12px; height: 12px; background-color: ${color}; border-radius: 50%; margin-right: 8px;"></span>
<span class="text-muted small fw-bold text-truncate" title="${label}">${label}</span>
</div>
<div class="fw-bold text-dark">${value}</div>
</div>`;
});
legendHtml += '</div>';
// Remove any old custom legend, then inject the new one right below the canvas container
$("#customSpendLegend").remove();
$(ctxSpend).parent().after(`<div id="customSpendLegend">${legendHtml}</div>`);
}
if (btn) showAlert("Dashboard data refreshed successfully!", "success");
@@ -117,13 +194,75 @@ $(document).ready(function () {
// --- 3. EXPORT TO EXCEL ---
window.exportExcel = function() {
if (!currentAnalyticsData) { showAlert("Please wait for analytics data to load.", "warning"); return; }
if (!currentAnalyticsData) {
showAlert("Please wait for analytics data to load.", "warning");
return;
}
// Helper function to cleanly format currency for the spreadsheet
const formatCurrency = (val) => '₱' + parseFloat(val).toLocaleString("en-US", { minimumFractionDigits: 2 });
const wb = XLSX.utils.book_new();
const kpiData = [ ["Metric", "Value"], ["Total Stock Valuation", parseFloat(currentAnalyticsData.total_value)], ["Total PO Spend", parseFloat(currentAnalyticsData.total_spend)], ["Inventory Turnover Ratio", parseFloat(currentAnalyticsData.turnover_ratio)], ["Total Unique Items", parseInt(currentAnalyticsData.total_items)], ["Shortages (Low Stock)", parseInt(currentAnalyticsData.low_stock)] ];
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(kpiData), "KPI Summary");
const valData = [["Supplier Name", "Stock Valuation (₱)"]]; currentAnalyticsData.valuation_data.forEach(row => { valData.push([row.supplier_name, parseFloat(row.supplier_value)]); }); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(valData), "Valuation by Supplier");
const spendData = [["Category/Supplier", "Total Spend (₱)"]]; currentAnalyticsData.spend_by_category.forEach(row => { spendData.push([row.category_name, parseFloat(row.category_spend)]); }); XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(spendData), "Spend by Category");
XLSX.writeFile(wb, `Inventory_Analytics_${new Date().toISOString().split('T')[0]}.xlsx`);
// SHEET 1: KPI Summary
const kpiData = [
["System Analytics", "Value"],
["Current Stock Value", formatCurrency(currentAnalyticsData.total_value)],
["Total Purchases (Spend)", formatCurrency(currentAnalyticsData.total_spend)],
["Inventory Turnover Ratio", parseFloat(currentAnalyticsData.turnover_ratio) + "x"],
["Total Unique Items", parseInt(currentAnalyticsData.total_items)],
["Items Issued/Borrowed (30 Days)", parseInt(currentAnalyticsData.issued_30_days || 0)],
["Critical Shortages (Action Items)", parseInt(currentAnalyticsData.low_stock)],
["Overstock Alerts", parseInt(currentAnalyticsData.overstock)]
];
const wsKpi = XLSX.utils.aoa_to_sheet(kpiData);
// Expand column widths to make it readable
wsKpi['!cols'] = [{ wch: 35 }, { wch: 20 }];
XLSX.utils.book_append_sheet(wb, wsKpi, "KPI Summary");
// SHEET 2: Valuation by Supplier
const valData = [["Supplier Name", "Stock Valuation"]];
if (currentAnalyticsData.valuation_data) {
currentAnalyticsData.valuation_data.forEach(row => {
valData.push([row.supplier_name, formatCurrency(row.supplier_value)]);
});
}
const wsVal = XLSX.utils.aoa_to_sheet(valData);
wsVal['!cols'] = [{ wch: 30 }, { wch: 20 }];
XLSX.utils.book_append_sheet(wb, wsVal, "Valuation by Supplier");
// SHEET 3: Spend by Category
const spendData = [["Category / Supplier", "Total Spend"]];
if (currentAnalyticsData.spend_by_category) {
currentAnalyticsData.spend_by_category.forEach(row => {
spendData.push([row.category_name, formatCurrency(row.category_spend)]);
});
}
const wsSpend = XLSX.utils.aoa_to_sheet(spendData);
wsSpend['!cols'] = [{ wch: 30 }, { wch: 20 }];
XLSX.utils.book_append_sheet(wb, wsSpend, "Spend by Category");
// SHEET 4: Stock Depletion Forecast
const forecastData = [["Item Name", "Current Stock", "Daily Burn Rate", "Estimated Days Left"]];
if (currentAnalyticsData.predictions && currentAnalyticsData.predictions.length > 0) {
currentAnalyticsData.predictions.forEach(item => {
forecastData.push([
item.name,
item.quantity + " units",
parseFloat(item.daily_burn).toFixed(2) + " / day",
item.days_left + " days"
]);
});
} else {
forecastData.push(["No imminent stockouts predicted!", "-", "-", "-"]);
}
const wsForecast = XLSX.utils.aoa_to_sheet(forecastData);
wsForecast['!cols'] = [{ wch: 35 }, { wch: 15 }, { wch: 18 }, { wch: 20 }];
XLSX.utils.book_append_sheet(wb, wsForecast, "Depletion Forecast");
// Generate and download the file
let dateStr = new Date().toISOString().split('T')[0];
XLSX.writeFile(wb, `ICIS_Analytics_Report_${dateStr}.xlsx`);
};
// --- 4. EXPORT TO PDF ---
@@ -132,20 +271,76 @@ $(document).ready(function () {
const element = document.getElementById('analyticsContainer');
const buttons = document.getElementById('analyticsButtons');
buttons.style.display = 'none';
const originalWidth = element.style.width; const originalMaxWidth = element.style.maxWidth; const originalBg = element.style.backgroundColor; const originalPadding = element.style.padding;
const targetWidth = 1200; element.style.width = targetWidth + 'px'; element.style.maxWidth = targetWidth + 'px'; element.style.backgroundColor = '#f3f4f6'; element.style.padding = '20px';
if (!element) return; // Failsafe
// 1. Hide buttons and lock layout for snapshot
buttons.style.display = 'none';
const originalWidth = element.style.width;
const originalMaxWidth = element.style.maxWidth;
const originalBg = element.style.backgroundColor;
const originalPadding = element.style.padding;
// Force a wide desktop view so charts don't squash together
const targetWidth = 1440;
element.style.width = targetWidth + 'px';
element.style.maxWidth = targetWidth + 'px';
element.style.backgroundColor = '#f8f9fa';
element.style.padding = '40px';
// 2. Add a tiny delay to ensure Chart.js animations are 100% finished
setTimeout(() => {
html2canvas(element, {
scale: 2.5, // 🔥 Upped to 3 for ultra-crisp text and charts
useCORS: true,
logging: false,
width: targetWidth,
windowWidth: targetWidth
}).then((canvas) => {
// 3. Restore the live UI instantly
buttons.style.display = 'flex';
element.style.width = originalWidth;
element.style.maxWidth = originalMaxWidth;
element.style.backgroundColor = originalBg;
element.style.padding = originalPadding;
const h100Cards = element.querySelectorAll('.h-100');
h100Cards.forEach(card => card.style.minHeight = '100%');
// 🔥 Use PNG instead of JPEG to prevent blurry artifacting!
const imgData = canvas.toDataURL('image/png');
// 4. Build the PDF
const pdf = new jsPDF('landscape', 'in', 'letter');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const margin = 0.3;
const maxImgWidth = pdfWidth - (margin * 2);
const maxImgHeight = pdfHeight - (margin * 2);
const ratio = Math.min(maxImgWidth / imgProps.width, maxImgHeight / imgProps.height);
const finalWidth = imgProps.width * ratio;
const finalHeight = imgProps.height * ratio;
// Add the sharp PNG to the PDF and download
pdf.addImage(imgData, 'PNG', (pdfWidth - finalWidth) / 2, margin, finalWidth, finalHeight);
pdf.save(`ICIS_Analytics_Report_${new Date().toISOString().split('T')[0]}.pdf`);
showAlert("High-Resolution PDF downloaded successfully!", "success");
html2canvas(element, { scale: 2, useCORS: true, logging: false, width: targetWidth, windowWidth: targetWidth }).then((canvas) => {
buttons.style.display = 'flex'; element.style.width = originalWidth; element.style.maxWidth = originalMaxWidth; element.style.backgroundColor = originalBg; element.style.padding = originalPadding;
const imgData = canvas.toDataURL('image/jpeg', 1.0); const pdf = new jsPDF('landscape', 'in', 'letter'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData); const margin = 0.3; const maxImgWidth = pdfWidth - (margin * 2); const maxImgHeight = pdfHeight - (margin * 2);
const ratio = Math.min(maxImgWidth / imgProps.width, maxImgHeight / imgProps.height); const finalWidth = imgProps.width * ratio; const finalHeight = imgProps.height * ratio;
pdf.addImage(imgData, 'JPEG', (pdfWidth - finalWidth) / 2, margin, finalWidth, finalHeight); pdf.save(`Inventory_Report_${new Date().toISOString().split('T')[0]}.pdf`);
}).catch(err => {
buttons.style.display = 'flex'; element.style.width = originalWidth; element.style.maxWidth = originalMaxWidth; element.style.backgroundColor = originalBg; element.style.padding = originalPadding;
// Restore UI if something breaks
buttons.style.display = 'flex';
element.style.width = originalWidth;
element.style.maxWidth = originalMaxWidth;
element.style.backgroundColor = originalBg;
element.style.padding = originalPadding;
showAlert("Failed to generate PDF.", "danger");
console.error(err);
});
}, 300); // 300ms pause
};
});
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
+8
View File
@@ -774,6 +774,13 @@ switch ($action) {
");
$predictive_data = $stmtPredict->fetchAll(PDO::FETCH_ASSOC);
$issuedStmt = $pdo->query("
SELECT COALESCE(SUM(qty_issued), 0) as total_issued
FROM inventory_transactions
WHERE transaction_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)
");
$issued_30_days = $issuedStmt->fetch(PDO::FETCH_ASSOC)['total_issued'];
// Calculate explicit days for the JSON payload
foreach($predictive_data as &$pred) {
$pred['days_left'] = floor($pred['quantity'] / $pred['daily_burn']);
@@ -792,6 +799,7 @@ switch ($action) {
'low_stock' => $shortages['low_stock'] ?? 0,
'total_spend' => $total_spend,
'turnover_ratio' => round($turnover_ratio, 2),
'issued_30_days' => $issued_30_days,
'spend_by_category' => $spend_by_category,
'valuation_data' => $valuation_data,
'predictions' => $predictive_data
+67 -73
View File
@@ -1,102 +1,96 @@
<div id="analyticsContainer">
<div class="d-flex justify-content-between align-items-center mb-4 header-actions">
<h4 class="mb-0 fw-bold">Dashboard Analytics</h4>
<div class="d-flex gap-2" id="analyticsButtons">
<button class="btn btn-outline-success shadow-sm rounded-pill px-3 bg-white" onclick="exportExcel()">
<div class="d-flex justify-content-between align-items-center mb-4 header-actions" id="analyticsButtons">
<h4 class="mb-0 fw-bold">System Overview</h4>
<div class="d-flex gap-2">
<button class="btn btn-outline-success bg-white shadow-sm" onclick="exportExcel()">
<i class="bi bi-file-earmark-excel me-1"></i> Excel
</button>
<button class="btn btn-outline-danger shadow-sm rounded-pill px-3 bg-white" onclick="exportPDF()">
<button class="btn btn-outline-danger bg-white shadow-sm" onclick="exportPDF()">
<i class="bi bi-file-earmark-pdf me-1"></i> PDF
</button>
<button class="btn btn-outline-primary shadow-sm rounded-pill px-3 bg-white" onclick="loadAnalytics(this)">
<button class="btn btn-primary shadow-sm" onclick="loadAnalytics(this)">
<i class="bi bi-arrow-clockwise me-1"></i> Refresh
</button>
</div>
</div>
<div class="row g-4">
<div class="col-12 col-lg-8">
<h6 class="fw-bold text-secondary text-uppercase mb-3" style="letter-spacing: 1px;"><i class="bi bi-eye text-primary me-2"></i>1. Descriptive (Current State)</h6>
<div class="row g-3 mb-5">
<div class="col-12 col-md-4">
<div class="card card-custom kpi-card p-3 text-center h-100 border-top border-primary border-4">
<div class="text-muted small fw-bold mb-1">STOCK VALUATION</div>
<h4 id="statTotalValue" class="fw-bold text-primary mb-0">₱0.00</h4>
</div>
</div>
<div class="col-12 col-md-4">
<div class="card card-custom kpi-card p-3 text-center h-100 border-top border-dark border-4">
<div class="text-muted small fw-bold mb-1">TOTAL PO SPEND</div>
<h4 id="statTotalSpend" class="fw-bold text-dark mb-0">₱0.00</h4>
</div>
</div>
<div class="col-12 col-md-4">
<div class="card card-custom kpi-card p-3 text-center h-100 border-top border-secondary border-4">
<div class="text-muted small fw-bold mb-1">UNIQUE ITEMS</div>
<h4 id="statTotalItems" class="fw-bold text-secondary mb-0">0</h4>
</div>
</div>
</div>
<h6 class="fw-bold text-secondary text-uppercase mb-3" style="letter-spacing: 1px;"><i class="bi bi-search text-info me-2"></i>2. Diagnostic (Trends & Causes)</h6>
<div id="analyticsContainer">
<div class="row g-3 mb-4">
<div class="col-12 col-md-4">
<div class="card card-custom kpi-card p-3 text-center h-100 bg-light">
<div class="text-muted small fw-bold mb-1">INVENTORY TURNOVER</div>
<h3 id="statTurnover" class="fw-bold text-info mb-1">0.00x</h3>
<span class="small text-muted">Spend vs. Current Stock</span>
</div>
</div>
<div class="col-12 col-md-8">
<div class="card card-custom p-3 h-100">
<div class="text-muted small fw-bold mb-2 text-center">SPEND BY CATEGORY (SUPPLIER)</div>
<div style="position: relative; height: 150px; width: 100%">
<canvas id="spendChart"></canvas>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100 bg-primary text-white">
<div class="card-body p-4">
<div class="text-white-50 small fw-bold text-uppercase mb-1">Current Stock Value</div>
<h3 class="fw-bold mb-0" id="statTotalValue">₱0.00</h3>
</div>
</div>
</div>
<div class="col-12">
<div class="card card-custom p-3">
<div class="text-muted small fw-bold mb-2 text-center">VALUATION BY SUPPLIER</div>
<div style="position: relative; height: 200px; width: 100%">
<canvas id="valuationChart"></canvas>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<div class="text-muted small fw-bold text-uppercase mb-1">Items Borrowed/Issued (30 Days)</div>
<h3 class="fw-bold mb-0 text-dark" id="statIssuedItems">0</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<div class="text-muted small fw-bold text-uppercase mb-1">Total Purchases</div>
<h3 class="fw-bold mb-0 text-dark" id="statTotalSpend">₱0.00</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<div class="text-muted small fw-bold text-uppercase mb-1">Total Unique Items</div>
<h3 class="fw-bold mb-0 text-dark" id="statTotalItems">0</h3>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-md-8">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<h6 class="fw-bold text-dark mb-4">Stock Value by Supplier</h6>
<div style="height: 300px;"><canvas id="valuationChart"></canvas></div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<h6 class="fw-bold text-dark mb-4">Spending by Category</h6>
<div style="height: 300px;"><canvas id="spendChart"></canvas></div>
</div>
<div class="col-12 col-lg-4">
<h6 class="fw-bold text-secondary text-uppercase mb-3" style="letter-spacing: 1px;"><i class="bi bi-graph-up-arrow text-warning me-2"></i>3. Predictive (Forecast)</h6>
<div class="card card-custom mb-5 p-0 overflow-hidden">
<div class="bg-warning bg-opacity-10 p-3 border-bottom border-warning border-opacity-25">
<h6 class="fw-bold text-dark mb-0">High-Risk Stockouts</h6>
<span class="small text-muted">Items predicted to run out in < 7 days based on recent burn rates.</span>
</div>
<div class="p-0">
<ul class="list-group list-group-flush" id="predictiveList">
<li class="list-group-item text-center py-4 text-muted small">Loading predictions...</li>
</ul>
</div>
</div>
<h6 class="fw-bold text-secondary text-uppercase mb-3" style="letter-spacing: 1px;"><i class="bi bi-lightning-charge-fill text-success me-2"></i>4. Prescriptive (Action)</h6>
<div class="card card-custom bg-success bg-opacity-10 border-0 p-4 text-center">
<div class="mb-3">
<i class="bi bi-robot text-success" style="font-size: 2.5rem;"></i>
<div class="row g-4">
<div class="col-md-6">
<div class="card border-0 shadow-sm h-100">
<div class="card-body p-4">
<h6 class="fw-bold text-dark mb-3"><i class="bi bi-graph-down-arrow text-danger me-2"></i>Stock Depletion Forecast</h6>
<p class="text-muted small mb-3">Items expected to run out soon based on recent usage.</p>
<ul class="list-group list-group-flush border-top" id="predictiveList"></ul>
</div>
<h5 class="fw-bold text-dark mb-2">System Recommendation</h5>
<p class="text-muted small mb-4">
You have <strong class="text-danger" id="prescriptiveLowCount">0</strong> items currently below their reorder thresholds. The system can automatically generate optimized Purchase Orders for all affected suppliers right now.
</p>
<button class="btn btn-success rounded-pill fw-bold shadow-sm w-100 py-2" id="prescriptiveAutoRestockBtn">
<i class="bi bi-magic me-2"></i> Execute Auto-Restock
</div>
</div>
<div class="col-md-6">
<div class="card border-0 shadow-sm h-100 bg-light border border-success border-opacity-25">
<div class="card-body p-4 text-center d-flex flex-column justify-content-center">
<div class="bg-success bg-opacity-10 text-success d-inline-flex p-3 rounded-circle mx-auto mb-3">
<i class="bi bi-box-seam fs-2"></i>
</div>
<h5 class="fw-bold text-dark">Action Items</h5>
<p class="text-muted mb-4">You have <strong id="prescriptiveLowCount" class="text-danger">0</strong> items currently at or below their minimum reorder level.</p>
<button class="btn btn-success rounded-pill px-4 py-2 mx-auto shadow-sm" id="prescriptiveAutoRestockBtn">
<i class="bi bi-lightning-charge-fill me-1"></i> Auto-Generate Restock Orders
</button>
</div>
</div>
</div>
</div>
</div>