diff --git a/js/admin.js b/js/admin.js index 424924e..b938962 100644 --- a/js/admin.js +++ b/js/admin.js @@ -1,6 +1,6 @@ const API_URL = '../api.php'; let allItems = []; -let currentAdminTab = 'critique'; +let currentAdminTab = 'videotheque'; // Défaut sur vidéothèque let currentPage = 1; const itemsPerPage = 12; let selectedIds = new Set(); @@ -56,6 +56,8 @@ function parseCSV(text) { document.addEventListener('DOMContentLoaded', () => { loadDashboardData(); initEventListeners(); + updateImportInterface(); // Mise à jour initiale de l'interface d'import + const confirmBtn = document.getElementById('confirm-btn'); if (confirmBtn) { confirmBtn.addEventListener('click', () => { @@ -68,6 +70,7 @@ document.addEventListener('DOMContentLoaded', () => { function initEventListeners() { const filmForm = document.getElementById('film-form'); if (filmForm) filmForm.addEventListener('submit', saveFilmForm); + const csvInput = document.getElementById('csv-file'); if (csvInput) { csvInput.addEventListener('change', (e) => { @@ -75,10 +78,13 @@ function initEventListeners() { else handleVideothequeUpload(e.target); }); } + const searchInput = document.getElementById('search-input'); if (searchInput) searchInput.addEventListener('input', () => { currentPage = 1; renderAdminTable(); }); + const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) selectAll.addEventListener('change', (e) => toggleSelectAll(e.target)); + document.addEventListener('click', (e) => { if (e.target.classList.contains('modal-close') || e.target.closest('.modal-close')) { const overlay = e.target.closest('.overlay'); @@ -86,6 +92,7 @@ function initEventListeners() { } if (e.target.classList.contains('overlay')) e.target.classList.remove('open'); }); + const physicalFilter = document.getElementById('admin-physical-checkbox'); if (physicalFilter) physicalFilter.addEventListener('change', () => { currentPage = 1; renderAdminTable(); }); } @@ -106,12 +113,13 @@ function renderAdminTable() { const tbody = document.getElementById('admin-table-body'); if (!tbody) return; tbody.innerHTML = ''; + const searchInput = document.getElementById('search-input'); const currentSearch = searchInput ? searchInput.value.toLowerCase() : ''; const physicalFilter = document.getElementById('admin-physical-checkbox'); let filtered = allItems.filter(item => item.type === currentAdminTab); - + if (physicalFilter && physicalFilter.checked) { filtered = filtered.filter(f => { if (f.type === 'critique') { @@ -120,22 +128,22 @@ function renderAdminTable() { return true; }); } - + if (currentSearch) { filtered = filtered.filter(f => f.title.toLowerCase().includes(currentSearch) || (f.director && f.director.toLowerCase().includes(currentSearch)) ); } - + const countLabel = document.getElementById('admin-count-label'); if (countLabel) countLabel.textContent = `${filtered.length} élément(s)`; - + const totalPages = Math.ceil(filtered.length / itemsPerPage) || 1; if (currentPage > totalPages) currentPage = totalPages; const startIdx = (currentPage - 1) * itemsPerPage; const pageItems = filtered.slice(startIdx, startIdx + itemsPerPage); - + pageItems.forEach(f => { const tr = document.createElement('tr'); const isChecked = selectedIds.has(String(f.id)) ? 'checked' : ''; @@ -154,9 +162,9 @@ function renderAdminTable() { `; tbody.appendChild(tr); }); - + renderPagination(totalPages, filtered.length); - + const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id))); } @@ -192,9 +200,9 @@ function renderPagination(totalPages, totalItems) { const container = document.getElementById('pagination-container'); if (!container) return; container.innerHTML = ''; - if (totalItems === 0) { - container.innerHTML = '

Aucun élément trouvé.

'; - return; + if (totalItems === 0) { + container.innerHTML = '

Aucun élément trouvé.

