diff --git a/js/admin.js b/js/admin.js index 8a632e8..1457015 100644 --- a/js/admin.js +++ b/js/admin.js @@ -244,7 +244,7 @@ function saveFilm() { format: '', length: '', publisher: '', ean_isbn13: '', number_of_discs: '', aspect_ratio: '', description: '' }; -if (editingFilmId !== null) { + if (editingFilmId !== null) { const index = films.findIndex(f => f.id === editingFilmId); if (index !== -1) { films[index] = { @@ -256,7 +256,7 @@ if (editingFilmId !== null) { poster: document.getElementById('f-poster').value.trim(), rating: type === 'critique' ? (currentRating || 1) : 0, review: type === 'critique' ? document.getElementById('f-review').value.trim() : '', - streaming: films[index].streaming || "Disponible au cinéma ou support physique", // Conserve ou ajoute par défaut + streaming: films[index].streaming || "Disponible au cinéma ou support physique", ...metadataVideotheque }; } @@ -270,7 +270,7 @@ if (editingFilmId !== null) { poster: document.getElementById('f-poster').value.trim(), rating: type === 'critique' ? (currentRating || 1) : 0, review: type === 'critique' ? document.getElementById('f-review').value.trim() : '', - streaming: "Disponible au cinéma ou support physique", // Valeur par défaut à la création + streaming: "Disponible au cinéma ou support physique", ...metadataVideotheque }; films.unshift(film); @@ -295,7 +295,6 @@ function renderAdminTable() { if (!tbody) return; - // Reset select-all + bulk-delete button BEFORE rebuilding DOM const selectAll = document.getElementById('th-select-all'); if (selectAll) selectAll.checked = false; const btnBulkReset = document.getElementById('btn-bulk-delete'); @@ -321,7 +320,6 @@ function renderAdminTable() { if (emptyState) emptyState.style.display = 'none'; - // Update film count display const countEl = document.getElementById('films-count'); if (countEl) countEl.textContent = filteredFilms.length; @@ -395,7 +393,6 @@ function deleteSelectedFilms() { } } -// ── Letterboxd CSV parser (handles quoted commas correctly) ────────────── function parseLetterboxdCSV(text) { const rows = []; const lines = text.split(/\r\n|\n/); @@ -420,7 +417,6 @@ function parseLetterboxdCSV(text) { return rows; } -// ── Show import progress overlay ───────────────────────────────────────── function showImportProgress(total) { let overlay = document.getElementById('import-progress-overlay'); if (!overlay) { @@ -464,7 +460,6 @@ function hideImportProgress() { if (el) el.remove(); } -// ── TMDB poster lookup (single film) ───────────────────────────────────── async function fetchTmdbPoster(title, year) { const key = localStorage.getItem('tmdb-api-key'); if (!key) return ''; @@ -477,7 +472,6 @@ async function fetchTmdbPoster(title, year) { if (data.results && data.results.length > 0 && data.results[0].poster_path) { return `https://image.tmdb.org/t/p/w500${data.results[0].poster_path}`; } - // Retry without year if no result if (year) { const res2 = await fetch( `https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR` @@ -491,9 +485,7 @@ async function fetchTmdbPoster(title, year) { return ''; } -// ── DVD/Blu-ray cover: Open Library (EAN) → TMDB fallback ──────────────── async function fetchDvdCover(ean, title, year) { - // Step 1 — Open Library ISBN/EAN lookup (no key needed) if (ean) { try { const olRes = await fetch( @@ -503,7 +495,6 @@ async function fetchDvdCover(ean, title, year) { const key = `ISBN:${ean}`; if (olData[key]) { const book = olData[key]; - // Prefer large cover, fall back to medium/small if (book.cover) { const coverUrl = book.cover.large || book.cover.medium || book.cover.small || ''; if (coverUrl) return coverUrl; @@ -511,7 +502,6 @@ async function fetchDvdCover(ean, title, year) { } } catch (e) {} - // Step 1b — Open Library cover API directly by ISBN (faster path) try { const testUrl = `https://covers.openlibrary.org/b/isbn/${ean}-L.jpg?default=false`; const testRes = await fetch(testUrl); @@ -520,12 +510,9 @@ async function fetchDvdCover(ean, title, year) { } } catch (e) {} } - - // Step 2 — TMDB fallback (uses existing key) return await fetchTmdbPoster(title, year); } -// ── Main Letterboxd import (reviews.csv OR ratings.csv) ────────────────── function importFromCSV(input) { const file = input.files[0]; if (!file) return; @@ -542,10 +529,8 @@ function importFromCSV(input) { } const headers = rows[0].map(h => h.trim().toLowerCase()); - - // Detect file type: reviews.csv has a "Review" column, ratings.csv doesn't const isReviews = headers.includes('review'); - const iCol = { // column indices by header name + const iCol = { name: headers.indexOf('name'), year: headers.indexOf('year'), rating: headers.indexOf('rating'), @@ -553,7 +538,6 @@ function importFromCSV(input) { watchedDate: headers.indexOf('watched date'), }; - // Collect rows to import (skip duplicates first pass) const toImport = []; let duplicateCount = 0; @@ -564,7 +548,6 @@ function importFromCSV(input) { const year = iCol.year >= 0 ? (cols[iCol.year]?.trim() || '') : ''; const ratingRaw = iCol.rating >= 0 ? parseFloat(cols[iCol.rating]) : 0; - // Letterboxd uses 0.5–5 scale; convert to our 1–5 integer scale const rating = Math.round(Math.min(5, Math.max(1, ratingRaw))); const review = (isReviews && iCol.review >= 0) ? (cols[iCol.review]?.trim() || '') : ''; @@ -594,11 +577,10 @@ function importFromCSV(input) { let streaming = "Disponible au cinéma ou support physique"; if (hasTmdb) { - // On récupère les deux infos en un seul traitement + // Double appel unifié (Affiche + Streaming) const details = await fetchTmdbDetails(title, year); poster = details.poster; streaming = details.streaming; - // Augmentation légère du délai pour respecter les limites de l'API avec le double appel await new Promise(r => setTimeout(r, 350)); } @@ -611,8 +593,9 @@ function importFromCSV(input) { rating, review, poster, - streaming, // Ajout du nouveau champ - format: '', length: '', publisher: '', ean_isbn13: '', number_of_discs: '1', aspect_ratio: '', description: '' + streaming, + format: '', + length: '', publisher: '', ean_isbn13: '', number_of_discs: '1', aspect_ratio: '', description: '' }); importedCount++; } @@ -626,7 +609,7 @@ function importFromCSV(input) { renderAdminTable(); const typeLabel = isReviews ? 'reviews.csv (avec critiques)' : 'ratings.csv (notes seules)'; - const posterInfo = hasTmdb ? ' · Affiches récupérées via TMDB' : ' · ⚠️ Clé TMDB absente, affiches non récupérées'; + const posterInfo = hasTmdb ? ' · Affiches et plateformes récupérées via TMDB' : ' · ⚠️ Clé TMDB absente'; alert( `Import Letterboxd — ${typeLabel}\n` + `✅ ${importedCount} film(s) importé(s)${posterInfo}\n` + @@ -670,7 +653,6 @@ function importVideothequeFromInput(input) { } async function importVideothequeCSV(text) { - // ── Parse CSV (handles quoted commas) ── const lines = []; let row = [""]; let inQuotes = false; @@ -691,7 +673,6 @@ async function importVideothequeCSV(text) { if (row.length > 1 || row[0] !== '') lines.push(row); if (lines.length <= 1) return; - // ── Collect rows to import ── const toImport = []; let duplicateCount = 0; @@ -734,7 +715,6 @@ async function importVideothequeCSV(text) { return; } - // ── Progress bar ── showImportProgress(toImport.length); let importedCount = 0; let coversFound = 0; @@ -745,14 +725,12 @@ async function importVideothequeCSV(text) { const sourceLabel = ean ? `EAN ${ean}` : title; updateImportProgress(i, toImport.length, `📀 ${sourceLabel}`); - // ── Fetch cover: Open Library (EAN) → TMDB ── let poster = ''; try { poster = await fetchDvdCover(ean, title, year); if (poster) coversFound++; } catch(e) {} - // Respectful delay await new Promise(r => setTimeout(r, 300)); films.push({ @@ -824,7 +802,6 @@ async function fetchTmdbDetails(title, year) { try { const yearParam = year ? `&year=${year}` : ''; - // Step 1 : Recherche du film pour obtenir son ID TMDB const res = await fetch( `https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR${yearParam}` ); @@ -836,7 +813,6 @@ async function fetchTmdbDetails(title, year) { const posterPath = movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : ''; let streamingInfo = streamingDefault; - // Step 2 : Requête sur l'endpoint "watch/providers" pour avoir le streaming en France (FR) try { const watchRes = await fetch( `https://api.themoviedb.org/3/movie/${movieId}/watch/providers?api_key=${key}` @@ -845,13 +821,10 @@ async function fetchTmdbDetails(title, year) { if (watchData.results && watchData.results.FR) { const frProviders = watchData.results.FR; - // On privilégie les plateformes incluses dans un abonnement (flatrate), sinon à la location/achat const providers = frProviders.flatrate || frProviders.buy || frProviders.rent || []; if (providers.length > 0) { - // Extrait les noms des plateformes trouvées (ex: Netflix, Canal+, Disney Plus) const names = providers.map(p => p.provider_name); - // On supprime les doublons potentiels et on limite aux 3 premières pour l'affichage const uniqueNames = [...new Set(names)].slice(0, 3); streamingInfo = `Disponible sur : ${uniqueNames.join(', ')}`; }