Files
quality_app/py_app/app/templates/daily_mirror.html
2025-12-01 23:48:09 +02:00

996 lines
32 KiB
HTML

{% extends "base.html" %}
{% block title %}Daily Mirror Dashboard - Quality Recticel{% endblock %}
{% block head %}
<!-- Chart.js Library -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
:root {
--primary-color: #007bff;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--info-color: #17a2b8;
--dark-color: #343a40;
--light-bg: #f8f9fa;
}
.dashboard-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
border-radius: 10px;
margin-bottom: 2rem;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.dashboard-header h1 {
margin: 0;
font-size: 2rem;
font-weight: 600;
}
.dashboard-header p {
margin: 0.5rem 0 0 0;
opacity: 0.9;
}
.summary-card {
background: white;
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
border-left: 4px solid var(--primary-color);
}
.summary-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.summary-card.orders {
border-left-color: #007bff;
}
.summary-card.production {
border-left-color: #28a745;
}
.summary-card.deliveries {
border-left-color: #ffc107;
}
.summary-card.customers {
border-left-color: #17a2b8;
}
.summary-card-icon {
font-size: 2.5rem;
opacity: 0.2;
position: absolute;
right: 1.5rem;
top: 50%;
transform: translateY(-50%);
}
.summary-card-title {
font-size: 0.875rem;
color: #6c757d;
text-transform: uppercase;
font-weight: 600;
letter-spacing: 0.5px;
margin-bottom: 0.5rem;
}
.summary-card-value {
font-size: 2rem;
font-weight: 700;
color: #343a40;
margin: 0;
}
.summary-card-subtitle {
font-size: 0.875rem;
color: #6c757d;
margin-top: 0.25rem;
}
.chart-container {
background: white;
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: relative;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e9ecef;
}
.chart-title {
font-size: 1.25rem;
font-weight: 600;
color: #343a40;
margin: 0;
}
.chart-toggle-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 0.875rem;
transition: background 0.2s;
}
.chart-toggle-btn:hover {
background: #0056b3;
}
.chart-toggle-btn i {
margin-right: 0.5rem;
}
.data-table-container {
display: none;
margin-top: 1.5rem;
animation: slideDown 0.3s ease-out;
}
.data-table-container.active {
display: block;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.data-table thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.data-table th {
padding: 0.75rem;
text-align: left;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
}
.data-table tbody tr {
border-bottom: 1px solid #e9ecef;
transition: background 0.2s;
}
.data-table tbody tr:hover {
background: #f8f9fa;
}
.data-table td {
padding: 0.75rem;
color: #495057;
}
.data-table td.number {
text-align: right;
font-weight: 500;
}
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 3px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
.badge-success {
background: #d4edda;
color: #155724;
}
.badge-warning {
background: #fff3cd;
color: #856404;
}
.badge-info {
background: #d1ecf1;
color: #0c5460;
}
.loading-spinner {
text-align: center;
padding: 3rem;
color: #6c757d;
}
.loading-spinner i {
font-size: 3rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.filter-section {
background: white;
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.filter-title {
font-size: 1rem;
font-weight: 600;
color: #343a40;
margin-bottom: 1rem;
}
.pivot-table-container {
overflow-x: auto;
margin-top: 1rem;
}
.pivot-table {
min-width: 100%;
border-collapse: collapse;
font-size: 0.813rem;
white-space: nowrap;
}
.pivot-table thead th {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 0.75rem 0.5rem;
text-align: center;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
border-right: 1px solid rgba(255,255,255,0.2);
}
.pivot-table thead th:first-child {
text-align: left;
min-width: 200px;
}
.pivot-table tbody td {
padding: 0.75rem 0.5rem;
border: 1px solid #e9ecef;
text-align: center;
}
.pivot-table tbody td:first-child {
font-weight: 600;
background: #f8f9fa;
text-align: left;
position: sticky;
left: 0;
z-index: 5;
}
.pivot-table tbody tr:hover {
background: #f1f3f5;
}
.pivot-cell-orders {
color: #007bff;
font-weight: 600;
}
.pivot-cell-production {
color: #28a745;
font-weight: 600;
}
.pivot-cell-deliveries {
color: #ffc107;
font-weight: 600;
}
.no-data-message {
text-align: center;
padding: 3rem;
color: #6c757d;
font-style: italic;
}
</style>
{% endblock %}
{% block content %}
<!-- Floating Help Button -->
<div class="floating-help-btn">
<a href="{{ url_for('main.help', page='daily_mirror') }}" target="_blank" title="Ajutor - Daily Mirror">
📖
</a>
</div>
<div class="container-fluid">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1><i class="fas fa-chart-line"></i> Daily Mirror Dashboard</h1>
<p>Comprehensive production, orders, and delivery analytics</p>
</div>
<div>
<a href="{{ url_for('daily_mirror.daily_mirror_history_route') }}" class="btn btn-light">
<i class="fas fa-history"></i> History
</a>
<a href="{{ url_for('daily_mirror.daily_mirror_main_route') }}" class="btn btn-light">
<i class="fas fa-arrow-left"></i> Back
</a>
</div>
</div>
</div>
<!-- Filter Section -->
<div class="filter-section">
<div class="filter-title"><i class="fas fa-filter"></i> Time Range Filter</div>
<div class="row align-items-end">
<div class="col-md-3">
<label for="weeksBack" class="form-label">Weeks to Display:</label>
<select id="weeksBack" class="form-select">
<option value="4">Last 4 Weeks</option>
<option value="8">Last 8 Weeks</option>
<option value="12" selected>Last 12 Weeks</option>
<option value="16">Last 16 Weeks</option>
<option value="24">Last 24 Weeks</option>
<option value="52">Last Year</option>
</select>
</div>
<div class="col-md-3">
<button type="button" class="btn btn-primary" onclick="loadDashboardData()">
<i class="fas fa-sync-alt"></i> Refresh Data
</button>
</div>
<div class="col-md-6 text-end">
<small class="text-muted" id="dateRangeDisplay">Loading...</small>
</div>
</div>
</div>
<!-- Summary Cards -->
<div class="row" id="summaryCardsContainer">
<div class="col-xl-3 col-md-6">
<div class="summary-card orders" onclick="scrollToChart('ordersChart')">
<i class="fas fa-shopping-cart summary-card-icon"></i>
<div class="summary-card-title">Customer Orders</div>
<div class="summary-card-value" id="totalOrders">-</div>
<div class="summary-card-subtitle"><span id="totalOrderQty">-</span> units ordered</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="summary-card production" onclick="scrollToChart('productionChart')">
<i class="fas fa-cogs summary-card-icon"></i>
<div class="summary-card-title">Production Finished</div>
<div class="summary-card-value" id="totalFinished">-</div>
<div class="summary-card-subtitle"><span id="totalApproved">-</span> approved</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="summary-card deliveries" onclick="scrollToChart('deliveriesChart')">
<i class="fas fa-truck summary-card-icon"></i>
<div class="summary-card-title">Deliveries</div>
<div class="summary-card-value" id="totalDeliveries">-</div>
<div class="summary-card-subtitle"><span id="totalDelivered">-</span> units delivered</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="summary-card customers" onclick="scrollToChart('customerPivotTable')">
<i class="fas fa-users summary-card-icon"></i>
<div class="summary-card-title">Active Customers</div>
<div class="summary-card-value" id="uniqueCustomers">-</div>
<div class="summary-card-subtitle">unique customers</div>
</div>
</div>
</div>
<!-- Orders Chart -->
<div class="chart-container" id="ordersChart">
<div class="chart-header">
<h3 class="chart-title"><i class="fas fa-shopping-cart"></i> Customer Orders by Week</h3>
<button class="chart-toggle-btn" onclick="toggleDataTable('ordersTable')">
<i class="fas fa-table"></i> Show Data Table
</button>
</div>
<canvas id="ordersChartCanvas" height="80"></canvas>
<div class="data-table-container" id="ordersTable">
<table class="data-table">
<thead>
<tr>
<th>Week</th>
<th>Customer</th>
<th>Orders</th>
<th>Total Quantity</th>
<th>Open</th>
<th>Completed</th>
</tr>
</thead>
<tbody id="ordersTableBody">
<tr><td colspan="6" class="text-center">No data available</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Production Chart -->
<div class="chart-container" id="productionChart">
<div class="chart-header">
<h3 class="chart-title"><i class="fas fa-cogs"></i> Production Finished by Week</h3>
<button class="chart-toggle-btn" onclick="toggleDataTable('productionTable')">
<i class="fas fa-table"></i> Show Data Table
</button>
</div>
<canvas id="productionChartCanvas" height="80"></canvas>
<div class="data-table-container" id="productionTable">
<table class="data-table">
<thead>
<tr>
<th>Week</th>
<th>Finished Orders</th>
<th>Approved Quantity</th>
<th>Rejected Quantity</th>
<th>Quality Rate</th>
</tr>
</thead>
<tbody id="productionTableBody">
<tr><td colspan="5" class="text-center">No data available</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Deliveries Chart -->
<div class="chart-container" id="deliveriesChart">
<div class="chart-header">
<h3 class="chart-title"><i class="fas fa-truck"></i> Deliveries by Week</h3>
<button class="chart-toggle-btn" onclick="toggleDataTable('deliveriesTable')">
<i class="fas fa-table"></i> Show Data Table
</button>
</div>
<canvas id="deliveriesChartCanvas" height="80"></canvas>
<div class="data-table-container" id="deliveriesTable">
<table class="data-table">
<thead>
<tr>
<th>Week</th>
<th>Customer</th>
<th>Delivery Count</th>
<th>Total Delivered</th>
</tr>
</thead>
<tbody id="deliveriesTableBody">
<tr><td colspan="4" class="text-center">No data available</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Customer Pivot Table -->
<div class="chart-container" id="customerPivotTable">
<div class="chart-header">
<h3 class="chart-title"><i class="fas fa-table"></i> Customer Orders Pivot Table</h3>
<div>
<select id="pivotMetric" class="form-select form-select-sm d-inline-block w-auto me-2" onchange="updatePivotTable()">
<option value="orders">Orders Count</option>
<option value="quantity" selected>Total Quantity</option>
<option value="open">Open Quantity</option>
<option value="completed">Completed Quantity</option>
</select>
</div>
</div>
<div class="pivot-table-container">
<table class="pivot-table" id="pivotTableElement">
<thead id="pivotTableHead">
<tr><th>Customer</th></tr>
</thead>
<tbody id="pivotTableBody">
<tr><td colspan="100%" class="text-center">Loading...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Loading Overlay -->
<div class="loading-spinner" id="loadingSpinner" style="display: none;">
<i class="fas fa-spinner"></i>
<p>Loading dashboard data...</p>
</div>
</div>
<script>
let dashboardData = null;
let ordersChart = null;
let productionChart = null;
let deliveriesChart = null;
// Load dashboard data on page load
document.addEventListener('DOMContentLoaded', function() {
loadDashboardData();
});
function loadDashboardData() {
const weeksBack = document.getElementById('weeksBack').value;
document.getElementById('loadingSpinner').style.display = 'block';
fetch(`/daily_mirror/api/dashboard_data?weeks_back=${weeksBack}`)
.then(response => {
if (!response.ok) {
return response.json().then(err => {
throw new Error(err.error || `HTTP ${response.status}: ${response.statusText}`);
});
}
return response.json();
})
.then(data => {
console.log('Dashboard data received:', data);
if (data.error) {
throw new Error(data.error);
}
dashboardData = data;
updateSummaryCards(data.summary);
updateDateRange(data.date_range);
renderOrdersChart(data.customer_orders_by_week);
renderProductionChart(data.finished_orders_by_week);
renderDeliveriesChart(data.deliveries_by_week);
updatePivotTable();
document.getElementById('loadingSpinner').style.display = 'none';
})
.catch(error => {
console.error('Error loading dashboard data:', error);
document.getElementById('loadingSpinner').style.display = 'none';
alert('Error loading dashboard data: ' + error.message + '\n\nPlease make sure you are logged in and have Daily Mirror access.');
});
}
function updateSummaryCards(summary) {
document.getElementById('totalOrders').textContent = summary.total_orders.toLocaleString();
document.getElementById('totalOrderQty').textContent = summary.total_quantity.toLocaleString();
document.getElementById('totalFinished').textContent = summary.total_finished.toLocaleString();
document.getElementById('totalApproved').textContent = summary.total_approved.toLocaleString();
document.getElementById('totalDeliveries').textContent = summary.total_deliveries.toLocaleString();
document.getElementById('totalDelivered').textContent = summary.total_delivered.toLocaleString();
document.getElementById('uniqueCustomers').textContent = summary.unique_customers.toLocaleString();
}
function updateDateRange(dateRange) {
document.getElementById('dateRangeDisplay').textContent =
`Data from ${dateRange.start} to ${dateRange.end}`;
}
function renderOrdersChart(data) {
// Aggregate by week
const weekMap = {};
data.forEach(item => {
const week = item.week_display;
if (!weekMap[week]) {
weekMap[week] = { total: 0, open: 0, completed: 0 };
}
weekMap[week].total += item.total_quantity || 0;
weekMap[week].open += item.open_quantity || 0;
weekMap[week].completed += item.completed_quantity || 0;
});
const weeks = Object.keys(weekMap).sort();
const totals = weeks.map(w => weekMap[w].total);
const opens = weeks.map(w => weekMap[w].open);
const completed = weeks.map(w => weekMap[w].completed);
const ctx = document.getElementById('ordersChartCanvas').getContext('2d');
if (ordersChart) {
ordersChart.destroy();
}
ordersChart = new Chart(ctx, {
type: 'bar',
data: {
labels: weeks,
datasets: [
{
label: 'Total Quantity',
data: totals,
backgroundColor: 'rgba(0, 123, 255, 0.6)',
borderColor: 'rgba(0, 123, 255, 1)',
borderWidth: 2
},
{
label: 'Open',
data: opens,
backgroundColor: 'rgba(255, 193, 7, 0.6)',
borderColor: 'rgba(255, 193, 7, 1)',
borderWidth: 2
},
{
label: 'Completed',
data: completed,
backgroundColor: 'rgba(40, 167, 69, 0.6)',
borderColor: 'rgba(40, 167, 69, 1)',
borderWidth: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + context.parsed.y.toLocaleString();
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value.toLocaleString();
}
}
}
}
}
});
// Update table
const tbody = document.getElementById('ordersTableBody');
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center">No data available</td></tr>';
} else {
tbody.innerHTML = data.map(item => `
<tr>
<td>${item.week_display || '-'}</td>
<td>${item.customer_name || 'Unknown'}</td>
<td class="number">${(item.order_count || 0).toLocaleString()}</td>
<td class="number">${(item.total_quantity || 0).toLocaleString()}</td>
<td class="number">${(item.open_quantity || 0).toLocaleString()}</td>
<td class="number">${(item.completed_quantity || 0).toLocaleString()}</td>
</tr>
`).join('');
}
}
function renderProductionChart(data) {
const weeks = data.map(item => item.week_display);
const finished = data.map(item => item.finished_count || 0);
const approved = data.map(item => item.approved_qty || 0);
const rejected = data.map(item => item.rejected_qty || 0);
const ctx = document.getElementById('productionChartCanvas').getContext('2d');
if (productionChart) {
productionChart.destroy();
}
productionChart = new Chart(ctx, {
type: 'line',
data: {
labels: weeks,
datasets: [
{
label: 'Finished Orders',
data: finished,
backgroundColor: 'rgba(40, 167, 69, 0.2)',
borderColor: 'rgba(40, 167, 69, 1)',
borderWidth: 3,
fill: true,
tension: 0.4
},
{
label: 'Approved Qty',
data: approved,
backgroundColor: 'rgba(23, 162, 184, 0.2)',
borderColor: 'rgba(23, 162, 184, 1)',
borderWidth: 2,
fill: false,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true,
position: 'top'
},
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + context.parsed.y.toLocaleString();
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return value.toLocaleString();
}
}
}
}
}
});
// Update table
const tbody = document.getElementById('productionTableBody');
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center">No data available</td></tr>';
} else {
tbody.innerHTML = data.map(item => {
const qualityRate = item.approved_qty ?
((item.approved_qty / (item.approved_qty + item.rejected_qty)) * 100).toFixed(1) : 0;
return `
<tr>
<td>${item.week_display || '-'}</td>
<td class="number">${(item.finished_count || 0).toLocaleString()}</td>
<td class="number">${(item.approved_qty || 0).toLocaleString()}</td>
<td class="number">${(item.rejected_qty || 0).toLocaleString()}</td>
<td class="number"><span class="badge badge-success">${qualityRate}%</span></td>
</tr>
`;
}).join('');
}
}
function renderDeliveriesChart(data) {
// Aggregate by week
const weekMap = {};
data.forEach(item => {
const week = item.week_display;
if (!weekMap[week]) {
weekMap[week] = { count: 0, quantity: 0 };
}
weekMap[week].count += item.delivery_count || 0;
weekMap[week].quantity += item.total_delivered || 0;
});
const weeks = Object.keys(weekMap).sort();
const counts = weeks.map(w => weekMap[w].count);
const quantities = weeks.map(w => weekMap[w].quantity);
const ctx = document.getElementById('deliveriesChartCanvas').getContext('2d');
if (deliveriesChart) {
deliveriesChart.destroy();
}
deliveriesChart = new Chart(ctx, {
type: 'bar',
data: {
labels: weeks,
datasets: [
{
label: 'Delivery Count',
data: counts,
backgroundColor: 'rgba(255, 193, 7, 0.6)',
borderColor: 'rgba(255, 193, 7, 1)',
borderWidth: 2,
yAxisID: 'y'
},
{
label: 'Quantity Delivered',
data: quantities,
backgroundColor: 'rgba(220, 53, 69, 0.6)',
borderColor: 'rgba(220, 53, 69, 1)',
borderWidth: 2,
type: 'line',
yAxisID: 'y1',
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true,
position: 'top'
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
beginAtZero: true,
title: {
display: true,
text: 'Delivery Count'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
beginAtZero: true,
title: {
display: true,
text: 'Quantity'
},
grid: {
drawOnChartArea: false
}
}
}
}
});
// Update table
const tbody = document.getElementById('deliveriesTableBody');
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="text-center">No data available</td></tr>';
} else {
tbody.innerHTML = data.map(item => `
<tr>
<td>${item.week_display || '-'}</td>
<td>${item.customer_name || 'Unknown'}</td>
<td class="number">${(item.delivery_count || 0).toLocaleString()}</td>
<td class="number">${(item.total_delivered || 0).toLocaleString()}</td>
</tr>
`).join('');
}
}
function updatePivotTable() {
if (!dashboardData || !dashboardData.customer_orders_by_week) {
return;
}
const metric = document.getElementById('pivotMetric').value;
const data = dashboardData.customer_orders_by_week;
// Get unique weeks and customers
const weeks = [...new Set(data.map(item => item.week_display))].sort();
const customers = [...new Set(data.map(item => item.customer_name))].sort();
// Build pivot data structure
const pivotData = {};
customers.forEach(customer => {
pivotData[customer] = {};
weeks.forEach(week => {
pivotData[customer][week] = 0;
});
});
data.forEach(item => {
const customer = item.customer_name;
const week = item.week_display;
let value = 0;
switch(metric) {
case 'orders':
value = item.order_count || 0;
break;
case 'quantity':
value = item.total_quantity || 0;
break;
case 'open':
value = item.open_quantity || 0;
break;
case 'completed':
value = item.completed_quantity || 0;
break;
}
if (pivotData[customer] && pivotData[customer][week] !== undefined) {
pivotData[customer][week] = value;
}
});
// Render table header
const thead = document.getElementById('pivotTableHead');
thead.innerHTML = `
<tr>
<th>Customer</th>
${weeks.map(week => `<th>${week}</th>`).join('')}
<th>Total</th>
</tr>
`;
// Render table body
const tbody = document.getElementById('pivotTableBody');
if (customers.length === 0) {
tbody.innerHTML = '<tr><td colspan="100%" class="text-center">No data available</td></tr>';
} else {
tbody.innerHTML = customers.map(customer => {
const rowTotal = weeks.reduce((sum, week) => sum + (pivotData[customer][week] || 0), 0);
return `
<tr>
<td>${customer}</td>
${weeks.map(week => {
const value = pivotData[customer][week] || 0;
return `<td class="pivot-cell-orders">${value > 0 ? value.toLocaleString() : '-'}</td>`;
}).join('')}
<td class="number pivot-cell-orders"><strong>${rowTotal.toLocaleString()}</strong></td>
</tr>
`;
}).join('');
// Add totals row
const totalRow = `
<tr style="background: #f8f9fa; font-weight: bold;">
<td>Total</td>
${weeks.map(week => {
const total = customers.reduce((sum, customer) => sum + (pivotData[customer][week] || 0), 0);
return `<td class="pivot-cell-orders">${total.toLocaleString()}</td>`;
}).join('')}
<td class="number pivot-cell-orders">
${customers.reduce((sum, customer) => {
return sum + weeks.reduce((s, week) => s + (pivotData[customer][week] || 0), 0);
}, 0).toLocaleString()}
</td>
</tr>
`;
tbody.innerHTML += totalRow;
}
}
function toggleDataTable(tableId) {
const table = document.getElementById(tableId);
const btn = event.target.closest('.chart-toggle-btn');
if (table.classList.contains('active')) {
table.classList.remove('active');
btn.innerHTML = '<i class="fas fa-table"></i> Show Data Table';
} else {
table.classList.add('active');
btn.innerHTML = '<i class="fas fa-chart-line"></i> Hide Data Table';
}
}
function scrollToChart(chartId) {
const element = document.getElementById(chartId);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
</script>
{% endblock %}