/** * ========================================================================= * Mon Cinéma - Module d'Administration (admin.js) * Gestion de l'état, de la persistance, des onglets et des imports/exports CSV * ========================================================================= */ // ── PARSEUR CSV UNIVERSEL (Gestion avancée des guillemets et séparateurs) ── function parseFlexibleCSV(text) { const rows = []; let row = [""]; let inQuotes = false; // Détection dynamique du séparateur principal (, ou ;) basé sur la première ligne const firstLine = text.split(/\r?\n/)[0] || ""; const semiColonCount = (firstLine.match(/;/g) || []).length; const commaCount = (firstLine.match(/,/g) || []).length; const separator = semiColonCount > commaCount ? ';' : ','; for (let i = 0; i < text.length; i++) { let c = text[i]; let next = text[i + 1]; if (c === '"') { if (inQuotes && next === '"') { row[row.length - 1] += '"'; i++; } else { inQuotes = !inQuotes; } } else if (c === separator && !inQuotes) { row.push(''); } else if ((c === '\r' || c === '\n') && !inQuotes) { if (c === '\r' && next === '\n') { i++; } rows.push(row); row = ['']; } else { row[row.length - 1] += c; } } if (row.length > 1 || row[0] !== '') rows.push(row); return rows; } // ── FONCTION D'IMPORTATION REVISITÉE ── 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 = parseFlexibleCSV(text); if (rows.length <= 1) { alert("Le fichier semble vide ou illisible."); input.value = ''; return; } // Nettoyage et normalisation des entêtes pour ignorer les accents et la casse const cleanHeader = (h) => h.trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); const headers = rows[0].map(cleanHeader); // Mappage ultra-large des colonnes (gère vos deux structures de fichiers) const idxTitle = headers.findIndex(h => h === 'titre' || h === 'title' || h === 'name'); const idxYear = headers.findIndex(h => h === 'annee' || h === 'year' || h === 'publish_date'); const idxDirector = headers.findIndex(h => h === 'realisateur' || h === 'director' || h === 'creators'); const idxRating = headers.findIndex(h => h === 'note' || h === 'rating'); const idxReview = headers.findIndex(h => h === 'critique' || h === 'review' || h === 'description'); const idxPoster = headers.findIndex(h => h === 'url_affiche' || h === 'url'); if (idxTitle === -1) { alert("Erreur : Impossible de trouver la colonne des titres. Entêtes détectés : " + rows[0].join(', ')); input.value = ''; return; } const toImport = []; let duplicateCount = 0; for (let i = 1; i < rows.length; i++) { const cols = rows[i]; if (!cols || cols.length === 0 || (cols.length === 1 && cols[0] === '')) continue; const titleRaw = cols[idxTitle]?.trim(); if (!titleRaw) continue; // Nettoyage des chaînes parasites let title = titleRaw.replace(/\s*\[.*?\]/g, "").replace(/\s*- Édition.*/gi, ""); // Année (on extrait les 4 premiers chiffres) let year = ''; if (idxYear >= 0 && cols[idxYear]) { const match = cols[idxYear].trim().match(/\d{4}/); year = match ? match[0] : cols[idxYear].trim().substring(0, 4); } // Réalisateur let director = ''; if (idxDirector >= 0 && cols[idxDirector]) { director = cols[idxDirector].split(',')[0].trim(); } // Note (de 1 à 5) let rating = 0; if (idxRating >= 0 && cols[idxRating]) { const ratingRaw = parseFloat(cols[idxRating]); if (!isNaN(ratingRaw) && ratingRaw > 0) { rating = Math.round(Math.min(5, Math.max(1, ratingRaw))); } } if (rating === 0) rating = 3; // Note par défaut // Critique ou description longue let review = idxReview >= 0 && cols[idxReview] ? cols[idxReview].trim() : ''; // Lien vers l'affiche let filePoster = idxPoster >= 0 && cols[idxPoster] ? cols[idxPoster].trim() : ''; // Vérification des doublons élargie (titre + année identiques, peu importe le type) const alreadyExists = films.some( f => f.title.toLowerCase() === title.toLowerCase() && (year === '' || f.year === year) ); if (alreadyExists) { duplicateCount++; continue; } toImport.push({ title, year, director, rating, review, filePoster }); } if (toImport.length === 0) { alert(`Aucun nouveau film inséré.\n⚠️ ${duplicateCount} doublon(s) filtré(s) ou lignes vides.`); 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, director, rating, review, filePoster } = toImport[i]; updateImportProgress(i, toImport.length, `🎬 Intégration : ${title}`); let poster = filePoster; let streaming = "Disponible sur vos étagères ou au cinéma"; // Si l'affiche locale manque et que TMDB est configuré, on interroge l'API if (!poster && hasTmdb) { const details = await fetchTmdbDetails(title, year); poster = details.poster; streaming = details.streaming; await new Promise(r => setTimeout(r, 150)); } else if (hasTmdb && poster) { const details = await fetchTmdbDetails(title, year); if (details.streaming) streaming = details.streaming; } // Structure globale unifiée pour 'critique' ou 'film' films.push({ id: Date.now() + Math.floor(Math.random() * 1000) + i, title, type: 'critique', // Forcer le type attendu par l'affichage principal de vos onglets year, director, rating, review, poster, streaming, 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, 300)); hideImportProgress(); // Tri et persistance immédiate films.sort((a, b) => b.id - a.id); persist(); switchTab('critique'); alert(`Succès ! ${importedCount} films ajoutés.\n(${duplicateCount} doublons ignorés)`); input.value = ''; }; // Utilisation de ISO-8859-1 (Latin-1) pour lire sans encombre les exports Windows/Excel contenant des accents reader.readAsText(file, 'ISO-8859-1'); } // ── COMPOSANTS ET OUTILS DE SUIVI DE L'IMPORT ── function showImportProgress(total) { let overlay = document.getElementById('import-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'import-overlay'; overlay.innerHTML = `
Initialisation...