feat(110): statistics summary

Phase 110 complete:
- add Statistics -> Podsumowanie page
- add monthly order count and value charts per integration plus total
- use Chart.js with table fallback and 04-2026 default history start
- update PAUL and DOCS technical documentation
This commit is contained in:
2026-04-28 22:47:14 +02:00
parent 1156ce046c
commit 0b4ffb7146
21 changed files with 2454 additions and 26 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,119 @@
(function () {
'use strict';
var palette = ['#2563eb', '#0f766e', '#c2410c', '#7c3aed', '#be123c', '#0369a1', '#4d7c0f', '#9333ea'];
var totalColor = '#111827';
function parseData() {
var node = document.getElementById('js-statistics-summary-data');
if (!node) return null;
try {
return JSON.parse(node.textContent || '{}');
} catch (error) {
return null;
}
}
function colorForSeries(item, index) {
return item.key === 'total' ? totalColor : palette[index % palette.length];
}
function moneyLabel(value) {
return Number(value || 0).toLocaleString('pl-PL', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
function datasetForSeries(item, index) {
var color = colorForSeries(item, index);
var isTotal = item.key === 'total';
return {
label: item.label || item.key || '',
data: item.values || [],
borderColor: color,
backgroundColor: color,
borderWidth: isTotal ? 3 : 2,
pointRadius: isTotal ? 4 : 3,
pointHoverRadius: isTotal ? 6 : 5,
tension: 0.25
};
}
function renderChart(container, chart) {
if (!window.Chart || !container || !chart || !Array.isArray(chart.labels) || !Array.isArray(chart.series)) return;
var canvas = container.querySelector('canvas');
if (!canvas) return;
var isMoney = chart.valueType === 'money';
var context = canvas.getContext('2d');
new window.Chart(context, {
type: 'line',
data: {
labels: chart.labels,
datasets: chart.series.map(datasetForSeries)
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 18,
boxHeight: 3,
usePointStyle: false
}
},
tooltip: {
callbacks: {
label: function (item) {
var label = item.dataset.label ? item.dataset.label + ': ' : '';
return label + (isMoney ? moneyLabel(item.parsed.y) : Math.round(item.parsed.y));
}
}
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
callback: function (value) {
return isMoney ? moneyLabel(value) : value;
}
}
}
}
}
});
}
function init() {
var data = parseData();
if (!data) return;
document.querySelectorAll('[data-statistics-chart]').forEach(function (container) {
var key = container.getAttribute('data-statistics-chart');
renderChart(container, data[key]);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();