add video backend server, fix styling and more #54

Merged
quoije merged 131 commits from 2026-video.backend into prod 2025-12-22 05:42:04 -05:00
2 changed files with 95 additions and 40 deletions
Showing only changes of commit e6f3998d88 - Show all commits
+26 -5
View File
@@ -1,5 +1,7 @@
window.onload = async function () { window.onload = async function () {
const token = localStorage.getItem('auth_token'); const token = localStorage.getItem('auth_token');
let winnersCache = null;
let completionsCache = null;
async function fetchActiveYear() { async function fetchActiveYear() {
try { try {
@@ -172,6 +174,11 @@ window.onload = async function () {
fetchCompletions(), fetchCompletions(),
]); ]);
winnersCache = winners;
completionsCache = completions;
renderWinners(winnersCache);
renderCompletions(completionsCache);
if (!statsRes.ok) { if (!statsRes.ok) {
throw new Error('Failed to fetch user stats'); throw new Error('Failed to fetch user stats');
} }
@@ -180,9 +187,7 @@ window.onload = async function () {
const userTableBody = document.getElementById('user-table-body'); const userTableBody = document.getElementById('user-table-body');
const table = document.querySelector('.user-table'); const table = document.querySelector('.user-table');
const spinner = document.getElementById('loading-spinner'); const spinner = document.getElementById('loading-spinner');
const statsError = document.getElementById('stats-error');
renderWinners(winners);
renderCompletions(completions);
// Ensure watchedRatio is treated as a number and sort in descending order // Ensure watchedRatio is treated as a number and sort in descending order
stats.sort((a, b) => parseFloat(b.watchedRatio) - parseFloat(a.watchedRatio)); stats.sort((a, b) => parseFloat(b.watchedRatio) - parseFloat(a.watchedRatio));
@@ -205,6 +210,7 @@ window.onload = async function () {
// Hide spinner and show table after loading // Hide spinner and show table after loading
spinner.style.display = 'none'; spinner.style.display = 'none';
table.style.display = 'table'; table.style.display = 'table';
if (statsError) statsError.classList.add('d-none');
// Add event listener for user links // Add event listener for user links
document.querySelectorAll('.user-link').forEach(link => { document.querySelectorAll('.user-link').forEach(link => {
@@ -233,9 +239,24 @@ window.onload = async function () {
}); });
} catch (error) { } catch (error) {
console.error('Error loading user statistics:', error); console.error('Error loading user statistics:', error);
const spinner = document.getElementById('loading-spinner');
const table = document.querySelector('.user-table');
const statsError = document.getElementById('stats-error');
if (spinner) spinner.style.display = 'none';
if (table) table.style.display = 'none';
if (statsError) {
statsError.textContent = "Impossible de charger le classement pour le moment.";
statsError.classList.remove('d-none');
}
// Still attempt to render these sections even if stats fail (best effort) // Still attempt to render these sections even if stats fail (best effort)
try { renderWinners(await fetchWinners()); } catch (_) {} try {
try { renderCompletions(await fetchCompletions()); } catch (_) {} if (winnersCache === null) winnersCache = await fetchWinners();
renderWinners(winnersCache);
} catch (_) {}
try {
if (completionsCache === null) completionsCache = await fetchCompletions();
renderCompletions(completionsCache);
} catch (_) {}
} }
// Log-off functionality // Log-off functionality
+50 -16
View File
@@ -30,6 +30,14 @@
.navbar-toggler-icon { .navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
} }
/* Stats tabs */
.stats-tabs .nav-link {
font-weight: 600;
}
.stats-tab-panel {
padding-top: 1rem;
}
</style> </style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
@@ -62,23 +70,29 @@
<div class="container mt-5"> <div class="container mt-5">
<h2>Statistiques des utilisateurs</h2> <h2>Statistiques des utilisateurs</h2>
<!-- Winners section --> <!-- Tabs -->
<div class="mt-4"> <ul class="nav nav-tabs stats-tabs mt-3" id="statsTabs" role="tablist">
<h4 class="mb-2">Gagnants</h4> <li class="nav-item" role="presentation">
<div class="text-muted mb-2">Définis dans l’admin (optionnel).</div> <button class="nav-link active" id="tab-ranking" data-bs-toggle="tab" data-bs-target="#pane-ranking" type="button" role="tab" aria-controls="pane-ranking" aria-selected="true">
<div id="winners-list" class="list-group mb-4"> Classement
<div class="list-group-item text-muted">Chargement…</div> </button>
</div> </li>
</div> <li class="nav-item" role="presentation">
<button class="nav-link" id="tab-winners" data-bs-toggle="tab" data-bs-target="#pane-winners" type="button" role="tab" aria-controls="pane-winners" aria-selected="false">
Gagnants
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-completion" data-bs-toggle="tab" data-bs-target="#pane-completion" type="button" role="tab" aria-controls="pane-completion" aria-selected="false">
100%
</button>
</li>
</ul>
<!-- 100% completers section --> <div class="tab-content stats-tab-panel" id="statsTabsContent">
<div class="mt-4"> <!-- Ranking tab -->
<h4 class="mb-2">Utilisateurs ayant terminé 100% (par année)</h4> <div class="tab-pane fade show active" id="pane-ranking" role="tabpanel" aria-labelledby="tab-ranking" tabindex="0">
<div class="text-muted mb-2">Completion = 100% pour l’année (tous les films de l’année vus).</div> <div id="stats-error" class="alert alert-danger d-none" role="alert"></div>
<div id="completers-by-year" class="mb-4">
<div class="text-muted">Chargement…</div>
</div>
</div>
<!-- Spinner shown while loading --> <!-- Spinner shown while loading -->
<div id="loading-spinner" class="spinner-border text-primary" role="status"> <div id="loading-spinner" class="spinner-border text-primary" role="status">
@@ -98,6 +112,26 @@
<!-- User rows will be inserted here --> <!-- User rows will be inserted here -->
</tbody> </tbody>
</table> </table>
</div>
<!-- Winners tab -->
<div class="tab-pane fade" id="pane-winners" role="tabpanel" aria-labelledby="tab-winners" tabindex="0">
<h4 class="mb-2">Gagnants</h4>
<div class="text-muted mb-2">Définis dans l’admin (optionnel).</div>
<div id="winners-list" class="list-group mb-4">
<div class="list-group-item text-muted">Chargement…</div>
</div>
</div>
<!-- 100% completers tab -->
<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>
<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="text-muted">Chargement…</div>
</div>
</div>
</div>
</div> </div>
<!-- Modal for displaying movies watched --> <!-- Modal for displaying movies watched -->