From a8976c7fb71d9f7b28493bea458cfb3ae60dbc2a Mon Sep 17 00:00:00 2001 From: Cedric Date: Mon, 22 Jun 2026 11:18:02 +0200 Subject: [PATCH] Actualiser js/admin.js --- js/admin.js | 208 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 135 insertions(+), 73 deletions(-) diff --git a/js/admin.js b/js/admin.js index 1acc8c1..b0e40a9 100644 --- a/js/admin.js +++ b/js/admin.js @@ -17,6 +17,7 @@ function getStarsHTML(rating) { return html; } +// ── PARSER CSV RENFORCÉ ── function parseCSV(text) { if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1); const rows = []; @@ -34,21 +35,23 @@ function parseCSV(text) { else if (c === '\n' || c === '\r') { if (c === '\r' && text[i+1] === '\n') i++; row.push(col); col = ''; - if (row.length > 1 || row[0] !== '') rows.push(row); + if (row.length > 1 || row[0].trim() !== '') rows.push(row); row = []; } else col += c; } } if (col !== '' || row.length > 0) { row.push(col); rows.push(row); } if (rows.length === 0) return []; + const headers = rows[0].map(h => h.trim()); const data = []; for (let i = 1; i < rows.length; i++) { - if (rows[i].length === headers.length) { - const obj = {}; - headers.forEach((h, idx) => obj[h] = rows[i][idx]); - data.push(obj); - } + if (rows[i].length === 1 && rows[i][0].trim() === '') continue; // Ignore les lignes 100% vides + const obj = {}; + headers.forEach((h, idx) => { + obj[h] = rows[i][idx] !== undefined ? rows[i][idx] : ''; + }); + data.push(obj); } return data; } @@ -127,7 +130,6 @@ function renderAdminTable() { let filtered = allItems.filter(item => item.type === currentAdminTab); - // Nouveau : Filtrage des formats dématérialisés if (physicalFilter && physicalFilter.checked) { filtered = filtered.filter(f => f.format && !['dématérialisé', 'vod', 'digital', 'streaming'].includes(f.format.toLowerCase())); } @@ -390,14 +392,9 @@ function closeAdminModal() { document.getElementById('admin-modal').classList.re async function openConfigModal() { document.getElementById('config-modal').classList.add('open'); - - // Réinitialise les placeholders document.getElementById('tmdb-key-input').placeholder = 'Pour les critiques (réalisateur, streaming)'; - try { - const res = await fetch(`${API_URL}?action=get_config_keys`, { - headers: { 'Authorization': localStorage.getItem('token') } - }); + const res = await fetch(`${API_URL}?action=get_config_keys`, { headers: { 'Authorization': localStorage.getItem('token') } }); const data = await res.json(); if (data.tmdb_api_key) document.getElementById('tmdb-key-input').placeholder = '✅ Clé configurée (laisser vide pour ne pas changer)'; } catch(e) { console.error(e); } @@ -448,54 +445,134 @@ async function saveFilmForm(e) { } catch (err) { console.error('Erreur sauvegarde :', err); } } +// ── IMPORT CSV : INTELLIGENCE ET FUSION ── async function handleCsvUpload(input) { if (!input.files || input.files.length === 0) return; - const file = input.files[0]; - input.value = ''; - try { - const text = await file.text(); - const allData = parseCSV(text); - if (allData.length === 0) { - alert('❌ Le fichier CSV est vide ou mal formaté.'); - return; - } - closeConfigModal(); - showProgressModal(allData.length); - - const batchSize = 5; - let processed = 0; - for (let i = 0; i < allData.length; i += batchSize) { - const batch = allData.slice(i, i + batchSize); - try { - await fetch(`${API_URL}?action=import_batch`, { - method: 'POST', - headers: { - 'Authorization': localStorage.getItem('token'), - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ items: batch, type: currentAdminTab }) - }); - } catch (err) { - console.error('Erreur sur un lot:', err); - } - processed += batch.length; - updateProgressModal(processed, allData.length); - } - closeProgressModal(); - alert(`✅ Import terminé ! ${allData.length} élément(s) traité(s).`); - loadDashboardData(); - } catch (err) { - console.error('Erreur lecture CSV :', err); - closeProgressModal(); - alert('Impossible de lire le fichier CSV.'); + + closeConfigModal(); + let allData = []; + + // 1. Lecture de TOUS les fichiers sélectionnés + 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); } } + + input.value = ''; // Reset de l'input + + if (allData.length === 0) { + alert('❌ Fichiers vides ou mal formatés.'); + return; + } + + // 2. Dédoublonnage et fusion intelligente des CSV en mémoire + 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}`; + + 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']; + } + } + }); + + const 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] : ''; + + // Cherche le film dans le dashboard déjà chargé + const existingDb = allItems.find(f => + f.title.toLowerCase().trim() === title.toLowerCase().trim() && + 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; + } + } + }); + + showProgressModal(finalData.length); + + // 4. 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`, { + method: 'POST', + headers: { + 'Authorization': localStorage.getItem('token'), + 'Content-Type': 'application/json' + }, + 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); + } + processed += batch.length; + updateProgressModal(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); + + loadDashboardData(); } async function saveConfigKeys() { - const keys = { - 'tmdb_api_key': document.getElementById('tmdb-key-input')?.value || '', - }; - + const keys = { 'tmdb_api_key': document.getElementById('tmdb-key-input')?.value || '' }; let successCount = 0; for (const [keyName, keyValue] of Object.entries(keys)) { if (keyValue) { @@ -510,13 +587,10 @@ async function saveConfigKeys() { } catch (err) { console.error(err); } } } - if (successCount > 0) { alert('✅ Clés API sauvegardées !'); closeConfigModal(); - } else { - alert('Aucune nouvelle clé valide à sauvegarder.'); - } + } else { alert('Aucune nouvelle clé valide à sauvegarder.'); } } async function saveNewPassword() { @@ -569,17 +643,11 @@ function closeProgressModal() { document.getElementById('progress-overlay').classList.remove('open'); } -// --- NOUVELLE FONCTION RECHERCHE EAN (UPCitemDB + TMDB) --- async function fetchInfoFromEAN() { const eanInput = document.getElementById('f-ean'); const ean = eanInput.value.trim(); + if (!ean || ean.length < 8) { alert("Veuillez saisir un code EAN valide."); return; } - if (!ean || ean.length < 8) { - alert("Veuillez saisir un code EAN valide."); - return; - } - - // Feedback visuel de chargement const btn = eanInput.nextElementSibling; const originalHtml = btn.innerHTML; btn.innerHTML = ''; @@ -590,10 +658,8 @@ async function fetchInfoFromEAN() { headers: { 'Authorization': localStorage.getItem('token') } }); const json = await res.json(); - if (json.success && json.data) { const d = json.data; - // Remplissage automatique des champs if (d.title) document.getElementById('f-title').value = d.title; if (d.director) document.getElementById('f-director').value = d.director; if (d.year) document.getElementById('f-year').value = d.year; @@ -603,15 +669,11 @@ async function fetchInfoFromEAN() { if (d.length) document.getElementById('f-length').value = d.length; if (d.number_of_discs) document.getElementById('f-discs').value = d.number_of_discs; if (d.aspect_ratio) document.getElementById('f-aspect').value = d.aspect_ratio; - - } else { - alert("Aucune information trouvée pour ce code EAN."); - } + } else { alert("Aucune information trouvée pour ce code EAN."); } } catch (e) { console.error("Erreur de recherche EAN:", e); alert("Erreur réseau lors de la recherche du code EAN."); } finally { - // Restaure le bouton btn.innerHTML = originalHtml; btn.disabled = false; }