<<<<<<< HEAD /** * ========================================================================= * 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 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 SÉCURISÉE (Latin-1 / Multi-formats) ── 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 en-têtes (ignore les accents et la casse) const cleanHeader = (h) => h.trim().toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, ""); const headers = rows[0].map(cleanHeader); // Mappage des colonnes (gère 'importcinetest.csv' et votre catalogue global) 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."); 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 suffixes d'éditions let title = titleRaw.replace(/\s*\[.*?\]/g, "").replace(/\s*- Édition.*/gi, ""); // Extraction propre de l'année (4 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 (Normalisée entre 1 et 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; // Critique ou description de l'œuvre let review = idxReview >= 0 && cols[idxReview] ? cols[idxReview].trim() : ''; // URL de l'affiche let filePoster = idxPoster >= 0 && cols[idxPoster] ? cols[idxPoster].trim() : ''; // Déduplication (titre + année) 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).`); 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"; 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; } // Injection forcée en type 'critique' pour correspondre à votre vue principale films.push({ id: Date.now() + Math.floor(Math.random() * 1000) + i, title, type: 'critique', 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(); 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 obligatoire de ISO-8859-1 (Latin-1) pour préserver les accents sous Excel / Windows reader.readAsText(file, 'ISO-8859-1'); } // ── COMPOSANTS INTERFACES DE PROGRESSION ── function showImportProgress(total) { let overlay = document.getElementById('import-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'import-overlay'; overlay.innerHTML = `

Importation en cours...

Initialisation...

