Statistique page cleanup #31
+99
-60
@@ -41,11 +41,15 @@ window.onload = async function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderWinners(winners) {
|
function renderWinners(winners) {
|
||||||
const winnersList = document.getElementById('winners-list');
|
const tbody = document.getElementById('winners-table-body');
|
||||||
if (!winnersList) return;
|
if (!tbody) return;
|
||||||
const list = Array.isArray(winners) ? winners : [];
|
const list = Array.isArray(winners) ? winners : [];
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
winnersList.innerHTML = '<div class="list-group-item text-muted">Aucun gagnant défini.</div>';
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-muted">Aucun gagnant défini.</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +68,7 @@ window.onload = async function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const years = Array.from(byYear.keys()).map(Number).sort((a, b) => b - a);
|
const years = Array.from(byYear.keys()).map(Number).sort((a, b) => b - a);
|
||||||
winnersList.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
years.forEach((year) => {
|
years.forEach((year) => {
|
||||||
const winnersForYear = byYear.get(String(year)) || [];
|
const winnersForYear = byYear.get(String(year)) || [];
|
||||||
@@ -77,35 +81,56 @@ window.onload = async function () {
|
|||||||
return String(a.name || '').localeCompare(String(b.name || ''), 'fr', { sensitivity: 'base' });
|
return String(a.name || '').localeCompare(String(b.name || ''), 'fr', { sensitivity: 'base' });
|
||||||
});
|
});
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const tr = document.createElement('tr');
|
||||||
item.className = 'list-group-item';
|
|
||||||
const tieLabel = sorted.length > 1 ? ' <span class="text-muted">(égalité)</span>' : '';
|
|
||||||
|
|
||||||
const winnersHtml = sorted
|
const tdYear = document.createElement('td');
|
||||||
.map((w) => {
|
tdYear.className = 'fw-semibold';
|
||||||
const pts = w.points === null || Number.isNaN(w.points) ? null : w.points;
|
tdYear.textContent = String(year);
|
||||||
const ptsLabel = pts === null ? '' : ` <span class="text-muted">— ${pts} pts</span>`;
|
if (sorted.length > 1) {
|
||||||
return `<div><strong>${w.name}</strong>${ptsLabel}</div>`;
|
const small = document.createElement('div');
|
||||||
})
|
small.className = 'text-muted small';
|
||||||
.join('');
|
small.textContent = 'égalité';
|
||||||
|
tdYear.appendChild(document.createElement('br'));
|
||||||
|
tdYear.appendChild(small);
|
||||||
|
}
|
||||||
|
|
||||||
item.innerHTML = `
|
const tdWinners = document.createElement('td');
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
sorted.forEach((w, idx) => {
|
||||||
<div class="fw-semibold">${year}${tieLabel}</div>
|
const row = document.createElement('div');
|
||||||
<span class="badge bg-secondary">${sorted.length}</span>
|
if (idx > 0) row.classList.add('mt-1');
|
||||||
</div>
|
|
||||||
<div class="mt-2">${winnersHtml}</div>
|
const name = document.createElement('span');
|
||||||
`;
|
name.className = 'fw-semibold';
|
||||||
winnersList.appendChild(item);
|
name.textContent = w?.name || '(utilisateur supprimé)';
|
||||||
|
row.appendChild(name);
|
||||||
|
|
||||||
|
const pts = w.points === null || Number.isNaN(w.points) ? null : w.points;
|
||||||
|
if (pts !== null) {
|
||||||
|
const ptsSpan = document.createElement('span');
|
||||||
|
ptsSpan.className = 'text-muted';
|
||||||
|
ptsSpan.textContent = ` — ${pts} pts`;
|
||||||
|
row.appendChild(ptsSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
tdWinners.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
tr.appendChild(tdYear);
|
||||||
|
tr.appendChild(tdWinners);
|
||||||
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCompletions(completions) {
|
function renderCompletions(completions) {
|
||||||
const root = document.getElementById('completers-by-year');
|
const tbody = document.getElementById('completers-table-body');
|
||||||
if (!root) return;
|
if (!tbody) return;
|
||||||
|
|
||||||
if (!completions || typeof completions !== 'object') {
|
if (!completions || typeof completions !== 'object') {
|
||||||
root.innerHTML = '<div class="text-muted">Impossible de charger les finisseurs 100%.</div>';
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-muted">Impossible de charger les finisseurs 100%.</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,48 +139,62 @@ window.onload = async function () {
|
|||||||
const byYear = completions?.completersByYear && typeof completions.completersByYear === 'object' ? completions.completersByYear : {};
|
const byYear = completions?.completersByYear && typeof completions.completersByYear === 'object' ? completions.completersByYear : {};
|
||||||
|
|
||||||
if (!years.length) {
|
if (!years.length) {
|
||||||
root.innerHTML = '<div class="text-muted">Aucune année trouvée.</div>';
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-muted">Aucune année trouvée.</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a simple accordion
|
const sortedYears = years
|
||||||
const accordionId = 'completersAccordion';
|
.map((y) => Number(y))
|
||||||
root.innerHTML = `<div class="accordion" id="${accordionId}"></div>`;
|
.filter((y) => Number.isInteger(y))
|
||||||
const acc = root.querySelector('.accordion');
|
.sort((a, b) => b - a);
|
||||||
|
|
||||||
years.forEach((y, idx) => {
|
tbody.innerHTML = '';
|
||||||
|
sortedYears.forEach((y) => {
|
||||||
const yearStr = String(y);
|
const yearStr = String(y);
|
||||||
const total = Number(totals?.[yearStr] ?? 0);
|
const total = Number(totals?.[yearStr] ?? 0);
|
||||||
const completers = Array.isArray(byYear?.[yearStr]) ? byYear[yearStr] : [];
|
const completers = Array.isArray(byYear?.[yearStr]) ? byYear[yearStr] : [];
|
||||||
const count = completers.length;
|
const count = completers.length;
|
||||||
|
|
||||||
const collapseId = `completers-${yearStr}`;
|
const tr = document.createElement('tr');
|
||||||
const headingId = `heading-${yearStr}`;
|
|
||||||
const show = idx === 0 ? 'show' : '';
|
|
||||||
const collapsed = idx === 0 ? '' : 'collapsed';
|
|
||||||
const expanded = idx === 0 ? 'true' : 'false';
|
|
||||||
|
|
||||||
const bodyHtml = count
|
const tdYear = document.createElement('td');
|
||||||
? `<ul class="list-group list-group-flush">
|
tdYear.className = 'fw-semibold';
|
||||||
${completers.map((u) => `<li class="list-group-item">${u?.name || '(sans nom)'}</li>`).join('')}
|
tdYear.textContent = yearStr;
|
||||||
</ul>`
|
|
||||||
: `<div class="text-muted">Aucun utilisateur à 100%.</div>`;
|
|
||||||
|
|
||||||
const item = document.createElement('div');
|
const tdCount = document.createElement('td');
|
||||||
item.className = 'accordion-item';
|
const badge = document.createElement('span');
|
||||||
item.innerHTML = `
|
badge.className = 'badge bg-secondary';
|
||||||
<h2 class="accordion-header" id="${headingId}">
|
badge.textContent = String(count);
|
||||||
<button class="accordion-button ${collapsed}" type="button" data-bs-toggle="collapse" data-bs-target="#${collapseId}" aria-expanded="${expanded}" aria-controls="${collapseId}">
|
tdCount.appendChild(badge);
|
||||||
${yearStr} — ${count} finisseur(s) (total films: ${total})
|
|
||||||
</button>
|
const tdTotal = document.createElement('td');
|
||||||
</h2>
|
tdTotal.textContent = String(Number.isFinite(total) ? total : 0);
|
||||||
<div id="${collapseId}" class="accordion-collapse collapse ${show}" aria-labelledby="${headingId}" data-bs-parent="#${accordionId}">
|
|
||||||
<div class="accordion-body">
|
const tdNames = document.createElement('td');
|
||||||
${bodyHtml}
|
if (!count) {
|
||||||
</div>
|
tdNames.className = 'text-muted';
|
||||||
</div>
|
tdNames.textContent = '—';
|
||||||
`;
|
} else {
|
||||||
acc.appendChild(item);
|
const wrap = document.createElement('div');
|
||||||
|
wrap.className = 'd-flex flex-wrap gap-1';
|
||||||
|
completers.forEach((u) => {
|
||||||
|
const name = document.createElement('span');
|
||||||
|
name.className = 'badge bg-light text-dark border';
|
||||||
|
name.textContent = u?.name || '(sans nom)';
|
||||||
|
wrap.appendChild(name);
|
||||||
|
});
|
||||||
|
tdNames.appendChild(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.appendChild(tdYear);
|
||||||
|
tr.appendChild(tdCount);
|
||||||
|
tr.appendChild(tdTotal);
|
||||||
|
tr.appendChild(tdNames);
|
||||||
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +280,8 @@ window.onload = async function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Hide spinner and show table after loading
|
// Hide spinner and show table after loading
|
||||||
spinner.style.display = 'none';
|
if (spinner) spinner.classList.add('d-none');
|
||||||
table.style.display = 'table';
|
if (table) table.classList.remove('d-none');
|
||||||
if (statsError) statsError.classList.add('d-none');
|
if (statsError) statsError.classList.add('d-none');
|
||||||
|
|
||||||
// Add event listener for user links
|
// Add event listener for user links
|
||||||
@@ -275,8 +314,8 @@ window.onload = async function () {
|
|||||||
const spinner = document.getElementById('loading-spinner');
|
const spinner = document.getElementById('loading-spinner');
|
||||||
const table = document.querySelector('.user-table');
|
const table = document.querySelector('.user-table');
|
||||||
const statsError = document.getElementById('stats-error');
|
const statsError = document.getElementById('stats-error');
|
||||||
if (spinner) spinner.style.display = 'none';
|
if (spinner) spinner.classList.add('d-none');
|
||||||
if (table) table.style.display = 'none';
|
if (table) table.classList.add('d-none');
|
||||||
if (statsError) {
|
if (statsError) {
|
||||||
statsError.textContent = "Impossible de charger le classement pour le moment.";
|
statsError.textContent = "Impossible de charger le classement pour le moment.";
|
||||||
statsError.classList.remove('d-none');
|
statsError.classList.remove('d-none');
|
||||||
|
|||||||
+31
-10
@@ -20,10 +20,6 @@
|
|||||||
.user-table {
|
.user-table {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
.ratio {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.navbar-toggler {
|
.navbar-toggler {
|
||||||
border-color: white; /* Make the border white */
|
border-color: white; /* Make the border white */
|
||||||
}
|
}
|
||||||
@@ -100,7 +96,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Table for users and movies watched -->
|
<!-- Table for users and movies watched -->
|
||||||
<table class="table user-table" style="display: none;">
|
<table class="table table-striped table-hover align-middle user-table d-none">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Nom</th>
|
<th scope="col">Nom</th>
|
||||||
@@ -117,9 +113,20 @@
|
|||||||
<!-- Winners tab -->
|
<!-- Winners tab -->
|
||||||
<div class="tab-pane fade" id="pane-winners" role="tabpanel" aria-labelledby="tab-winners" tabindex="0">
|
<div class="tab-pane fade" id="pane-winners" role="tabpanel" aria-labelledby="tab-winners" tabindex="0">
|
||||||
<h4 class="mb-2">Gagnants</h4>
|
<h4 class="mb-2">Gagnants</h4>
|
||||||
<div class="text-muted mb-2">Définis dans l’admin (optionnel).</div>
|
<div class="table-responsive mb-4">
|
||||||
<div id="winners-list" class="list-group mb-4">
|
<table class="table table-sm table-striped align-middle mb-0">
|
||||||
<div class="list-group-item text-muted">Chargement…</div>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style="width: 110px;">Année</th>
|
||||||
|
<th scope="col">Gagnant(s)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="winners-table-body">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-muted">Chargement…</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -127,8 +134,22 @@
|
|||||||
<div class="tab-pane fade" id="pane-completion" role="tabpanel" aria-labelledby="tab-completion" tabindex="0">
|
<div class="tab-pane fade" id="pane-completion" role="tabpanel" aria-labelledby="tab-completion" tabindex="0">
|
||||||
<h4 class="mb-2">Utilisateurs ayant terminé 100% (par année)</h4>
|
<h4 class="mb-2">Utilisateurs ayant terminé 100% (par année)</h4>
|
||||||
<div class="text-muted mb-2">Completion = 100% pour l’année (tous les films de l’année vus).</div>
|
<div class="text-muted mb-2">Completion = 100% pour l’année (tous les films de l’année vus).</div>
|
||||||
<div id="completers-by-year" class="mb-4">
|
<div class="table-responsive mb-4">
|
||||||
<div class="text-muted">Chargement…</div>
|
<table class="table table-sm table-striped align-middle mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style="width: 110px;">Année</th>
|
||||||
|
<th scope="col" style="width: 140px;">Finisseur(s)</th>
|
||||||
|
<th scope="col" style="width: 140px;">Total films</th>
|
||||||
|
<th scope="col">Noms</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="completers-table-body">
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-muted">Chargement…</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user