'; + return; } if (totalPages <= 1) return; @@ -202,16 +210,16 @@ function renderPagination(totalPages, totalItems) { info.className = 'pagination-info'; info.textContent = `Page ${currentPage} sur ${totalPages}`; container.appendChild(info); - + const prevBtn = document.createElement('button'); prevBtn.innerHTML = ''; prevBtn.disabled = currentPage === 1; prevBtn.onclick = () => { currentPage--; renderAdminTable(); }; container.appendChild(prevBtn); - + let startPage = Math.max(1, currentPage - 2); let endPage = Math.min(totalPages, currentPage + 2); - + if (startPage > 1) { const firstBtn = document.createElement('button'); firstBtn.textContent = '1'; @@ -223,7 +231,7 @@ function renderPagination(totalPages, totalItems) { container.appendChild(dots); } } - + for (let i = startPage; i <= endPage; i++) { const btn = document.createElement('button'); btn.textContent = i; @@ -231,7 +239,7 @@ function renderPagination(totalPages, totalItems) { btn.onclick = () => { currentPage = i; renderAdminTable(); }; container.appendChild(btn); } - + if (endPage < totalPages) { if (endPage < totalPages - 1) { const dots = document.createElement('span'); @@ -243,7 +251,7 @@ function renderPagination(totalPages, totalItems) { lastBtn.onclick = () => { currentPage = totalPages; renderAdminTable(); }; container.appendChild(lastBtn); } - + const nextBtn = document.createElement('button'); nextBtn.innerHTML = ''; nextBtn.disabled = currentPage === totalPages; @@ -337,26 +345,21 @@ async function openConfigModal() { try { const res = await fetch(`${API_URL}?action=get_config_keys`, { headers: { 'Authorization': localStorage.getItem('token') } }); const data = await res.json(); - const tmdbIn = document.getElementById('tmdb-key-input'); if (tmdbIn) tmdbIn.placeholder = data.tmdb_api_key ? '✅ Clé TMDB configurée' : 'Entrez votre clé TMDB'; - const upcIn = document.getElementById('upcmdb-key-input'); if (upcIn) upcIn.placeholder = data.upcmdb_api_key ? '✅ Clé UPCMDB configurée' : 'Clé gratuite sur upcmdb.com'; } catch(e) { console.error(e); } } function closeConfigModal() { document.getElementById('config-modal').classList.remove('open'); } - function openPasswordModal() { document.getElementById('pwd-error').style.display = 'none'; document.getElementById('new-password-input').value = ''; document.getElementById('new-password-confirm').value = ''; document.getElementById('password-modal').classList.add('open'); } - function closePasswordModal() { document.getElementById('password-modal').classList.remove('open'); } - function logout() { localStorage.removeItem('token'); window.location.href = 'login.html'; } async function saveFilmForm(e) { @@ -411,98 +414,76 @@ async function handleCritiqueUpload(input) { loadDashboardData(); } +// ✅ CORRECTION : Extrait TOUTES les métadonnées du CSV pour ne rien perdre function normalizeVideothequeRow(row) { - // Recherche de l'EAN dans plusieurs colonnes possibles let ean = row['ean_isbn13'] || row['EAN'] || row['ean'] || row['Barcode'] || row['UPC'] || row['upc_isbn10'] || ''; - ean = String(ean).replace(/[^0-9]/g, ''); + if (ean) ean = String(ean).replace(/[^0-9]/g, ''); - // Recherche du titre dans plusieurs colonnes - let title = row['title'] || row['Title'] || row['Titre'] || row['titre'] || row['nom'] || row['Nom'] || ''; + let title = row['title'] || row['Title'] || row['Titre'] || ''; title = String(title).trim(); - - // Si on n'a ni EAN ni titre, on ignore if (!ean && !title) return null; - - return { ean, title }; + + return { + ean: ean, + title: title, + year: row['publish_date'] ? String(row['publish_date']).substring(0, 4) : (row['Year'] || row['year'] || ''), + synopsis: row['description'] || '', + review: row['review'] || '', + rating: row['rating'] || '', + publisher: row['publisher'] || '', + length: row['length'] || '', + number_of_discs: row['number_of_discs'] || 1, + aspect_ratio: row['aspect_ratio'] || '', + actors: row['creators'] || row['ensemble'] || '' // 'creators' contient le casting dans votre CSV + }; } function handleVideothequeUpload(input) { const file = input.files[0]; if (!file) return; - const reader = new FileReader(); reader.onload = async (e) => { const text = e.target.result; const parsed = parseCSV(text); - if (!parsed.length) { - alert("❌ CSV vide ou mal formaté."); - input.value = ''; - return; - } - - // Transformer chaque ligne en objet {ean, title} + if (!parsed.length) { alert("❌ CSV vide."); return; } + const items = parsed.map(row => normalizeVideothequeRow(row)).filter(Boolean); - if (!items.length) { - alert("❌ Aucun titre ou EAN valide trouvé dans le CSV."); - input.value = ''; - return; - } - - console.log(`Lignes parsées : ${parsed.length}, items valides : ${items.length}`); - - // Ouvrir la modale de progression + if (!items.length) { alert("❌ Aucun titre ou EAN valide trouvé."); return; } + showImportModal(items.length, 'videotheque'); - let processed = 0; - let totalImported = 0; - let totalSkipped = 0; - - // Taille du lot : 10 films par requête - const BATCH_SIZE = 10; + let processed = 0, totalImp = 0, totalSkp = 0; try { + // ✅ Envoi par lots de 5 pour que le throttle PHP fonctionne au sein du même script + const BATCH_SIZE = 5; for (let i = 0; i < items.length; i += BATCH_SIZE) { const batch = items.slice(i, i + BATCH_SIZE); - const response = await fetch(`${API_URL}?action=import_batch`, { + const res = await fetch(`${API_URL}?action=import_batch`, { method: 'POST', - headers: { - 'Authorization': localStorage.getItem('token'), - 'Content-Type': 'application/json' - }, + headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'videotheque', items: batch }) }); - - if (!response.ok) { - throw new Error(`Erreur HTTP ${response.status} - ${response.statusText}`); - } - - const result = await response.json(); - if (!result.success) { - throw new Error(result.error || "Erreur inconnue lors de l'import."); - } - - totalImported += result.imported || 0; - totalSkipped += result.skipped || 0; + + if (!res.ok) throw new Error(`Erreur HTTP ${res.status}`); + const data = await res.json(); + if (!data.success) throw new Error(data.error || "Échec API "); + + totalImp += data.imported || 0; + totalSkp += data.skipped || 0; processed += batch.length; - - // Mise à jour de la barre de progression updateImportModal(processed, items.length); - - // Petit délai pour éviter de saturer le serveur - await new Promise(resolve => setTimeout(resolve, 200)); + await new Promise(r => setTimeout(r, 500)); // Petit délai réseau } - - // Fermer la modale - closeImportModal(); + input.value = ''; - - const msg = totalSkipped > 0 ? ` (${totalSkipped} ignoré(s))` : ''; - showSuccessModal(`${totalImported} film(s) importé(s) avec succès.${msg}`); + closeImportModal(); + const msg = totalSkp > 0 ? ` (${totalSkp} ignoré(s))` : ''; + showSuccessModal(`${totalImp} édition(s) importée(s).${msg}`); loadDashboardData(); - } catch (err) { console.error(err); closeImportModal(); - alert(`❌ Échec de l'import : ${err.message}`); + alert("❌ Échec import : " + err.message); input.value = ''; } }; @@ -528,7 +509,6 @@ function closeImportModal() { document.getElementById('import-progress-modal').c async function saveConfigKeys() { const tmdb = document.getElementById('tmdb-key-input').value.trim(); const upcmdb = document.getElementById('upcmdb-key-input').value.trim(); - try { if (tmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'tmdb_api_key',key_value:tmdb}) }); if (upcmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'upcmdb_api_key',key_value:upcmdb}) }); @@ -550,15 +530,16 @@ async function saveNewPassword() { } catch (err) { console.error(err); } } +// ✅ SÉPARATION CLAIRE DES IMPORTS function updateImportInterface() { const title = document.getElementById('import-title'); const desc = document.getElementById('import-desc'); if (currentAdminTab === 'videotheque') { title.innerHTML = 'Importer ma Vidéothèque'; - desc.textContent = 'Import CSV (EAN + Titre) : Blu-ray.com pour le physique, TMDB pour l\'affiche, le casting et le synopsis.'; + desc.textContent = 'Importez votre CSV (ex: CLC, MediaControl). Le système récupérera les jaquettes HD via MovieCovers/Blu-ray.com et complétera avec TMDB.'; } else { title.innerHTML = 'Importer Critiques & Notes'; - desc.textContent = 'Sélectionnez vos fichiers "ratings.csv" et "reviews.csv" (Letterboxd).'; + desc.textContent = 'Importez vos fichiers CSV de notes et critiques (ex: Letterboxd ou export personnalisé).'; } }