diff --git a/js/admin.js b/js/admin.js index f8a008b..9e42099 100644 --- a/js/admin.js +++ b/js/admin.js @@ -446,95 +446,84 @@ async function saveFilmForm(e) { } catch (err) { console.error('Erreur sauvegarde :', err); } } -// ── IMPORT CSV : INTELLIGENCE ET FUSION ── +// --- FONCTION D'IMPORT INTELLIGENTE --- async function handleCsvUpload(input) { if (!input.files || input.files.length === 0) return; - closeConfigModal(); let allData = []; - // 1. Lecture de TOUS les fichiers sélectionnés + // 1. Lecture de tous les fichiers (supporte le multi-upload) for (const file of input.files) { try { const text = await file.text(); const data = parseCSV(text); allData = allData.concat(data); - } catch(e) { console.error('Erreur fichier', e); } + } catch(e) { + console.error('Erreur lecture fichier', file.name, e); + } } - input.value = ''; // Reset de l'input + input.value = ''; // Reset l'input file if (allData.length === 0) { alert('❌ Fichiers vides ou mal formatés.'); return; } - // 2. Dédoublonnage et fusion intelligente des CSV en mémoire + // 2. Fusion et Dédoublonnage en mémoire + // Utilise une Map pour regrouper les données par "Titre|Année" const mergedMap = new Map(); allData.forEach(row => { - const title = row['title'] || row['Name'] || 'Sans titre'; - const dateStr = row['publish_date'] || row['Year'] || row['year'] || ''; - const m = String(dateStr).match(/(\d{4})/); - const year = m ? m[1] : ''; - - const key = `${title.toLowerCase().trim()}|${year}`; + const title = (row['Title'] || row['Name'] || '').trim(); + const dateStr = (row['Date'] || row['Year'] || row['year'] || '').toString(); + const year = dateStr.match(/(\d{4})/)?.[1] || ''; + const key = `${title.toLowerCase()}|${year}`; if (!mergedMap.has(key)) { mergedMap.set(key, { ...row }); } else { const existing = mergedMap.get(key); - // Si la ligne actuelle a une note/critique et que l'existante n'en a pas, on complète - if ((row['Rating'] || row['rating']) && !(existing['Rating'] || existing['rating'])) { - existing['Rating'] = row['Rating'] || row['rating']; - } - if ((row['Review'] || row['review']) && !(existing['Review'] || existing['review'])) { - existing['Review'] = row['Review'] || row['review']; - } + // Fusion des notes et critiques si présentes + if ((row['Rating'] || row['rating']) && !existing['Rating']) existing['Rating'] = row['Rating']; + if ((row['Review'] || row['review']) && !existing['Review']) existing['Review'] = row['Review']; } }); - const finalData = Array.from(mergedMap.values()); + let finalData = Array.from(mergedMap.values()); - // 3. Protection de la Base de Données (Ne pas écraser l'existant) - finalData.forEach(row => { - const title = row['title'] || row['Name'] || 'Sans titre'; - const dateStr = row['publish_date'] || row['Year'] || row['year'] || ''; - const m = String(dateStr).match(/(\d{4})/); - const year = m ? m[1] : ''; + // 3. Protection : Ne pas écraser les données BDD existantes + finalData = finalData.map(row => { + const title = (row['Title'] || row['Name'] || '').trim(); + const dateStr = (row['Date'] || row['Year'] || row['year'] || '').toString(); + const year = dateStr.match(/(\d{4})/)?.[1] || ''; - // Cherche le film dans le dashboard déjà chargé const existingDb = allItems.find(f => - f.title.toLowerCase().trim() === title.toLowerCase().trim() && + f.title.toLowerCase().trim() === title.toLowerCase() && String(f.year) === String(year) && f.type === currentAdminTab ); if (existingDb) { - const csvRating = row['Rating'] || row['rating']; - const csvReview = row['Review'] || row['review']; - - // Récupère l'ancienne note si le fichier CSV n'en contient pas - if (!csvRating && existingDb.rating) { - row['Rating'] = existingDb.rating; - } - // Récupère l'ancienne critique si le fichier CSV n'en contient pas - if (!csvReview && existingDb.review) { - row['Review'] = existingDb.review; - } + return { + ...row, + 'Rating': (row['Rating'] || row['rating']) || existingDb.rating, + 'Review': (row['Review'] || row['review']) || existingDb.review + }; } + return row; }); - showProgressModal(finalData.length); + // 4. Lancement de la modale de progression stylisée + showImportModal(finalData.length, currentAdminTab); - // 4. Envoi par lots de 10 - const batchSize = 10; + // 5. Envoi par lots de 10 + const batchSize = 10; let processed = 0; - let totalStats = { upc_hits: 0, tmdb_hits: 0, no_image: 0 }; for (let i = 0; i < finalData.length; i += batchSize) { const batch = finalData.slice(i, i + batchSize); try { - const response = await fetch(`${API_URL}?action=import_batch`, { + await fetch(`${API_URL}?action=import_batch`, { method: 'POST', headers: { 'Authorization': localStorage.getItem('token'), @@ -542,33 +531,16 @@ async function handleCsvUpload(input) { }, body: JSON.stringify({ items: batch, type: currentAdminTab }) }); - const resData = await response.json(); - if (resData.stats) { - totalStats.upc_hits += resData.stats.upc_hits || 0; - totalStats.tmdb_hits += resData.stats.tmdb_hits || 0; - totalStats.no_image += resData.stats.no_image || 0; - } } catch (err) { - console.error('Erreur sur un lot:', err); + console.error('Erreur lot import:', err); } processed += batch.length; - updateProgressModal(processed, finalData.length); + updateImportModal(processed, finalData.length); } - closeProgressModal(); - - // 5. Affichage du bilan - let statsMessage = `✅ Import terminé avec succès !\n\n🎬 Films traités : ${finalData.length}\n`; - if (totalStats.upc_hits > 0 || totalStats.tmdb_hits > 0 || totalStats.no_image > 0) { - statsMessage += `\n📊 Bilan des recherches d'images :\n`; - if (currentAdminTab === 'videotheque') { - statsMessage += `• 📦 Jaquettes via Code EAN (UPC) : ${totalStats.upc_hits}\n`; - } - statsMessage += `• 🍿 Affiches via le titre (TMDB) : ${totalStats.tmdb_hits}\n`; - statsMessage += `• ❌ Aucune image trouvée : ${totalStats.no_image}\n`; - } - alert(statsMessage); - + // 6. Finalisation + closeImportModal(); + alert(`✅ Import terminé ! ${finalData.length} élément(s) traité(s).`); loadDashboardData(); } @@ -627,21 +599,33 @@ async function saveNewPassword() { } catch (err) { console.error('Erreur mise à jour mot de passe :', err); } } -function showProgressModal(total) { - document.getElementById('progress-text').textContent = 'Traitement des films...'; - document.getElementById('progress-bar').style.width = '0%'; - document.getElementById('progress-count').textContent = `0 / ${total}`; - document.getElementById('progress-overlay').classList.add('open'); +// --- IMPORT PROGRESSIVE DYNAMIQUE --- +function showImportModal(total, type) { + const modal = document.getElementById('import-progress-modal'); + const title = document.getElementById('import-modal-title'); + const desc = document.getElementById('import-modal-desc'); + + // Adaptation selon le contexte + title.innerHTML = type === 'critique' + ? ' Import des Critiques' + : ' Import Vidéothèque'; + + desc.textContent = `Traitement de ${total} élément(s)...`; + + document.getElementById('import-progress-bar').style.width = '0%'; + document.getElementById('import-modal-counter').textContent = '0%'; + + modal.classList.add('open'); } -function updateProgressModal(current, total) { +function updateImportModal(current, total) { const pct = Math.round((current / total) * 100); - document.getElementById('progress-bar').style.width = pct + '%'; - document.getElementById('progress-count').textContent = `${current} / ${total}`; + document.getElementById('import-progress-bar').style.width = pct + '%'; + document.getElementById('import-modal-counter').textContent = `${pct}%`; } -function closeProgressModal() { - document.getElementById('progress-overlay').classList.remove('open'); +function closeImportModal() { + document.getElementById('import-progress-modal').classList.remove('open'); } async function fetchInfoFromEAN() {