- 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>
185 lines
6.7 KiB
HTML
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">← 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>
|