const ADMIN_PASSWORD = 'cinema2024'; const STORAGE_KEY = 'mon-cinema-films'; let films = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); let currentRating = 0; let editingFilmId = null; let currentTab = 'critique'; function persist() { localStorage.setItem(STORAGE_KEY, JSON.stringify(films)); } if (location.pathname.includes('dashboard.html') && sessionStorage.getItem('admin-auth') !== '1') { window.location.href = 'login.html'; } function doLogin() { const val = document.getElementById('login-pwd').value; if (val === ADMIN_PASSWORD) { sessionStorage.setItem('admin-auth', '1'); window.location.href = 'dashboard.html'; } else { document.getElementById('login-err').style.display = 'block'; } } function switchTab(tabName) { currentTab = tabName; document.getElementById('tab-critiques').classList.toggle('active', tabName === 'critique'); document.getElementById('tab-videotheque').classList.toggle('active', tabName === 'videotheque'); const thDynamic = document.getElementById('th-dynamic-col'); if (thDynamic) { thDynamic.textContent = tabName === 'critique' ? 'Note' : 'Format / Éditeur'; } const btnCritiqueExport = document.getElementById('actions-critiques-export'); const btnCritiqueImport = document.getElementById('actions-critiques-import'); const btnVideoExport = document.getElementById('actions-video-export'); const btnVideoImport = document.getElementById('actions-video-import'); if (tabName === 'videotheque') { if (btnCritiqueExport) btnCritiqueExport.style.display = 'none'; if (btnCritiqueImport) btnCritiqueImport.style.display = 'none'; if (btnVideoExport) btnVideoExport.style.display = 'inline-flex'; if (btnVideoImport) btnVideoImport.style.display = 'inline-flex'; } else { if (btnCritiqueExport) btnCritiqueExport.style.display = 'inline-flex'; if (btnCritiqueImport) btnCritiqueImport.style.display = 'inline-flex'; if (btnVideoExport) btnVideoExport.style.display = 'none'; if (btnVideoImport) btnVideoImport.style.display = 'none'; } renderAdminTable(); } function handleTypeChange(typeValue) { const videoFields = document.getElementById('form-group-videotheque-fields'); const critiqueFields = document.getElementById('form-group-critique-fields'); if (typeValue === 'videotheque') { if (videoFields) videoFields.style.display = 'block'; if (critiqueFields) critiqueFields.style.display = 'none'; } else { if (videoFields) videoFields.style.display = 'none'; if (critiqueFields) critiqueFields.style.display = 'block'; } } function setRating(n) { currentRating = n; const buttons = document.querySelectorAll('#star-select button'); buttons.forEach((btn, idx) => { btn.style.color = idx < n ? 'var(--gold)' : 'var(--muted)'; }); } function resetStars() { currentRating = 0; const buttons = document.querySelectorAll('#star-select button'); buttons.forEach(btn => btn.style.color = 'var(--muted)'); } function saveTmdbKey() { const key = document.getElementById('tmdb-key-input').value.trim(); localStorage.setItem('tmdb-api-key', key); alert('Clé TMDB enregistrée avec succès !'); } async function searchTMDB() { const query = document.getElementById('search-input').value.trim(); const key = localStorage.getItem('tmdb-api-key') || document.getElementById('tmdb-key-input').value.trim(); const resultsWrap = document.getElementById('search-results-wrap'); if (!key) { alert("Veuillez configurer votre clé API TMDB d'abord."); return; } if (!query) return; resultsWrap.innerHTML = '
Recherche en cours...
'; try { const res = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(query)}&language=fr-FR`); const data = await res.json(); if (!data.results || data.results.length === 0) { resultsWrap.innerHTML = '
Aucun résultat trouvé.
'; return; } resultsWrap.innerHTML = `
${data.results.slice(0, 5).map(movie => { const posterUrl = movie.poster_path ? `https://image.tmdb.org/t/p/w300${movie.poster_path}` : ''; const year = movie.release_date ? movie.release_date.substring(0, 4) : ''; const safeTitle = movie.title.replace(/'/g, "\\'").replace(/"/g, """); return `
${posterUrl ? `` : `
`} ${movie.title} (${year})
`; }).join('')}
`; } catch (err) { resultsWrap.innerHTML = '
Erreur lors de la recherche.
'; } } function selectTmdbMovie(title, year, poster) { document.getElementById('f-title').value = title; document.getElementById('f-year').value = year; if (poster) { document.getElementById('f-poster').value = poster; updatePosterPreview(poster); } document.getElementById('search-results-wrap').innerHTML = ''; } function updatePosterPreview(url) { const img = document.getElementById('poster-preview-img'); const none = document.getElementById('poster-preview-none'); const info = document.getElementById('poster-preview-info'); if (url) { img.src = url; img.style.display = 'block'; none.style.display = 'none'; info.innerHTML = "Affiche sélectionnée"; } else { img.style.display = 'none'; none.style.display = 'flex'; info.innerHTML = "Aucune affiche sélectionnée.
Faites une recherche ou collez une URL."; } } function openAddModal() { editingFilmId = null; document.getElementById('modal-title').textContent = "Nouvelle entrée"; document.getElementById('f-type').value = currentTab; handleTypeChange(currentTab); resetStars(); document.getElementById('f-title').value = ''; document.getElementById('f-year').value = ''; document.getElementById('f-director').value = ''; document.getElementById('f-poster').value = ''; document.getElementById('f-review').value = ''; document.getElementById('f-format').value = 'dvd'; document.getElementById('f-length').value = ''; document.getElementById('f-publisher').value = ''; document.getElementById('f-ean').value = ''; document.getElementById('f-discs').value = '1'; document.getElementById('f-aspect').value = ''; document.getElementById('f-description').value = ''; document.getElementById('search-input').value = ''; document.getElementById('search-results-wrap').innerHTML = ''; updatePosterPreview(''); document.getElementById('form-overlay').classList.add('open'); } function openEditModal(id) { const f = films.find(x => x.id === id); if (!f) return; editingFilmId = id; document.getElementById('modal-title').textContent = "Modifier l'entrée"; const type = f.type || 'critique'; document.getElementById('f-type').value = type; handleTypeChange(type); document.getElementById('f-title').value = f.title || ''; document.getElementById('f-year').value = f.year || ''; document.getElementById('f-director').value = f.director || ''; document.getElementById('f-poster').value = f.poster || ''; document.getElementById('f-review').value = f.review || ''; document.getElementById('f-format').value = f.format || 'dvd'; document.getElementById('f-length').value = f.length || ''; document.getElementById('f-publisher').value = f.publisher || ''; document.getElementById('f-ean').value = f.ean_isbn13 || ''; document.getElementById('f-discs').value = f.number_of_discs || '1'; document.getElementById('f-aspect').value = f.aspect_ratio || ''; document.getElementById('f-description').value = f.description || ''; document.getElementById('search-input').value = ''; document.getElementById('search-results-wrap').innerHTML = ''; setRating(f.rating || 0); updatePosterPreview(f.poster || ''); document.getElementById('form-overlay').classList.add('open'); } function closeModal() { document.getElementById('form-overlay').classList.remove('open'); } function saveFilm() { const title = document.getElementById('f-title').value.trim(); if (!title) { alert("Le titre du film est obligatoire !"); return; } const type = document.getElementById('f-type').value; const metadataVideotheque = type === 'videotheque' ? { format: document.getElementById('f-format').value, length: document.getElementById('f-length').value.trim(), publisher: document.getElementById('f-publisher').value.trim(), ean_isbn13: document.getElementById('f-ean').value.trim(), number_of_discs: document.getElementById('f-discs').value.trim(), aspect_ratio: document.getElementById('f-aspect').value.trim(), description: document.getElementById('f-description').value.trim() } : { format: '', length: '', publisher: '', ean_isbn13: '', number_of_discs: '', aspect_ratio: '', description: '' }; if (editingFilmId !== null) { const index = films.findIndex(f => f.id === editingFilmId); if (index !== -1) { films[index] = { ...films[index], title: title, type: type, year: document.getElementById('f-year').value.trim(), director: document.getElementById('f-director').value.trim(), 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 ...metadataVideotheque }; } } else { const film = { id: Date.now(), title: title, type: type, year: document.getElementById('f-year').value.trim(), director: document.getElementById('f-director').value.trim(), 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 ...metadataVideotheque }; films.unshift(film); } persist(); renderAdminTable(); closeModal(); } function deleteFilm(id) { if (confirm("Voulez-vous vraiment supprimer cet élément ?")) { films = films.filter(f => f.id !== id); persist(); renderAdminTable(); } } function renderAdminTable() { const tbody = document.getElementById('admin-tbody'); const emptyState = document.getElementById('admin-empty'); const emptyMessage = document.getElementById('empty-message'); 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'); if (btnBulkReset) btnBulkReset.style.display = 'none'; const countSpanReset = document.getElementById('bulk-select-count'); if (countSpanReset) countSpanReset.textContent = '0'; const filteredFilms = films.filter(f => { const fType = f.type || 'critique'; return fType === currentTab; }); if (filteredFilms.length === 0) { tbody.innerHTML = ''; if (emptyState) { emptyMessage.textContent = currentTab === 'critique' ? "Aucun film pour l'instant. Ajoutez votre première critique !" : "Votre vidéothèque est vide. Ajoutez vos premiers DVD ou Blu-ray !"; emptyState.style.display = 'block'; } return; } if (emptyState) emptyState.style.display = 'none'; // Update film count display const countEl = document.getElementById('films-count'); if (countEl) countEl.textContent = filteredFilms.length; tbody.innerHTML = filteredFilms.map(f => { const posterHtml = f.poster ? `` : '
'; let dynamicCellHtml = ''; if (currentTab === 'critique') { dynamicCellHtml = `${'★'.repeat(f.rating || 1)}${'☆'.repeat(5 - (f.rating || 1))}`; } else { let formatLabel = (f.format || 'DVD').toUpperCase().replace('_4K', ' 4K'); let publisherLabel = f.publisher ? `
${f.publisher}
` : ''; dynamicCellHtml = `${formatLabel}${publisherLabel}`; } return ` ${posterHtml} ${f.title} ${f.year || '—'} ${f.director || '—'} ${dynamicCellHtml} `; }).join(''); } function toggleSelectAll(masterCheckbox) { const checkboxes = document.querySelectorAll('.row-select'); checkboxes.forEach(cb => cb.checked = masterCheckbox.checked); updateBulkDeleteButton(); } function updateBulkDeleteButton() { const checkedBoxes = document.querySelectorAll('.row-select:checked'); const btnBulk = document.getElementById('btn-bulk-delete'); const countSpan = document.getElementById('bulk-select-count'); if (btnBulk && countSpan) { if (checkedBoxes && checkedBoxes.length > 0) { countSpan.textContent = checkedBoxes.length; btnBulk.style.display = 'inline-flex'; } else { btnBulk.style.display = 'none'; } } } function deleteSelectedFilms() { const checkedBoxes = document.querySelectorAll('.row-select:checked'); if (!checkedBoxes || checkedBoxes.length === 0) return; if (confirm(`Êtes-vous sûr de vouloir supprimer définitivement ces ${checkedBoxes.length} éléments ?`)) { const idsToDelete = Array.from(checkedBoxes).map(cb => parseInt(cb.getAttribute('data-id'))); films = films.filter(f => !idsToDelete.includes(f.id)); persist(); renderAdminTable(); } } // ── Letterboxd CSV parser (handles quoted commas correctly) ────────────── function parseLetterboxdCSV(text) { const rows = []; const lines = text.split(/\r\n|\n/); for (const line of lines) { if (!line.trim()) continue; const cols = []; let cur = '', inQ = false; for (let i = 0; i < line.length; i++) { const c = line[i]; if (c === '"') { if (inQ && line[i+1] === '"') { cur += '"'; i++; } else inQ = !inQ; } else if (c === ',' && !inQ) { cols.push(cur); cur = ''; } else { cur += c; } } cols.push(cur); rows.push(cols); } return rows; } // ── Show import progress overlay ───────────────────────────────────────── function showImportProgress(total) { let overlay = document.getElementById('import-progress-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'import-progress-overlay'; overlay.style.cssText = ` position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:999; display:flex;align-items:center;justify-content:center; `; overlay.innerHTML = `
Import
Initialisation…
0 / ${total}
`; document.body.appendChild(overlay); } return overlay; } function updateImportProgress(done, total, label) { const bar = document.getElementById('import-progress-bar'); const lbl = document.getElementById('import-progress-label'); const cnt = document.getElementById('import-progress-count'); if (bar) bar.style.width = Math.round((done / total) * 100) + '%'; if (lbl) lbl.textContent = label; if (cnt) cnt.textContent = `${done} / ${total}`; } function hideImportProgress() { const el = document.getElementById('import-progress-overlay'); if (el) el.remove(); } // ── TMDB poster lookup (single film) ───────────────────────────────────── async function fetchTmdbPoster(title, year) { const key = localStorage.getItem('tmdb-api-key'); if (!key) return ''; try { const yearParam = year ? `&year=${year}` : ''; const res = await fetch( `https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR${yearParam}` ); const data = await res.json(); 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` ); const data2 = await res2.json(); if (data2.results && data2.results.length > 0 && data2.results[0].poster_path) { return `https://image.tmdb.org/t/p/w500${data2.results[0].poster_path}`; } } } catch (e) {} 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( `https://openlibrary.org/api/books?bibkeys=ISBN:${encodeURIComponent(ean)}&format=json&jscmd=data` ); const olData = await olRes.json(); 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; } } } 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); if (testRes.ok && testRes.headers.get('content-type')?.includes('image')) { return testUrl; } } 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; const reader = new FileReader(); reader.onload = async function (e) { const text = e.target.result; const rows = parseLetterboxdCSV(text); if (rows.length <= 1) { alert("Le fichier semble vide ou invalide."); input.value = ''; return; } 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 name: headers.indexOf('name'), year: headers.indexOf('year'), rating: headers.indexOf('rating'), review: isReviews ? headers.indexOf('review') : -1, watchedDate: headers.indexOf('watched date'), }; // Collect rows to import (skip duplicates first pass) const toImport = []; let duplicateCount = 0; for (let i = 1; i < rows.length; i++) { const cols = rows[i]; const title = iCol.name >= 0 ? cols[iCol.name]?.trim() : ''; if (!title) continue; 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() || '') : ''; const alreadyExists = films.some( f => f.title.toLowerCase() === title.toLowerCase() && f.year === year ); if (alreadyExists) { duplicateCount++; continue; } toImport.push({ title, year, rating, review }); } if (toImport.length === 0) { alert(`Aucun nouvel élément à importer.\n⚠️ ${duplicateCount} doublon(s) ignoré(s).`); input.value = ''; return; } const hasTmdb = !!localStorage.getItem('tmdb-api-key'); showImportProgress(toImport.length); let importedCount = 0; for (let i = 0; i < toImport.length; i++) { const { title, year, rating, review } = toImport[i]; updateImportProgress(i, toImport.length, `🎬 ${title}`); let poster = ''; let streaming = "Disponible au cinéma ou support physique"; if (hasTmdb) { // On récupère les deux infos en un seul traitement 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)); } films.push({ id: Date.now() + Math.floor(Math.random() * 100000) + i, title, type: 'critique', year, director: '', rating, review, poster, streaming, // Ajout du nouveau champ format: '', length: '', publisher: '', ean_isbn13: '', number_of_discs: '1', aspect_ratio: '', description: '' }); importedCount++; } updateImportProgress(toImport.length, toImport.length, '✅ Terminé !'); await new Promise(r => setTimeout(r, 600)); hideImportProgress(); films.sort((a, b) => b.id - a.id); persist(); 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'; alert( `Import Letterboxd — ${typeLabel}\n` + `✅ ${importedCount} film(s) importé(s)${posterInfo}\n` + `⚠️ ${duplicateCount} doublon(s) ignoré(s).` ); input.value = ''; }; reader.readAsText(file, 'UTF-8'); } function exportToCSV() { if (films.length === 0) { alert("Aucune donnée à exporter."); return; } let csvContent = "ID;Titre;Annee;Realisateur;Note;Critique;URL_Affiche;Type;Format\r\n"; films.filter(f => (f.type || 'critique') === 'critique').forEach(f => { const cleanTitle = (f.title || '').replace(/"/g, '""'); const cleanDirector = (f.director || '').replace(/"/g, '""'); const cleanReview = (f.review || '').replace(/\r?\n|\r/g, ' ').replace(/"/g, '""'); const cleanPoster = (f.poster || '').replace(/"/g, '""'); csvContent += `${f.id};"${cleanTitle}";${f.year || ''};"${cleanDirector}";${f.rating || 0};"${cleanReview}";"${cleanPoster}";"critique";""\r\n`; }); const blob = new Blob([new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `export_critiques_${Date.now()}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); } function importVideothequeFromInput(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = async function(e) { await importVideothequeCSV(e.target.result); input.value = ''; }; reader.readAsText(file, 'UTF-8'); } async function importVideothequeCSV(text) { // ── Parse CSV (handles quoted commas) ── const lines = []; let row = [""]; let inQuotes = false; for (let i = 0; i < text.length; i++) { let c = text[i], next = text[i+1]; if (c === '"') { if (inQuotes && next === '"') { row[row.length - 1] += '"'; i++; } else { inQuotes = !inQuotes; } } else if (c === ',' && !inQuotes) { row.push(''); } else if ((c === '\r' || c === '\n') && !inQuotes) { if (c === '\r' && next === '\n') { i++; } lines.push(row); row = ['']; } else { row[row.length - 1] += c; } } if (row.length > 1 || row[0] !== '') lines.push(row); if (lines.length <= 1) return; // ── Collect rows to import ── const toImport = []; let duplicateCount = 0; for (let i = 1; i < lines.length; i++) { const data = lines[i]; if (data.length < 2 || !data[1]) continue; const title = data[1].trim(); const creators = data[2] ? data[2].trim() : ''; const ean = data[6] ? data[6].trim() : ''; const description = data[8] ? data[8].trim() : ''; const publisher = data[9] ? data[9].trim() : ''; const publishDate = data[10] ? data[10].trim() : ''; const length = data[15] ? data[15].trim() : ''; const discs = data[16] ? data[16].trim() : '1'; const aspect = data[20] ? data[20].trim() : ''; let year = publishDate ? publishDate.split('-')[0] : ''; const lowerTitle = title.toLowerCase(); const lowerDesc = description.toLowerCase(); let format = 'dvd'; if (lowerTitle.includes('blu-ray') || lowerTitle.includes('bluray') || lowerDesc.includes('blu-ray') || lowerTitle.includes('4k')) { format = (lowerTitle.includes('4k') || lowerDesc.includes('4k')) ? 'bluray_4k' : 'bluray'; } const duplicate = films.some( f => f.type === 'videotheque' && f.title.toLowerCase() === title.toLowerCase() && f.year === year ); if (duplicate) { duplicateCount++; continue; } toImport.push({ title, creators, ean, description, publisher, year, length, discs, aspect, format }); } if (toImport.length === 0) { alert(`Aucun nouvel élément à importer.\n⚠️ ${duplicateCount} doublon(s) ignoré(s).`); return; } // ── Progress bar ── showImportProgress(toImport.length); let importedCount = 0; let coversFound = 0; for (let i = 0; i < toImport.length; i++) { const { title, creators, ean, description, publisher, year, length, discs, aspect, format } = toImport[i]; 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({ id: Date.now() + Math.floor(Math.random() * 100000) + i, title, type: 'videotheque', director: creators, year, poster, format, length, publisher, ean_isbn13: ean, number_of_discs: discs, aspect_ratio: aspect, description, rating: 0, review: '' }); importedCount++; } updateImportProgress(toImport.length, toImport.length, '✅ Terminé !'); await new Promise(r => setTimeout(r, 600)); hideImportProgress(); persist(); renderAdminTable(); alert( `Import Vidéothèque\n` + `✅ ${importedCount} film(s) importé(s)\n` + `🖼️ ${coversFound} jaquette(s) trouvée(s) (Open Library + TMDB)\n` + `⚠️ ${duplicateCount} doublon(s) ignoré(s).` ); } function exportVideothequeToCSV() { const filmsVideo = films.filter(f => (f.type || 'critique') === 'videotheque'); if (filmsVideo.length === 0) { alert("Aucune donnée de vidéothèque à exporter."); return; } let csvContent = "item_type,title,creators,ean_isbn13,description,publisher,publish_date,length,number_of_discs,aspect_ratio\r\n"; filmsVideo.forEach(f => { const cleanTitle = (f.title || '').replace(/"/g, '""'); const cleanDirector = (f.director || '').replace(/"/g, '""'); const cleanEan = (f.ean_isbn13 || ''); const cleanDesc = (f.description || '').replace(/\r?\n|\r/g, ' ').replace(/"/g, '""'); const cleanPublisher = (f.publisher || '').replace(/"/g, '""'); const fakePublishDate = f.year ? `${f.year}-01-01` : ''; csvContent += `movie,"${cleanTitle}","${cleanDirector}","${cleanEan}","${cleanDesc}","${cleanPublisher}","${fakePublishDate}","${f.length || ''}","${f.number_of_discs || '1'}","${f.aspect_ratio || ''}"\r\n`; }); const blob = new Blob([new Uint8Array([0xEF, 0xBB, 0xBF]), csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `export_videotheque_${Date.now()}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); } function goPublic() { window.location.href = '../index.html'; } document.addEventListener('DOMContentLoaded', () => { const savedKey = localStorage.getItem('tmdb-api-key'); if (savedKey && document.getElementById('tmdb-key-input')) { document.getElementById('tmdb-key-input').value = savedKey; } renderAdminTable(); }); // Récupère l'affiche ET les plateformes de streaming en France async function fetchTmdbDetails(title, year) { const key = localStorage.getItem('tmdb-api-key'); let streamingDefault = "Disponible au cinéma ou support physique"; if (!key) return { poster: '', streaming: streamingDefault }; try { const yearParam = year ? `&year=${year}` : ''; const res = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR${yearParam}`); const data = await res.json(); let movie = data.results && data.results.length > 0 ? data.results[0] : null; if (!movie && year) { const res2 = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR`); const data2 = await res2.json(); if (data2.results && data2.results.length > 0) movie = data2.results[0]; } if (movie) { const poster = movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : ''; let streaming = streamingDefault; try { const providerRes = await fetch(`https://api.themoviedb.org/3/movie/${movie.id}/watch/providers?api_key=${key}`); const providerData = await providerRes.json(); // On cherche les plateformes d'abonnement (flatrate) disponibles en France (FR) if (providerData.results && providerData.results.FR && providerData.results.FR.flatrate) { const providers = providerData.results.FR.flatrate.map(p => p.provider_name); if (providers.length > 0) { streaming = providers.join(', '); } } } catch (e) { console.error("Erreur fournisseurs streaming:", e); } return { poster, streaming }; } } catch (e) { console.error("Erreur recherche TMDB:", e); } return { poster: '', streaming: streamingDefault }; }