diff --git a/js/admin.js b/js/admin.js index 58e0416..a9680c3 100644 --- a/js/admin.js +++ b/js/admin.js @@ -6,6 +6,16 @@ const itemsPerPage = 12; let selectedIds = new Set(); let pendingDeleteAction = null; +// ── UTILITAIRES DOM SÉCURISÉS (Anti-Crash) ── +function safeGetValue(id, defaultValue = '') { + const el = document.getElementById(id); + return el ? el.value : defaultValue; +} +function safeSetValue(id, value) { + const el = document.getElementById(id); + if (el) el.value = value; +} + // ── GÉNÉRATEUR D'ÉTOILES ── function getStarsHTML(rating) { const r = parseFloat(rating) || 0; @@ -112,39 +122,40 @@ async function loadDashboardData() { } catch (err) { console.error('Erreur chargement :', err); } } -// ── RENDU DU TABLEAU (PAGINATION CORRIGÉE) ── +// ── FILTRES (Répare le bug de la recherche combinée) ── +function getFilteredItems() { + const searchInput = document.getElementById('search-input'); + const currentSearch = searchInput ? searchInput.value.toLowerCase() : ''; + let filtered = allItems.filter(item => item.type === currentAdminTab); + if (currentSearch) { + filtered = filtered.filter(f => + (f.title && f.title.toLowerCase().includes(currentSearch)) || + (f.director && f.director.toLowerCase().includes(currentSearch)) + ); + } + return filtered; +} + +// ── RENDU DU TABLEAU (6 COLONNES) ── function renderAdminTable() { const tbody = document.getElementById('admin-table-body'); if (!tbody) return; tbody.innerHTML = ''; - const searchInput = document.getElementById('search-input'); - const currentSearch = searchInput ? searchInput.value.toLowerCase() : ''; - - let filtered = allItems.filter(item => item.type === currentAdminTab); - - if (currentSearch) { - filtered = filtered.filter(f => - f.title.toLowerCase().includes(currentSearch) || - (f.director && f.director.toLowerCase().includes(currentSearch)) - ); - } + const filtered = getFilteredItems(); const countLabel = document.getElementById('admin-count-label'); if(countLabel) countLabel.textContent = `${filtered.length} élément(s)`; - // --- LOGIQUE DE PAGINATION --- const totalPages = Math.ceil(filtered.length / itemsPerPage) || 1; if (currentPage > totalPages) currentPage = totalPages; const startIdx = (currentPage - 1) * itemsPerPage; const pageItems = filtered.slice(startIdx, startIdx + itemsPerPage); - // ----------------------------- pageItems.forEach(f => { const tr = document.createElement('tr'); const isChecked = selectedIds.has(String(f.id)) ? 'checked' : ''; - // 6 COLONNES (Plus d'affiche) tr.innerHTML = ` @@ -170,21 +181,21 @@ function renderAdminTable() { } } -// ── SÉLECTION ── +// ── SÉLECTION & BULK ── function toggleSingleSelect(id, checkbox) { if (checkbox.checked) selectedIds.add(String(id)); else selectedIds.delete(String(id)); updateBulkBar(); - const filtered = allItems.filter(item => item.type === currentAdminTab); + const pageItems = getFilteredItems().slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage); const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) { - selectAll.checked = filtered.length > 0 && filtered.every(f => selectedIds.has(String(f.id))); + selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id))); } } function toggleSelectAll(source) { - const filtered = allItems.filter(item => item.type === currentAdminTab); + const filtered = getFilteredItems(); if (source.checked) { filtered.forEach(f => selectedIds.add(String(f.id))); } else { @@ -273,7 +284,7 @@ function createEllipsis() { return span; } -// ── MODALES & UI ── +// ── POP-UP CONFIRMATION ── function showConfirmModal(actionFn) { pendingDeleteAction = actionFn; const modal = document.getElementById('confirm-modal'); @@ -285,6 +296,7 @@ function closeConfirmModal() { pendingDeleteAction = null; } +// ── SUPPRESSIONS ── async function executeBulkDelete() { const ids = Array.from(selectedIds); if (ids.length === 0) return; @@ -297,9 +309,7 @@ async function executeBulkDelete() { }); if (!res.ok) throw new Error("Erreur serveur."); selectedIds.clear(); - document.getElementById('bulk-actions-bar').style.display = 'none'; - const selectAll = document.getElementById('select-all-checkbox'); - if (selectAll) selectAll.checked = false; + updateBulkBar(); loadDashboardData(); } catch (err) { console.error('Erreur bulk delete :', err); @@ -316,6 +326,8 @@ async function deleteSingleFilm(id) { headers: { 'Authorization': localStorage.getItem('token') } }); if (!res.ok) throw new Error("Erreur serveur."); + selectedIds.delete(String(id)); + updateBulkBar(); loadDashboardData(); } catch (err) { console.error('Erreur delete :', err); @@ -324,6 +336,7 @@ async function deleteSingleFilm(id) { }); } +// ── MODALES & UI ── function toggleFormFields() { const critFields = document.getElementById('form-critique-fields'); const vidFields = document.getElementById('form-videotheque-fields'); @@ -337,43 +350,52 @@ function switchAdminTab(tabName) { selectedIds.clear(); const searchInput = document.getElementById('search-input'); if (searchInput) searchInput.value = ''; + document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); const btn = document.getElementById(`btn-tab-${tabName}`); if (btn) btn.classList.add('active'); + toggleFormFields(); + updateBulkBar(); renderAdminTable(); } function openAddModal() { - document.getElementById('film-form').reset(); - document.getElementById('f-id').value = ''; + const form = document.getElementById('film-form'); + if (form) form.reset(); + safeSetValue('f-id', ''); toggleFormFields(); - document.getElementById('admin-modal').classList.add('open'); + const modal = document.getElementById('admin-modal'); + if (modal) modal.classList.add('open'); } function openEditModal(id) { const item = allItems.find(x => String(x.id) === String(id)); if (!item) return; - 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 || ''; - toggleFormFields(); + + safeSetValue('f-id', item.id); + safeSetValue('f-title', item.title); + safeSetValue('f-year', item.year); + safeSetValue('f-director', item.director); + safeSetValue('f-poster', 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 || ''; + safeSetValue('f-rating', item.rating || 3); + safeSetValue('f-review', item.review); + safeSetValue('f-streaming', 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 || ''; + safeSetValue('f-format', item.format); + safeSetValue('f-length', item.length); + safeSetValue('f-publisher', item.publisher); + safeSetValue('f-aspect', item.aspect_ratio); + safeSetValue('f-ean', item.ean_isbn13); + safeSetValue('f-discs', item.number_of_discs || 1); + safeSetValue('f-description', item.description); } - document.getElementById('admin-modal').classList.add('open'); + toggleFormFields(); + + const modal = document.getElementById('admin-modal'); + if (modal) modal.classList.add('open'); } function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); } @@ -389,26 +411,42 @@ function logout() { window.location.href = 'login.html'; } -// ── SAUVEGARDES ── +// ── PROGRESS BAR ── +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'); +} +function updateProgressModal(current, total) { + const pct = Math.round((current / total) * 100); + document.getElementById('progress-bar').style.width = pct + '%'; + document.getElementById('progress-count').textContent = `${current} / ${total}`; +} +function closeProgressModal() { + document.getElementById('progress-overlay').classList.remove('open'); +} + +// ── SAUVEGARDE FILM ── async function saveFilmForm(e) { e.preventDefault(); const payload = { type: currentAdminTab, - id: document.getElementById('f-id').value, - title: document.getElementById('f-title').value, - year: document.getElementById('f-year').value, - director: document.getElementById('f-director').value, - poster: document.getElementById('f-poster').value, - rating: document.getElementById('f-rating') ? document.getElementById('f-rating').value : '', - review: document.getElementById('f-review') ? document.getElementById('f-review').value : '', - streaming: document.getElementById('f-streaming') ? document.getElementById('f-streaming').value : '', - format: document.getElementById('f-format') ? document.getElementById('f-format').value : '', - length: document.getElementById('f-length') ? document.getElementById('f-length').value : '', - publisher: document.getElementById('f-publisher') ? document.getElementById('f-publisher').value : '', - aspect_ratio: document.getElementById('f-aspect') ? document.getElementById('f-aspect').value : '', - ean_isbn13: document.getElementById('f-ean') ? document.getElementById('f-ean').value : '', - number_of_discs: document.getElementById('f-discs') ? document.getElementById('f-discs').value : 1, - description: document.getElementById('f-description') ? document.getElementById('f-description').value : '' + id: safeGetValue('f-id'), + title: safeGetValue('f-title'), + year: safeGetValue('f-year'), + director: safeGetValue('f-director'), + poster: safeGetValue('f-poster'), + rating: safeGetValue('f-rating', 3), + review: safeGetValue('f-review'), + streaming: safeGetValue('f-streaming'), + format: safeGetValue('f-format'), + length: safeGetValue('f-length'), + publisher: safeGetValue('f-publisher'), + aspect_ratio: safeGetValue('f-aspect'), + ean_isbn13: safeGetValue('f-ean'), + number_of_discs: safeGetValue('f-discs', 1), + description: safeGetValue('f-description') }; try { await fetch(`${API_URL}?action=save_film`, { @@ -421,20 +459,21 @@ async function saveFilmForm(e) { } catch (err) { console.error('Erreur sauvegarde :', err); } } +// ── IMPORT CSV ── 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; } + if (allData.length === 0) { + alert('❌ Le fichier CSV est vide ou mal formaté.'); + return; + } closeConfigModal(); - - const progressOverlay = document.getElementById('progress-overlay'); - const progressBar = document.getElementById('progress-bar'); - const progressCount = document.getElementById('progress-count'); - if(progressOverlay) progressOverlay.classList.add('open'); + showProgressModal(allData.length); const batchSize = 5; let processed = 0; @@ -448,19 +487,18 @@ async function handleCsvUpload(input) { }); } catch (err) { console.error('Erreur sur un lot:', err); } processed += batch.length; - const pct = Math.round((processed / allData.length) * 100); - if(progressBar) progressBar.style.width = pct + '%'; - if(progressCount) progressCount.textContent = `${processed} / ${allData.length}`; + updateProgressModal(processed, allData.length); } - if(progressOverlay) progressOverlay.classList.remove('open'); + closeProgressModal(); alert(`✅ Import terminé ! ${allData.length} élément(s) traité(s).`); loadDashboardData(); } catch (err) { - console.error('Erreur lecture CSV :', err); - alert('Impossible de lire le fichier CSV.'); + closeProgressModal(); + alert(' Impossible de lire le fichier CSV.'); } } +// ── SAUVEGARDE CLÉ TMDB ── async function saveTmdbKey() { const input = document.getElementById('tmdb-key-input'); if (input && input.value) { @@ -471,17 +509,23 @@ async function saveTmdbKey() { body: JSON.stringify({ key_name: 'tmdb_api_key', key_value: input.value }) }); const data = await res.json(); - if (data.success) { alert('✅ Clé API sauvegardée !'); closeConfigModal(); } - else { alert('❌ Erreur : ' + (data.error || 'Impossible de sauvegarder.')); } + if (data.success) { + alert('✅ Clé API sauvegardée !'); + closeConfigModal(); + } else { + alert('❌ Erreur : ' + (data.error || 'Impossible de sauvegarder.')); + } } catch (err) { alert('Erreur de communication avec le serveur.'); } } } +// ── SAUVEGARDE MOT DE PASSE ── async function saveNewPassword() { const pwdInput = document.getElementById('new-password-input'); const pwdConfirm = document.getElementById('new-password-confirm'); const errorMsg = document.getElementById('pwd-error'); if (!pwdInput || !pwdConfirm) return; + if (pwdInput.value !== pwdConfirm.value) { errorMsg.textContent = "Les mots de passe ne correspondent pas."; errorMsg.style.display = "block"; @@ -492,6 +536,7 @@ async function saveNewPassword() { errorMsg.style.display = "block"; return; } + try { const response = await fetch(`${API_URL}?action=update_password`, { method: 'POST', @@ -500,10 +545,12 @@ async function saveNewPassword() { }); const data = await response.json(); if (data.success) { - pwdInput.value = ''; pwdConfirm.value = ''; errorMsg.style.display = "none"; + pwdInput.value = ''; + pwdConfirm.value = ''; + errorMsg.style.display = "none"; closePasswordModal(); alert('Mot de passe mis à jour.'); loadDashboardData(); } - } catch (err) { console.error('Erreur mise à jour mot de passe :', err); } + } catch (err) { console.error('Erreur :', err); } } \ No newline at end of file