Greg 1434e906fd feat: add season-aware views on main page and reports
- Main page: filter table by current season by default; show Next Season button when next season dates exist in DB
- Reports page: filter charts to current season; add Last Season toggle button when previous season data exists
- Admin archive: export all dates/players as CSV (no season filter)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 13:39:52 +01:00

185 lines
6.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Padel Nivelles - Reports</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial, sans-serif; background: #222831; color: #DFD0B8; margin: 2em; }
.charts-flex {
display: flex;
gap: 2em;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
}
.chart-container.large-chart {
background: #393E46;
padding: 1.5em 2em;
border-radius: 22px;
margin-bottom: 1em;
min-width: 400px;
max-width: 520px;
flex: 1 1 400px;
box-sizing: border-box;
}
h2 { color: #948979; font-size: 1.1em; margin-bottom: 0.5em; }
.btn {
color: #222831;
background: #948979;
border: none;
padding: 0.7em 1.5em;
border-radius: 18px;
font-weight: bold;
font-size: 1em;
transition: background 0.2s;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn:hover { background: #7c765c; }
.btn-secondary {
background: #393E46;
color: #DFD0B8;
border: 1px solid #948979;
}
.btn-secondary:hover { background: #948979; color: #222831; }
#season-label { color: #948979; font-size: 0.95em; }
@media (max-width: 900px) {
.charts-flex { flex-direction: column; align-items: stretch; }
.chart-container.large-chart { max-width: 100%; min-width: 0; }
}
@media (max-width: 600px) {
body { margin: 0.7em; }
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div style="display: flex; gap: 1em; align-items: center; margin-bottom: 1.5em; flex-wrap: wrap;">
<a href="/" class="btn">&#8592; Attendance</a>
<button id="last-season-btn" class="btn btn-secondary" style="display:none;"></button>
<span id="season-label"></span>
</div>
<div class="charts-flex">
<div class="chart-container large-chart">
<h2>Attendance Count per Player</h2>
<canvas id="barChart" width="480" height="400"></canvas>
</div>
<div class="chart-container large-chart">
<h2>Player Attendance Share (Pie)</h2>
<canvas id="pieChart" width="400" height="400"></canvas>
</div>
</div>
<script>
function getSeasonYear(dateStr) {
const [d, m, y] = dateStr.split('/').map(Number);
return m >= 4 ? 2000 + y : 1999 + y;
}
function currentSeasonYear() {
const now = new Date();
const m = now.getMonth() + 1;
return m >= 4 ? now.getFullYear() : now.getFullYear() - 1;
}
function seasonLabel(y) { return `Season ${y}/${y + 1}`; }
let barChart, pieChart;
function renderCharts(data, targetYear) {
const seasonDates = new Set((data.dates || []).filter(d => getSeasonYear(d) === targetYear));
const players = data.players;
const attendance = data.attendance;
const yesCounts = Array(players.length).fill(0);
Object.entries(attendance).forEach(([key, value]) => {
if (value === true) {
const [date, colIdx] = key.split('|');
if (seasonDates.has(date)) {
const idx = parseInt(colIdx);
if (idx < players.length) yesCounts[idx]++;
}
}
});
document.getElementById('season-label').textContent = seasonLabel(targetYear);
if (barChart) barChart.destroy();
if (pieChart) pieChart.destroy();
barChart = new Chart(document.getElementById('barChart').getContext('2d'), {
type: 'bar',
data: {
labels: players,
datasets: [{
label: 'Number of Yes',
data: yesCounts,
backgroundColor: '#948979',
borderColor: '#222831',
borderWidth: 1
}]
},
options: {
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, ticks: { color: '#DFD0B8' }, grid: { color: '#393E46' } },
x: { ticks: { color: '#DFD0B8' }, grid: { color: '#393E46' } }
}
}
});
const totalYes = yesCounts.reduce((a, b) => a + b, 0);
pieChart = new Chart(document.getElementById('pieChart').getContext('2d'), {
type: 'pie',
data: {
labels: players,
datasets: [{
data: yesCounts,
backgroundColor: ['#948979', '#DFD0B8', '#7c765c', '#393E46', '#222831', '#bfa181', '#b7b7a4', '#a7c957']
}]
},
options: {
plugins: {
legend: { labels: { color: '#DFD0B8' } },
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed;
const percent = totalYes ? ((value / totalYes) * 100).toFixed(1) : 0;
return label + ': ' + value + ' (' + percent + '%)';
}
}
}
}
}
});
}
fetch('/api/data').then(r => r.json()).then(data => {
const thisSeason = currentSeasonYear();
const seasons = [...new Set((data.dates || []).map(getSeasonYear))].sort();
const hasLastSeason = seasons.includes(thisSeason - 1);
let viewingLastSeason = false;
renderCharts(data, thisSeason);
const lastSeasonBtn = document.getElementById('last-season-btn');
if (hasLastSeason) {
lastSeasonBtn.style.display = '';
lastSeasonBtn.textContent = `Last Season (${thisSeason - 1}/${thisSeason})`;
lastSeasonBtn.onclick = () => {
viewingLastSeason = !viewingLastSeason;
const target = viewingLastSeason ? thisSeason - 1 : thisSeason;
renderCharts(data, target);
lastSeasonBtn.textContent = viewingLastSeason
? `Current Season (${thisSeason}/${thisSeason + 1})`
: `Last Season (${thisSeason - 1}/${thisSeason})`;
};
}
});
</script>
</body>
</html>