diff --git a/public/js/app_checklist.js b/public/js/app_checklist.js
index b0779ab..b5ee5a4 100644
--- a/public/js/app_checklist.js
+++ b/public/js/app_checklist.js
@@ -1,6 +1,7 @@
window.onload = async function () {
const token = localStorage.getItem('auth_token');
const movieTableBody = document.getElementById('movie-table-body');
+ const watchedRatioEl = document.getElementById('watched-ratio');
function showTableLoading() {
if (!movieTableBody) return;
@@ -18,6 +19,18 @@ window.onload = async function () {
`;
}
+ function setInlineLoading(el, label = 'Chargement…') {
+ if (!el) return;
+ el.innerHTML = `
+
+
+ Chargement...
+
+ ${label}
+
+ `;
+ }
+
function showTableError(message) {
if (!movieTableBody) return;
movieTableBody.innerHTML = `
@@ -135,29 +148,52 @@ window.onload = async function () {
return Number.isNaN(d.getTime()) ? null : d;
}
+ async function fetchMoviesSummary(year) {
+ const res = await fetch(`/api/movies/summary?year=${encodeURIComponent(String(year))}`, {
+ method: 'GET',
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ if (!res.ok) throw new Error('Failed to fetch movies summary');
+ return await res.json();
+ }
+
const activeYear = await fetchActiveYear();
document.title = `Pool Oscars ${activeYear} - Checklist`;
const oscarYearEl = document.getElementById('oscar-year');
if (oscarYearEl) oscarYearEl.textContent = String(activeYear);
- const oscarEffectiveDate = await fetchOscarEffectiveDate(activeYear);
- const targetDate =
- parseLocalNoonFromIsoDate(oscarEffectiveDate) ||
- new Date(`March 15, ${activeYear} 12:00:00`);
-
+ // Render countdown immediately with a deterministic fallback (then refine from settings).
const oscarDayMonthEl = document.getElementById('oscar-date-day-month');
+ const timeLeftEl = document.getElementById('time-left');
+ const fallbackDate = parseLocalNoonFromIsoDate(`${activeYear}-03-15`) || new Date(`March 15, ${activeYear} 12:00:00`);
if (oscarDayMonthEl) {
try {
- oscarDayMonthEl.textContent = targetDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long' });
+ oscarDayMonthEl.textContent = fallbackDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long' });
} catch (_) {
oscarDayMonthEl.textContent = '15 mars';
}
}
const currentDate = new Date();
- const timeDifference = targetDate - currentDate;
- const daysLeft = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
- document.getElementById('time-left').textContent = `${daysLeft} jours`;
+ let timeDifference = fallbackDate - currentDate;
+ let daysLeft = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
+ if (timeLeftEl) timeLeftEl.textContent = `${daysLeft} jours`;
+
+ fetchOscarEffectiveDate(activeYear).then((oscarEffectiveDate) => {
+ const targetDate =
+ parseLocalNoonFromIsoDate(oscarEffectiveDate) ||
+ fallbackDate;
+
+ if (oscarDayMonthEl) {
+ try {
+ oscarDayMonthEl.textContent = targetDate.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long' });
+ } catch (_) {}
+ }
+ const now = new Date();
+ timeDifference = targetDate - now;
+ daysLeft = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
+ if (timeLeftEl) timeLeftEl.textContent = `${daysLeft} jours`;
+ }).catch(() => {});
if (token) {
try {
@@ -186,22 +222,35 @@ window.onload = async function () {
}
showTableLoading();
+ setInlineLoading(watchedRatioEl);
+ const progressBarEl = document.getElementById('progress-bar');
+ if (progressBarEl) {
+ progressBarEl.classList.add('progress-bar-striped', 'progress-bar-animated');
+ }
let movies = [];
let watchedMovies = [];
let watchedMoviesInYear = [];
let movieImdbIds = new Set();
try {
- // Fetch remaining data in parallel (Render latency is high).
- const [completionModalContent, moviesRes, watchedRes] = await Promise.all([
+ // Start summary fetch immediately so the header can update without waiting for the full list.
+ const summaryPromise = fetchMoviesSummary(activeYear)
+ .then((summary) => {
+ const totalCount = Number(summary?.totalMoviesCount) || 0;
+ const watchedCount = Number(summary?.watchedMoviesCount) || 0;
+ const ratioPct = totalCount > 0 ? ((watchedCount / totalCount) * 100).toFixed(1) : '0.0';
+ if (watchedRatioEl) watchedRatioEl.innerText = `Vu: ${watchedCount} / ${totalCount} (${ratioPct}%)`;
+ updateProgressBar(watchedCount, totalCount, false);
+ return summary;
+ })
+ .catch(() => null);
+
+ // Fetch remaining data in parallel.
+ const [completionModalContent, moviesRes] = await Promise.all([
fetchCompletionModalContent(),
fetch(`/api/movies?year=${encodeURIComponent(String(activeYear))}&view=checklist`, {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
- }),
- fetch('/api/movies/watchedMovies', {
- method: 'GET',
- headers: { 'Authorization': `Bearer ${token}` }
})
]);
@@ -218,8 +267,11 @@ window.onload = async function () {
// Sort movies alphabetically by title
movies.sort((a, b) => a.title.localeCompare(b.title, 'fr', { sensitivity: 'base' }));
- if (watchedRes.ok) {
- watchedMovies = await watchedRes.json();
+ const summary = await summaryPromise;
+ watchedMovies = Array.isArray(summary?.watchedMovies) ? summary.watchedMovies : [];
+
+ if (progressBarEl) {
+ progressBarEl.classList.remove('progress-bar-striped', 'progress-bar-animated');
}
} catch (e) {
showTableError('Impossible de charger la checklist. Réessaie dans quelques instants.');
@@ -259,10 +311,12 @@ window.onload = async function () {
frame();
}
- function updateProgressBar(watchedCount, totalCount) {
+ function updateProgressBar(watchedCount, totalCount, celebrate = true) {
const progressBar = document.getElementById('progress-bar');
- const percentage = (watchedCount / totalCount) * 100;
- console.log(percentage);
+ if (!progressBar) return;
+ const safeTotal = Number(totalCount);
+ const safeWatched = Number(watchedCount);
+ const percentage = safeTotal > 0 && Number.isFinite(safeWatched) ? (safeWatched / safeTotal) * 100 : 0;
progressBar.style.width = `${percentage}%`;
if (percentage <= 50) {
@@ -273,15 +327,16 @@ window.onload = async function () {
progressBar.setAttribute('aria-valuenow', Math.round(percentage));
- const videoModal = new bootstrap.Modal(document.getElementById("videoModal"));
-
- if (percentage === 100) {
- videoModal.show();
- launchConfetti();
- const rewardVideo = document.getElementById("rewardVideo");
- // Only attempt autoplay if we actually have a visible