From d96938eff659797d1c03382733ce9c95a8cbb263 Mon Sep 17 00:00:00 2001 From: Cedric Date: Sun, 21 Jun 2026 14:18:09 +0200 Subject: [PATCH] Actualiser js/admin.js --- js/admin.js | 320 +++++++++++++++++++++------------------------------- 1 file changed, 129 insertions(+), 191 deletions(-) diff --git a/js/admin.js b/js/admin.js index b7417ec..ce8daaf 100644 --- a/js/admin.js +++ b/js/admin.js @@ -5,13 +5,14 @@ let currentPage = 1; const itemsPerPage = 12; let selectedIds = new Set(); let pendingDeleteAction = null; -let adminPhysicalOnlyFilter = false; +let physicalOnlyFilter = false; -// ── UTILITAIRES DOM SÉCURISÉS (Anti-Crash) ── +// ─ UTILITAIRES DOM SÉCURISÉS ── 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; @@ -66,6 +67,24 @@ function parseCSV(text) { return data; } +// ── BARRE DE PROGRESSION ── +function showProgressModal(total) { + document.getElementById('progress-text').textContent = 'Traitement des films et récupération TMDB...'; + 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'); +} + // ── INITIALISATION ── document.addEventListener('DOMContentLoaded', () => { loadDashboardData(); @@ -80,14 +99,6 @@ document.addEventListener('DOMContentLoaded', () => { }); function initEventListeners() { - const physicalCheckbox = document.getElementById('admin-physical-checkbox'); - if (physicalCheckbox) { - physicalCheckbox.addEventListener('change', (e) => { - adminPhysicalOnlyFilter = e.target.checked; - currentPage = 1; - renderAdminTable(); - }); - } const filmForm = document.getElementById('film-form'); if (filmForm) filmForm.addEventListener('submit', saveFilmForm); @@ -107,6 +118,16 @@ function initEventListeners() { selectAll.addEventListener('change', (e) => toggleSelectAll(e.target)); } + // Filtre support physique + const physicalCheckbox = document.getElementById('admin-physical-checkbox'); + if (physicalCheckbox) { + physicalCheckbox.addEventListener('change', (e) => { + physicalOnlyFilter = e.target.checked; + currentPage = 1; + renderAdminTable(); + }); + } + document.addEventListener('click', (e) => { if (e.target.classList.contains('modal-close') || e.target.closest('.modal-close')) { const overlay = e.target.closest('.overlay'); @@ -118,7 +139,7 @@ function initEventListeners() { }); } -// ── CHARGEMENT ── +// ── CHARGEMENT DES DONNÉES ── async function loadDashboardData() { try { const res = await fetch(`${API_URL}?action=get_films`, { cache: 'no-store' }); @@ -131,149 +152,88 @@ async function loadDashboardData() { } catch (err) { console.error('Erreur chargement :', err); } } -// ── 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 (VERSION COMPLÈTE ET CORRIGÉE) ── +// ── RENDU DU TABLEAU ── function renderAdminTable() { const tbody = document.getElementById('admin-table-body'); if (!tbody) return; tbody.innerHTML = ''; - // 1. Récupération des valeurs de filtrage const searchInput = document.getElementById('search-input'); - const query = searchInput ? searchInput.value.toLowerCase().trim() : ''; - - const physicalCheckbox = document.getElementById('admin-physical-checkbox'); - const isPhysicalOnly = physicalCheckbox ? physicalCheckbox.checked : false; + const currentSearch = searchInput ? searchInput.value.toLowerCase() : ''; - // 2. Filtrage des données let filtered = allItems.filter(item => item.type === currentAdminTab); // Filtre support physique / cinéma uniquement - if (isPhysicalOnly) { - filtered = filtered.filter(item => { - const streaming = item.streaming ? item.streaming.trim() : ''; + if (physicalOnlyFilter) { + filtered = filtered.filter(f => { + const streaming = f.streaming ? f.streaming.trim() : ''; return !streaming || streaming === 'Disponible en support physique ou Cinéma'; }); } // Filtre recherche - if (query) { - filtered = filtered.filter(item => - item.title.toLowerCase().includes(query) || - (item.director && item.director.toLowerCase().includes(query)) + if (currentSearch) { + filtered = filtered.filter(f => + f.title.toLowerCase().includes(currentSearch) || + (f.director && f.director.toLowerCase().includes(currentSearch)) ); } - // 3. Mise à jour du compteur const countLabel = document.getElementById('admin-count-label'); if (countLabel) countLabel.textContent = `${filtered.length} élément(s)`; - // 4. Logique de pagination + // Pagination const totalPages = Math.ceil(filtered.length / itemsPerPage) || 1; if (currentPage > totalPages) currentPage = totalPages; - const startIndex = (currentPage - 1) * itemsPerPage; - const pageItems = filtered.slice(startIndex, startIndex + itemsPerPage); + const startIdx = (currentPage - 1) * itemsPerPage; + const pageItems = filtered.slice(startIdx, startIdx + itemsPerPage); - // 5. Génération des lignes (6 colonnes : Checkbox, Titre, Année, Réalisateur, Info, Actions) - pageItems.forEach(item => { + pageItems.forEach(f => { const tr = document.createElement('tr'); - const isChecked = selectedIds.has(String(item.id)) ? 'checked' : ''; - - const infoHTML = currentAdminTab === 'critique' - ? `${getStarsHTML(item.rating)}` - : `${item.format || '-'}`; - + const isChecked = selectedIds.has(String(f.id)) ? 'checked' : ''; tr.innerHTML = ` - + - ${item.title} - ${item.year || '-'} - ${item.director || '-'} - ${infoHTML} - + + ${f.poster ? `Affiche` : '
'} + + ${f.title} + ${f.year || '-'} + ${f.director || '-'} + ${currentAdminTab === 'critique' ? `${getStarsHTML(f.rating)}` : `${f.format || '-'}`} +
- - + +
- - `; + `; tbody.appendChild(tr); }); - // 6. Rendu de la pagination renderPagination(totalPages, filtered.length); - // 7. Synchronisation de la case "Tout sélectionner" - syncSelectAllCheckbox(); -} - -// ── GESTION DES CHECKBOXES INDIVIDUELLES ─ -function handleFilmCheckbox(checkbox) { - const id = checkbox.value; - if (checkbox.checked) { - selectedIds.add(id); - } else { - selectedIds.delete(id); - } - updateBulkBar(); - syncSelectAllCheckbox(); -} - -// ─ SYNCHRONISATION DU "TOUT SÉLECTIONNER" ── -function syncSelectAllCheckbox() { - const selectAll = document.getElementById('select-all-checkbox'); - if (!selectAll) return; - - const visibleCheckboxes = document.querySelectorAll('#admin-table-body .film-checkbox'); - const allChecked = visibleCheckboxes.length > 0 && Array.from(visibleCheckboxes).every(cb => cb.checked); - - selectAll.checked = allChecked; -} - -// ── BARRE D'ACTIONS GROUPÉES ── -function updateBulkBar() { - const bulkBar = document.getElementById('bulk-actions-bar'); - const bulkCount = document.getElementById('bulk-count'); - - if (!bulkBar || !bulkCount) return; - - if (selectedIds.size > 0) { - bulkBar.style.display = 'flex'; - bulkCount.textContent = selectedIds.size; - } else { - bulkBar.style.display = 'none'; - } -} - -// ── SÉLECTION & BULK ── -function toggleSingleSelect(id, checkbox) { - if (checkbox.checked) selectedIds.add(String(id)); - else selectedIds.delete(String(id)); - updateBulkBar(); - - const pageItems = getFilteredItems().slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage); const selectAll = document.getElementById('select-all-checkbox'); if (selectAll) { selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id))); } } +// ── SÉLECTION ── +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 selectAll = document.getElementById('select-all-checkbox'); + if (selectAll) { + selectAll.checked = filtered.length > 0 && filtered.every(f => selectedIds.has(String(f.id))); + } +} + function toggleSelectAll(source) { - const filtered = getFilteredItems(); + const filtered = allItems.filter(item => item.type === currentAdminTab); if (source.checked) { filtered.forEach(f => selectedIds.add(String(f.id))); } else { @@ -293,8 +253,6 @@ function updateBulkBar() { if (bulkCount) bulkCount.textContent = selectedIds.size; } else { if (bulkBar) bulkBar.style.display = 'none'; - const selectAll = document.getElementById('select-all-checkbox'); - if (selectAll) selectAll.checked = false; } } @@ -368,6 +326,7 @@ function showConfirmModal(actionFn) { const modal = document.getElementById('confirm-modal'); if (modal) modal.classList.add('open'); } + function closeConfirmModal() { const modal = document.getElementById('confirm-modal'); if (modal) modal.classList.remove('open'); @@ -387,7 +346,9 @@ async function executeBulkDelete() { }); if (!res.ok) throw new Error("Erreur serveur."); selectedIds.clear(); - updateBulkBar(); + document.getElementById('bulk-actions-bar').style.display = 'none'; + const selectAll = document.getElementById('select-all-checkbox'); + if (selectAll) selectAll.checked = false; loadDashboardData(); } catch (err) { console.error('Erreur bulk delete :', err); @@ -404,8 +365,6 @@ 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); @@ -428,54 +387,43 @@ 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() { - const form = document.getElementById('film-form'); - if (form) form.reset(); - safeSetValue('f-id', ''); + document.getElementById('film-form').reset(); + document.getElementById('f-id').value = ''; toggleFormFields(); - const modal = document.getElementById('admin-modal'); - if (modal) modal.classList.add('open'); + document.getElementById('admin-modal').classList.add('open'); } function openEditModal(id) { const item = allItems.find(x => String(x.id) === String(id)); if (!item) return; - - 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') { - // Remplacez la ligne existante par celle-ci : - document.getElementById('f-rating').value = parseFloat(item.rating || 3); - - document.getElementById('f-review').value = item.review || ''; - document.getElementById('f-streaming').value = item.streaming || ''; - } else { - 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('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(); - - const modal = document.getElementById('admin-modal'); - if (modal) modal.classList.add('open'); + 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 || ''; + } + document.getElementById('admin-modal').classList.add('open'); } function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); } @@ -491,42 +439,26 @@ function logout() { window.location.href = 'login.html'; } -// ── 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: 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') + 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 : '' }; try { await fetch(`${API_URL}?action=save_film`, { @@ -539,12 +471,11 @@ async function saveFilmForm(e) { } catch (err) { console.error('Erreur sauvegarde :', err); } } -// ── IMPORT CSV ── +// ─ IMPORT CSV PAR LOTS ── 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); @@ -562,10 +493,15 @@ async function handleCsvUpload(input) { try { await fetch(`${API_URL}?action=import_batch`, { method: 'POST', - headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' }, + 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); } + } catch (err) { + console.error('Erreur sur un lot:', err); + } processed += batch.length; updateProgressModal(processed, allData.length); } @@ -573,8 +509,9 @@ async function handleCsvUpload(input) { 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.'); + alert('Impossible de lire le fichier CSV.'); } } @@ -590,12 +527,15 @@ async function saveTmdbKey() { }); const data = await res.json(); if (data.success) { - alert('✅ Clé API sauvegardée !'); + alert('✅ Clé API sauvegardée et chiffrée en base de données !'); closeConfigModal(); } else { alert('❌ Erreur : ' + (data.error || 'Impossible de sauvegarder.')); } - } catch (err) { alert('Erreur de communication avec le serveur.'); } + } catch (err) { + console.error('Erreur sauvegarde clé :', err); + alert('Erreur de communication avec le serveur.'); + } } } @@ -605,7 +545,6 @@ async function saveNewPassword() { 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"; @@ -616,7 +555,6 @@ async function saveNewPassword() { errorMsg.style.display = "block"; return; } - try { const response = await fetch(`${API_URL}?action=update_password`, { method: 'POST', @@ -632,5 +570,5 @@ async function saveNewPassword() { alert('Mot de passe mis à jour.'); loadDashboardData(); } - } catch (err) { console.error('Erreur :', err); } + } catch (err) { console.error('Erreur mise à jour mot de passe :', err); } } \ No newline at end of file