`; document.body.appendChild(overlay); } overlay.style.display = 'block'; } function updateImportProgress(current, total, message) { const fill = document.getElementById('import-progress-fill'); const text = document.getElementById('import-progress-text'); const percent = Math.round((current / total) * 100); if (fill) fill.style.width = `${percent}%`; if (text) text.innerText = `${message} (${percent}%)`; } function hideImportProgress() { const overlay = document.getElementById('import-overlay'); if (overlay) overlay.style.display = 'none'; } // ── FONCTION D'EXPORTATION DU CATALOGUE (CSV) ── function exportToCSV() { if (!films || films.length === 0) { alert("Aucune donnée disponible pour l'exportation."); return; } const headers = ['ID', 'Titre', 'Annee', 'Realisateur', 'Note', 'Critique', 'URL_Affiche']; const csvRows = [headers.join(';')]; films.forEach(f => { const row = [ f.id, `"${(f.title || '').replace(/"/g, '""')}"`, f.year || '', `"${(f.director || '').replace(/"/g, '""')}"`, f.rating || 0, `"${(f.review || '').replace(/"/g, '""')}"`, `"${(f.poster || '').replace(/"/g, '""')}"` ]; csvRows.push(row.join(';')); }); const csvContent = "\uFEFF" + csvRows.join("\n"); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `mon_cinema_export_${new Date().toISOString().slice(0,10)}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); } // ── ENREGISTREMENT LOCAL STORAGE ── function persist() { localStorage.setItem('mon-cinema-films', JSON.stringify(films)); if (typeof renderFilms === 'function') { renderFilms(); } } // ── NAVIGATION ONGLETS ── function switchTab(tabName) { document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active')); const targetTab = document.getElementById(`tab-${tabName}`); const targetBtn = document.querySelector(`[onclick="switchTab('${tabName}')"]`); if (targetTab) targetTab.classList.add('active'); if (targetBtn) targetBtn.classList.add('active'); localStorage.setItem('mon-cinema-active-tab', tabName); } ======= // ══════════════════════════════════════════════════════════════════ // admin.js — Backoffice Mon Cinéma // Chargé UNIQUEMENT par dashboard.html // ══════════════════════════════════════════════════════════════════ const API_URL = '../api.php'; let allItems = []; let currentAdminTab = 'critique'; // ── Garde de session (dashboard uniquement) ────────────────────── (function guardSession() { if (!localStorage.getItem('token')) { window.location.href = 'login.html'; } })(); // ══════════════════════════════════════════════════════════════════ // 1. CHARGEMENT & RENDU // ══════════════════════════════════════════════════════════════════ async function loadDashboardData() { try { const res = await fetch(`${API_URL}?action=get_films`); allItems = await res.json(); // Vérifier si compte sécurisé const secRes = await fetch(`${API_URL}?action=check_security_status`); const secData = await secRes.json(); const banner = document.getElementById('security-banner'); if (banner) banner.style.display = secData.is_blank ? 'flex' : 'none'; renderAdminTable(); } catch (err) { console.error('Erreur chargement :', err); } } function switchAdminTab(tabName) { currentAdminTab = tabName; document.getElementById('btn-tab-critique').classList.toggle('active', tabName === 'critique'); document.getElementById('btn-tab-videotheque').classList.toggle('active', tabName === 'videotheque'); const importSection = document.getElementById('import-section'); if (importSection) importSection.style.display = tabName === 'critique' ? 'block' : 'none'; document.getElementById('admin-subtitle').textContent = tabName === 'critique' ? 'Gestion de vos critiques de films' : 'Gestion de votre stock physique de films'; document.getElementById('th-dynamic').textContent = tabName === 'critique' ? 'Note' : 'Format'; const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) selectAll.checked = false; renderAdminTable(); } function renderAdminTable() { const tbody = document.getElementById('admin-table-body'); const countLabel = document.getElementById('admin-count-label'); if (!tbody) return; tbody.innerHTML = ''; const filtered = allItems.filter(item => item.type === currentAdminTab); if (countLabel) { countLabel.textContent = `${filtered.length} élément(s) dans cette catégorie`; } if (filtered.length === 0) { tbody.innerHTML = ` Aucun film trouvé dans cette liste.`; updateBulkBarVisibility(); return; } filtered.forEach(f => { const tr = document.createElement('tr'); const imgHTML = f.poster ? `${f.title}` : `
`; const dynamicCell = currentAdminTab === 'critique' ? `${'★'.repeat(f.rating)}${'☆'.repeat(5 - f.rating)}` : `${f.format || 'Physique'}`; tr.innerHTML = ` ${imgHTML} ${f.title} ${f.year || '—'} ${f.director || 'Inconnu'} ${dynamicCell}
`; tbody.appendChild(tr); }); updateBulkBarVisibility(); } // ══════════════════════════════════════════════════════════════════ // 2. SÉLECTION EN MASSE // ══════════════════════════════════════════════════════════════════ function toggleSelectAll(master) { document.querySelectorAll('.film-checkbox').forEach(cb => cb.checked = master.checked); updateBulkBarVisibility(); } function updateBulkBarVisibility() { const checked = document.querySelectorAll('.film-checkbox:checked'); const bulkBar = document.getElementById('bulk-actions-bar'); const bulkCount = document.getElementById('bulk-count'); const selectAll = document.getElementById('select-all-checkbox'); if (!bulkBar || !bulkCount) return; if (checked.length > 0) { bulkBar.style.display = 'flex'; bulkCount.textContent = checked.length; } else { bulkBar.style.display = 'none'; if (selectAll) selectAll.checked = false; } } async function executeBulkDelete() { const checked = document.querySelectorAll('.film-checkbox:checked'); const idsToDelete = Array.from(checked).map(cb => cb.value); if (!idsToDelete.length) return; if (!confirm(`Supprimer définitivement ces ${idsToDelete.length} élément(s) ?`)) return; try { const res = await fetch(`${API_URL}?action=delete_multiple_films&type=${currentAdminTab}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('token') }, body: JSON.stringify({ ids: idsToDelete }) }); const data = await res.json(); if (data.success) { const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) selectAll.checked = false; loadDashboardData(); } else { alert('Erreur suppression groupée : ' + data.error); } } catch (err) { console.error('Bulk delete :', err); } } // ══════════════════════════════════════════════════════════════════ // 3. SUPPRESSION UNITAIRE // ══════════════════════════════════════════════════════════════════ async function deleteSingleFilm(id) { if (!confirm('Supprimer définitivement cette œuvre ?')) return; try { const res = await fetch(`${API_URL}?action=delete_film&id=${id}&type=${currentAdminTab}`, { method: 'DELETE', headers: { 'Authorization': localStorage.getItem('token') } }); const data = await res.json(); if (data.success) loadDashboardData(); } catch (err) { console.error(err); } } // ══════════════════════════════════════════════════════════════════ // 4. MODALES FILM (ouvrir / fermer / remplir) // ══════════════════════════════════════════════════════════════════ function openAddModal() { document.getElementById('film-form').reset(); document.getElementById('f-id').value = ''; document.getElementById('modal-form-title').textContent = currentAdminTab === 'critique' ? 'Rédiger une Critique' : 'Ajouter un film physique'; document.getElementById('form-critique-fields').style.display = currentAdminTab === 'critique' ? 'block' : 'none'; document.getElementById('form-videotheque-fields').style.display = currentAdminTab === 'videotheque' ? 'block' : 'none'; document.getElementById('admin-modal').classList.add('open'); } function openEditModal(id) { const item = allItems.find(x => String(x.id) === String(id)); if (!item) return; openAddModal(); document.getElementById('modal-form-title').textContent = "Modifier l'œuvre"; document.getElementById('f-id').value = item.id; document.getElementById('f-title').value = item.title || ''; document.getElementById('f-year').value = item.year || ''; document.getElementById('f-director').value = item.director || ''; document.getElementById('f-poster').value = item.poster || ''; if (currentAdminTab === 'critique') { document.getElementById('f-rating').value = item.rating || 3; document.getElementById('f-review').value = item.review || ''; document.getElementById('f-streaming').value = item.streaming || ''; } else { document.getElementById('f-format').value = item.format || ''; document.getElementById('f-length').value = item.length || ''; document.getElementById('f-publisher').value = item.publisher || ''; document.getElementById('f-aspect').value = item.aspect_ratio || ''; document.getElementById('f-ean').value = item.ean_isbn13 || ''; document.getElementById('f-discs').value = item.number_of_discs || 1; document.getElementById('f-description').value = item.description || ''; } } function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); } // ══════════════════════════════════════════════════════════════════ // 5. SAUVEGARDE FORMULAIRE FILM // ══════════════════════════════════════════════════════════════════ async function saveFilmForm(e) { e.preventDefault(); const payload = { type: currentAdminTab, id: document.getElementById('f-id').value || null, title: document.getElementById('f-title').value, year: document.getElementById('f-year').value, director: document.getElementById('f-director').value, poster: document.getElementById('f-poster').value }; if (currentAdminTab === 'critique') { payload.rating = parseInt(document.getElementById('f-rating').value); payload.review = document.getElementById('f-review').value; payload.streaming = document.getElementById('f-streaming').value; } else { payload.format = document.getElementById('f-format').value; payload.length = document.getElementById('f-length').value; payload.publisher = document.getElementById('f-publisher').value; payload.aspect_ratio = document.getElementById('f-aspect').value; payload.ean_isbn13 = document.getElementById('f-ean').value; payload.number_of_discs = document.getElementById('f-discs').value; payload.description = document.getElementById('f-description').value; } try { const res = await fetch(`${API_URL}?action=save_film`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('token') }, body: JSON.stringify(payload) }); const data = await res.json(); if (data.success) { closeAdminModal(); loadDashboardData(); } else { alert('Erreur lors de l\'enregistrement.'); } } catch (err) { console.error('Erreur save :', err); } } // ══════════════════════════════════════════════════════════════════ // 6. IMPORT CSV (Letterboxd) // ══════════════════════════════════════════════════════════════════ async function handleCsvUpload(input) { if (!input.files || input.files.length === 0) return; const formData = new FormData(); formData.append('csv_file', input.files[0]); try { const res = await fetch(`${API_URL}?action=import_csv`, { method: 'POST', headers: { 'Authorization': localStorage.getItem('token') }, body: formData }); const data = await res.json(); if (data.success) { alert(`✅ Import réussi ! ${data.imported} films ajoutés ou mis à jour.`); loadDashboardData(); } else { alert('❌ Erreur import : ' + (data.error || 'Erreur inconnue')); } } catch (err) { console.error('Erreur Import CSV :', err); alert('Impossible d\'importer le fichier.'); } finally { input.value = ''; } } // ══════════════════════════════════════════════════════════════════ // 7. MODALE TMDB // ══════════════════════════════════════════════════════════════════ function openConfigModal() { document.getElementById('config-modal').classList.add('open'); } function closeConfigModal() { document.getElementById('config-modal').classList.remove('open'); } async function saveTmdbKey() { const key = document.getElementById('tmdb-key-input').value.trim(); if (!key) { alert('Veuillez saisir une clé.'); return; } try { const res = await fetch(`${API_URL}?action=save_tmdb_key`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('token') }, body: JSON.stringify({ tmdb_key: key }) }); const data = await res.json(); if (data.success) { alert('✅ Clé TMDB chiffrée et sauvegardée.'); document.getElementById('tmdb-key-input').value = ''; closeConfigModal(); } } catch (err) { console.error(err); } } // ══════════════════════════════════════════════════════════════════ // 8. MODALE MOT DE PASSE // ══════════════════════════════════════════════════════════════════ function openPasswordModal() { document.getElementById('password-modal').classList.add('open'); } function closePasswordModal() { document.getElementById('password-modal').classList.remove('open'); } async function saveNewPassword() { const pwd1 = document.getElementById('new-password-input').value; const pwd2 = document.getElementById('new-password-confirm').value; const errEl = document.getElementById('pwd-error'); errEl.style.display = 'none'; if (pwd1.length < 4) { errEl.textContent = 'Le mot de passe doit faire au moins 4 caractères.'; errEl.style.display = 'block'; return; } if (pwd1 !== pwd2) { errEl.textContent = 'Les mots de passe ne correspondent pas.'; errEl.style.display = 'block'; return; } try { // Premier usage (compte vide) → setup_admin, sinon update_password const secRes = await fetch(`${API_URL}?action=check_security_status`); const secData = await secRes.json(); const action = secData.is_blank ? 'setup_admin' : 'update_password'; const res = await fetch(`${API_URL}?action=${action}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': localStorage.getItem('token') }, body: JSON.stringify({ password: pwd1, new_password: pwd1 }) }); const data = await res.json(); if (data.success || data.success === '') { alert('✅ Mot de passe mis à jour. Vous allez être redirigé vers la connexion.'); localStorage.removeItem('token'); window.location.href = 'login.html'; } } catch (err) { console.error(err); } } // ══════════════════════════════════════════════════════════════════ // 9. DÉCONNEXION // ══════════════════════════════════════════════════════════════════ function logout() { localStorage.removeItem('token'); window.location.href = 'login.html'; } // ══════════════════════════════════════════════════════════════════ // INIT // ══════════════════════════════════════════════════════════════════ document.addEventListener('DOMContentLoaded', loadDashboardData); >>>>>>> 5a22e3f (all)