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() {