380 lines
13 KiB
HTML
380 lines
13 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Performance Analytics & Stats - Karung.AI{% endblock %}
|
|
|
|
{% block extra_head %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
.analytics-layout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 28px;
|
|
}
|
|
|
|
/* Metric Summary Grid */
|
|
.metric-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
|
|
.metric-card {
|
|
background-color: var(--bg-card);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: var(--radius-lg);
|
|
padding: 24px;
|
|
box-shadow: var(--shadow-sm);
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
min-height: 140px;
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.metric-card:hover {
|
|
box-shadow: var(--shadow-md);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.metric-title {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.metric-title i {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 36px;
|
|
font-weight: 700;
|
|
letter-spacing: -1px;
|
|
color: var(--text-primary);
|
|
margin: 12px 0 4px 0;
|
|
}
|
|
|
|
.metric-subtext {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.metric-subtext strong {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
/* Chart Section */
|
|
.chart-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.chart-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 16px;
|
|
}
|
|
|
|
.chart-title-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.chart-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
.chart-subtitle {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.chart-filters {
|
|
display: flex;
|
|
background-color: var(--bg-base);
|
|
padding: 4px;
|
|
border-radius: var(--radius-md);
|
|
border: 1px solid var(--border-color);
|
|
gap: 2px;
|
|
}
|
|
|
|
.filter-btn {
|
|
background: none;
|
|
border: none;
|
|
padding: 6px 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
color: var(--text-secondary);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.filter-btn:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.filter-btn.active {
|
|
background-color: var(--bg-card);
|
|
color: var(--accent-blue);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 380px;
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="analytics-layout">
|
|
<!-- Top Row: Metrics Overview -->
|
|
<div class="metric-grid">
|
|
<!-- Card 1: Today's Count -->
|
|
<div class="metric-card">
|
|
<div class="metric-title">
|
|
<i class="fa-solid fa-calendar-day" style="color: var(--accent-blue);"></i> Today's Sacks
|
|
</div>
|
|
<div class="metric-value" id="todayCount">0</div>
|
|
<div class="metric-subtext" id="todayBatches">0 batches recorded</div>
|
|
</div>
|
|
|
|
<!-- Card 2: Yesterday's Count -->
|
|
<div class="metric-card">
|
|
<div class="metric-title">
|
|
<i class="fa-solid fa-history" style="color: var(--text-secondary);"></i> Yesterday's Sacks
|
|
</div>
|
|
<div class="metric-value" id="yesterdayCount">0</div>
|
|
<div class="metric-subtext" id="yesterdayBatches">0 batches recorded</div>
|
|
</div>
|
|
|
|
<!-- Card 3: Daily Average -->
|
|
<div class="metric-card">
|
|
<div class="metric-title">
|
|
<i class="fa-solid fa-calculator" style="color: var(--accent-green);"></i> Daily Average
|
|
</div>
|
|
<div class="metric-value" id="avgCount">0</div>
|
|
<div class="metric-subtext" id="recordedDays">0 days recorded in total</div>
|
|
</div>
|
|
|
|
<!-- Card 4: Best Day Record -->
|
|
<div class="metric-card">
|
|
<div class="metric-title">
|
|
<i class="fa-solid fa-trophy" style="color: var(--accent-orange);"></i> All-Time Record
|
|
</div>
|
|
<div class="metric-value" id="bestCount">0</div>
|
|
<div class="metric-subtext" id="bestDate">Best day: --</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Row: Chart -->
|
|
<div class="card chart-card">
|
|
<div class="chart-header">
|
|
<div class="chart-title-group">
|
|
<h3 class="chart-title">Counting Trends</h3>
|
|
<span class="chart-subtitle">Daily count output and total batch processing volumes</span>
|
|
</div>
|
|
<div class="chart-filters">
|
|
<button class="filter-btn active" data-days="7" onclick="loadChartData(7)">7 Days</button>
|
|
<button class="filter-btn" data-days="30" onclick="loadChartData(30)">30 Days</button>
|
|
<button class="filter-btn" data-days="90" onclick="loadChartData(90)">90 Days</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<canvas id="mainChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let mainChart = null;
|
|
let chartDays = 7;
|
|
|
|
async function loadMetrics() {
|
|
try {
|
|
const res = await fetch('/api/summary');
|
|
const data = await res.json();
|
|
|
|
document.getElementById('todayCount').textContent = (data.today.total_count || 0).toLocaleString();
|
|
document.getElementById('todayBatches').textContent = `${data.today.total_batches || 0} batches recorded`;
|
|
|
|
document.getElementById('yesterdayCount').textContent = (data.yesterday.total_count || 0).toLocaleString();
|
|
document.getElementById('yesterdayBatches').textContent = `${data.yesterday.total_batches || 0} batches recorded`;
|
|
|
|
document.getElementById('avgCount').textContent = Math.round(data.average_per_day || 0).toLocaleString();
|
|
document.getElementById('recordedDays').textContent = `${data.all_time.total_days || 0} active days recorded`;
|
|
|
|
document.getElementById('bestCount').textContent = (data.best_day.count || 0).toLocaleString();
|
|
document.getElementById('bestDate').textContent = data.best_day.date ? `Record date: ${data.best_day.date}` : 'Record date: --';
|
|
} catch (err) {
|
|
console.error('Failed to load metrics summary:', err);
|
|
}
|
|
}
|
|
|
|
async function loadChartData(days) {
|
|
chartDays = days;
|
|
|
|
// Update active class on buttons
|
|
document.querySelectorAll('.chart-filters button').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
if (parseInt(btn.getAttribute('data-days')) === days) {
|
|
btn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
try {
|
|
const res = await fetch(`/api/daily-data?days=${days}`);
|
|
const data = await res.json();
|
|
|
|
const labels = data.map(d => {
|
|
const date = new Date(d.date);
|
|
return date.toLocaleDateString('en-US', { day: 'numeric', month: 'short' });
|
|
});
|
|
const counts = data.map(d => d.total_count);
|
|
const batches = data.map(d => d.total_batches);
|
|
|
|
if (mainChart) mainChart.destroy();
|
|
|
|
const ctx = document.getElementById('mainChart').getContext('2d');
|
|
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
|
|
// Dynamic theme colors
|
|
const gridColor = isDark ? '#2C2C2E' : '#E5E7EB';
|
|
const textColor = isDark ? '#8E8E93' : '#86868B';
|
|
|
|
const barBg = isDark ? 'rgba(10, 132, 255, 0.7)' : 'rgba(0, 113, 227, 0.7)';
|
|
const barBorder = isDark ? '#0A84FF' : '#0071E3';
|
|
|
|
const lineBorder = isDark ? '#30D158' : '#34C759';
|
|
|
|
mainChart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'Total Sacks',
|
|
data: counts,
|
|
backgroundColor: barBg,
|
|
borderColor: barBorder,
|
|
borderWidth: 1.5,
|
|
borderRadius: 6,
|
|
yAxisID: 'y'
|
|
},
|
|
{
|
|
label: 'Total Batches',
|
|
data: batches,
|
|
type: 'line',
|
|
borderColor: lineBorder,
|
|
backgroundColor: 'transparent',
|
|
borderWidth: 2.5,
|
|
pointRadius: 4,
|
|
pointBackgroundColor: lineBorder,
|
|
tension: 0.35,
|
|
yAxisID: 'y1'
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: {
|
|
usePointStyle: true,
|
|
padding: 16,
|
|
font: {
|
|
size: 11,
|
|
family: 'Inter, sans-serif'
|
|
},
|
|
color: textColor
|
|
}
|
|
},
|
|
tooltip: {
|
|
padding: 12,
|
|
cornerRadius: 8,
|
|
titleFont: { size: 12, family: 'Inter, sans-serif', weight: 'bold' },
|
|
bodyFont: { size: 11, family: 'Inter, sans-serif' }
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: {
|
|
font: { size: 10, family: 'Inter, sans-serif' },
|
|
color: textColor
|
|
}
|
|
},
|
|
y: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
grid: { color: gridColor },
|
|
ticks: {
|
|
color: textColor,
|
|
font: { size: 10, family: 'Inter, sans-serif' }
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: 'Sacks Count',
|
|
color: textColor,
|
|
font: { size: 11, family: 'Inter, sans-serif', weight: 'bold' }
|
|
}
|
|
},
|
|
y1: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
grid: { display: false },
|
|
ticks: {
|
|
color: textColor,
|
|
font: { size: 10, family: 'Inter, sans-serif' }
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: 'Batches Count',
|
|
color: textColor,
|
|
font: { size: 11, family: 'Inter, sans-serif', weight: 'bold' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error('Failed to load chart:', err);
|
|
}
|
|
}
|
|
|
|
// Redraw charts dynamically on theme switch
|
|
window.addEventListener('themeChanged', () => {
|
|
loadChartData(chartDays);
|
|
});
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
loadMetrics();
|
|
loadChartData(7);
|
|
});
|
|
</script>
|
|
{% endblock %}
|