Actualiser js/admin.js

This commit is contained in:
2026-07-01 12:05:30 +02:00
parent fc0c11e355
commit 64ddb21b79
+67 -86
View File
@@ -1,6 +1,6 @@
const API_URL = '../api.php'; const API_URL = '../api.php';
let allItems = []; let allItems = [];
let currentAdminTab = 'critique'; let currentAdminTab = 'videotheque'; // Défaut sur vidéothèque
let currentPage = 1; let currentPage = 1;
const itemsPerPage = 12; const itemsPerPage = 12;
let selectedIds = new Set(); let selectedIds = new Set();
@@ -56,6 +56,8 @@ function parseCSV(text) {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
loadDashboardData(); loadDashboardData();
initEventListeners(); initEventListeners();
updateImportInterface(); // Mise à jour initiale de l'interface d'import
const confirmBtn = document.getElementById('confirm-btn'); const confirmBtn = document.getElementById('confirm-btn');
if (confirmBtn) { if (confirmBtn) {
confirmBtn.addEventListener('click', () => { confirmBtn.addEventListener('click', () => {
@@ -68,6 +70,7 @@ document.addEventListener('DOMContentLoaded', () => {
function initEventListeners() { function initEventListeners() {
const filmForm = document.getElementById('film-form'); const filmForm = document.getElementById('film-form');
if (filmForm) filmForm.addEventListener('submit', saveFilmForm); if (filmForm) filmForm.addEventListener('submit', saveFilmForm);
const csvInput = document.getElementById('csv-file'); const csvInput = document.getElementById('csv-file');
if (csvInput) { if (csvInput) {
csvInput.addEventListener('change', (e) => { csvInput.addEventListener('change', (e) => {
@@ -75,10 +78,13 @@ function initEventListeners() {
else handleVideothequeUpload(e.target); else handleVideothequeUpload(e.target);
}); });
} }
const searchInput = document.getElementById('search-input'); const searchInput = document.getElementById('search-input');
if (searchInput) searchInput.addEventListener('input', () => { currentPage = 1; renderAdminTable(); }); if (searchInput) searchInput.addEventListener('input', () => { currentPage = 1; renderAdminTable(); });
const selectAll = document.getElementById('select-all-checkbox'); const selectAll = document.getElementById('select-all-checkbox');
if (selectAll) selectAll.addEventListener('change', (e) => toggleSelectAll(e.target)); if (selectAll) selectAll.addEventListener('change', (e) => toggleSelectAll(e.target));
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if (e.target.classList.contains('modal-close') || e.target.closest('.modal-close')) { if (e.target.classList.contains('modal-close') || e.target.closest('.modal-close')) {
const overlay = e.target.closest('.overlay'); const overlay = e.target.closest('.overlay');
@@ -86,6 +92,7 @@ function initEventListeners() {
} }
if (e.target.classList.contains('overlay')) e.target.classList.remove('open'); if (e.target.classList.contains('overlay')) e.target.classList.remove('open');
}); });
const physicalFilter = document.getElementById('admin-physical-checkbox'); const physicalFilter = document.getElementById('admin-physical-checkbox');
if (physicalFilter) physicalFilter.addEventListener('change', () => { currentPage = 1; renderAdminTable(); }); if (physicalFilter) physicalFilter.addEventListener('change', () => { currentPage = 1; renderAdminTable(); });
} }
@@ -106,12 +113,13 @@ function renderAdminTable() {
const tbody = document.getElementById('admin-table-body'); const tbody = document.getElementById('admin-table-body');
if (!tbody) return; if (!tbody) return;
tbody.innerHTML = ''; tbody.innerHTML = '';
const searchInput = document.getElementById('search-input'); const searchInput = document.getElementById('search-input');
const currentSearch = searchInput ? searchInput.value.toLowerCase() : ''; const currentSearch = searchInput ? searchInput.value.toLowerCase() : '';
const physicalFilter = document.getElementById('admin-physical-checkbox'); const physicalFilter = document.getElementById('admin-physical-checkbox');
let filtered = allItems.filter(item => item.type === currentAdminTab); let filtered = allItems.filter(item => item.type === currentAdminTab);
if (physicalFilter && physicalFilter.checked) { if (physicalFilter && physicalFilter.checked) {
filtered = filtered.filter(f => { filtered = filtered.filter(f => {
if (f.type === 'critique') { if (f.type === 'critique') {
@@ -120,22 +128,22 @@ function renderAdminTable() {
return true; return true;
}); });
} }
if (currentSearch) { if (currentSearch) {
filtered = filtered.filter(f => filtered = filtered.filter(f =>
f.title.toLowerCase().includes(currentSearch) || f.title.toLowerCase().includes(currentSearch) ||
(f.director && f.director.toLowerCase().includes(currentSearch)) (f.director && f.director.toLowerCase().includes(currentSearch))
); );
} }
const countLabel = document.getElementById('admin-count-label'); const countLabel = document.getElementById('admin-count-label');
if (countLabel) countLabel.textContent = `${filtered.length} élément(s)`; if (countLabel) countLabel.textContent = `${filtered.length} élément(s)`;
const totalPages = Math.ceil(filtered.length / itemsPerPage) || 1; const totalPages = Math.ceil(filtered.length / itemsPerPage) || 1;
if (currentPage > totalPages) currentPage = totalPages; if (currentPage > totalPages) currentPage = totalPages;
const startIdx = (currentPage - 1) * itemsPerPage; const startIdx = (currentPage - 1) * itemsPerPage;
const pageItems = filtered.slice(startIdx, startIdx + itemsPerPage); const pageItems = filtered.slice(startIdx, startIdx + itemsPerPage);
pageItems.forEach(f => { pageItems.forEach(f => {
const tr = document.createElement('tr'); const tr = document.createElement('tr');
const isChecked = selectedIds.has(String(f.id)) ? 'checked' : ''; const isChecked = selectedIds.has(String(f.id)) ? 'checked' : '';
@@ -154,9 +162,9 @@ function renderAdminTable() {
</td>`; </td>`;
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
renderPagination(totalPages, filtered.length); renderPagination(totalPages, filtered.length);
const selectAll = document.getElementById('select-all-checkbox'); const selectAll = document.getElementById('select-all-checkbox');
if (selectAll) selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id))); if (selectAll) selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id)));
} }
@@ -192,9 +200,9 @@ function renderPagination(totalPages, totalItems) {
const container = document.getElementById('pagination-container'); const container = document.getElementById('pagination-container');
if (!container) return; if (!container) return;
container.innerHTML = ''; container.innerHTML = '';
if (totalItems === 0) { if (totalItems === 0) {
container.innerHTML = '<p style="color:var(--muted); text-align:center; width:100%;">Aucun élément trouvé.</p>'; container.innerHTML = '<p style="color:var(--muted); text-align:center; width:100%;">Aucun élément trouvé.</p>';
return; return;
} }
if (totalPages <= 1) return; if (totalPages <= 1) return;
@@ -202,16 +210,16 @@ function renderPagination(totalPages, totalItems) {
info.className = 'pagination-info'; info.className = 'pagination-info';
info.textContent = `Page ${currentPage} sur ${totalPages}`; info.textContent = `Page ${currentPage} sur ${totalPages}`;
container.appendChild(info); container.appendChild(info);
const prevBtn = document.createElement('button'); const prevBtn = document.createElement('button');
prevBtn.innerHTML = '<i class="ti ti-chevron-left"></i>'; prevBtn.innerHTML = '<i class="ti ti-chevron-left"></i>';
prevBtn.disabled = currentPage === 1; prevBtn.disabled = currentPage === 1;
prevBtn.onclick = () => { currentPage--; renderAdminTable(); }; prevBtn.onclick = () => { currentPage--; renderAdminTable(); };
container.appendChild(prevBtn); container.appendChild(prevBtn);
let startPage = Math.max(1, currentPage - 2); let startPage = Math.max(1, currentPage - 2);
let endPage = Math.min(totalPages, currentPage + 2); let endPage = Math.min(totalPages, currentPage + 2);
if (startPage > 1) { if (startPage > 1) {
const firstBtn = document.createElement('button'); const firstBtn = document.createElement('button');
firstBtn.textContent = '1'; firstBtn.textContent = '1';
@@ -223,7 +231,7 @@ function renderPagination(totalPages, totalItems) {
container.appendChild(dots); container.appendChild(dots);
} }
} }
for (let i = startPage; i <= endPage; i++) { for (let i = startPage; i <= endPage; i++) {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.textContent = i; btn.textContent = i;
@@ -231,7 +239,7 @@ function renderPagination(totalPages, totalItems) {
btn.onclick = () => { currentPage = i; renderAdminTable(); }; btn.onclick = () => { currentPage = i; renderAdminTable(); };
container.appendChild(btn); container.appendChild(btn);
} }
if (endPage < totalPages) { if (endPage < totalPages) {
if (endPage < totalPages - 1) { if (endPage < totalPages - 1) {
const dots = document.createElement('span'); const dots = document.createElement('span');
@@ -243,7 +251,7 @@ function renderPagination(totalPages, totalItems) {
lastBtn.onclick = () => { currentPage = totalPages; renderAdminTable(); }; lastBtn.onclick = () => { currentPage = totalPages; renderAdminTable(); };
container.appendChild(lastBtn); container.appendChild(lastBtn);
} }
const nextBtn = document.createElement('button'); const nextBtn = document.createElement('button');
nextBtn.innerHTML = '<i class="ti ti-chevron-right"></i>'; nextBtn.innerHTML = '<i class="ti ti-chevron-right"></i>';
nextBtn.disabled = currentPage === totalPages; nextBtn.disabled = currentPage === totalPages;
@@ -337,26 +345,21 @@ async function openConfigModal() {
try { 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(); const data = await res.json();
const tmdbIn = document.getElementById('tmdb-key-input'); const tmdbIn = document.getElementById('tmdb-key-input');
if (tmdbIn) tmdbIn.placeholder = data.tmdb_api_key ? '✅ Clé TMDB configurée' : 'Entrez votre clé TMDB'; if (tmdbIn) tmdbIn.placeholder = data.tmdb_api_key ? '✅ Clé TMDB configurée' : 'Entrez votre clé TMDB';
const upcIn = document.getElementById('upcmdb-key-input'); const upcIn = document.getElementById('upcmdb-key-input');
if (upcIn) upcIn.placeholder = data.upcmdb_api_key ? '✅ Clé UPCMDB configurée' : 'Clé gratuite sur upcmdb.com'; if (upcIn) upcIn.placeholder = data.upcmdb_api_key ? '✅ Clé UPCMDB configurée' : 'Clé gratuite sur upcmdb.com';
} catch(e) { console.error(e); } } catch(e) { console.error(e); }
} }
function closeConfigModal() { document.getElementById('config-modal').classList.remove('open'); } function closeConfigModal() { document.getElementById('config-modal').classList.remove('open'); }
function openPasswordModal() { function openPasswordModal() {
document.getElementById('pwd-error').style.display = 'none'; document.getElementById('pwd-error').style.display = 'none';
document.getElementById('new-password-input').value = ''; document.getElementById('new-password-input').value = '';
document.getElementById('new-password-confirm').value = ''; document.getElementById('new-password-confirm').value = '';
document.getElementById('password-modal').classList.add('open'); document.getElementById('password-modal').classList.add('open');
} }
function closePasswordModal() { document.getElementById('password-modal').classList.remove('open'); } function closePasswordModal() { document.getElementById('password-modal').classList.remove('open'); }
function logout() { localStorage.removeItem('token'); window.location.href = 'login.html'; } function logout() { localStorage.removeItem('token'); window.location.href = 'login.html'; }
async function saveFilmForm(e) { async function saveFilmForm(e) {
@@ -411,98 +414,76 @@ async function handleCritiqueUpload(input) {
loadDashboardData(); loadDashboardData();
} }
// ✅ CORRECTION : Extrait TOUTES les métadonnées du CSV pour ne rien perdre
function normalizeVideothequeRow(row) { function normalizeVideothequeRow(row) {
// Recherche de l'EAN dans plusieurs colonnes possibles
let ean = row['ean_isbn13'] || row['EAN'] || row['ean'] || row['Barcode'] || row['UPC'] || row['upc_isbn10'] || ''; let ean = row['ean_isbn13'] || row['EAN'] || row['ean'] || row['Barcode'] || row['UPC'] || row['upc_isbn10'] || '';
ean = String(ean).replace(/[^0-9]/g, ''); if (ean) ean = String(ean).replace(/[^0-9]/g, '');
// Recherche du titre dans plusieurs colonnes let title = row['title'] || row['Title'] || row['Titre'] || '';
let title = row['title'] || row['Title'] || row['Titre'] || row['titre'] || row['nom'] || row['Nom'] || '';
title = String(title).trim(); title = String(title).trim();
// Si on n'a ni EAN ni titre, on ignore
if (!ean && !title) return null; if (!ean && !title) return null;
return { ean, title }; return {
ean: ean,
title: title,
year: row['publish_date'] ? String(row['publish_date']).substring(0, 4) : (row['Year'] || row['year'] || ''),
synopsis: row['description'] || '',
review: row['review'] || '',
rating: row['rating'] || '',
publisher: row['publisher'] || '',
length: row['length'] || '',
number_of_discs: row['number_of_discs'] || 1,
aspect_ratio: row['aspect_ratio'] || '',
actors: row['creators'] || row['ensemble'] || '' // 'creators' contient le casting dans votre CSV
};
} }
function handleVideothequeUpload(input) { function handleVideothequeUpload(input) {
const file = input.files[0]; const file = input.files[0];
if (!file) return; if (!file) return;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = async (e) => { reader.onload = async (e) => {
const text = e.target.result; const text = e.target.result;
const parsed = parseCSV(text); const parsed = parseCSV(text);
if (!parsed.length) { if (!parsed.length) { alert("❌ CSV vide."); return; }
alert("❌ CSV vide ou mal formaté.");
input.value = '';
return;
}
// Transformer chaque ligne en objet {ean, title}
const items = parsed.map(row => normalizeVideothequeRow(row)).filter(Boolean); const items = parsed.map(row => normalizeVideothequeRow(row)).filter(Boolean);
if (!items.length) { if (!items.length) { alert("❌ Aucun titre ou EAN valide trouvé."); return; }
alert("❌ Aucun titre ou EAN valide trouvé dans le CSV.");
input.value = '';
return;
}
console.log(`Lignes parsées : ${parsed.length}, items valides : ${items.length}`);
// Ouvrir la modale de progression
showImportModal(items.length, 'videotheque'); showImportModal(items.length, 'videotheque');
let processed = 0; let processed = 0, totalImp = 0, totalSkp = 0;
let totalImported = 0;
let totalSkipped = 0;
// Taille du lot : 10 films par requête
const BATCH_SIZE = 10;
try { try {
// ✅ Envoi par lots de 5 pour que le throttle PHP fonctionne au sein du même script
const BATCH_SIZE = 5;
for (let i = 0; i < items.length; i += BATCH_SIZE) { for (let i = 0; i < items.length; i += BATCH_SIZE) {
const batch = items.slice(i, i + BATCH_SIZE); const batch = items.slice(i, i + BATCH_SIZE);
const response = await fetch(`${API_URL}?action=import_batch`, { const res = await fetch(`${API_URL}?action=import_batch`, {
method: 'POST', method: 'POST',
headers: { headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
'Authorization': localStorage.getItem('token'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: 'videotheque', items: batch }) body: JSON.stringify({ type: 'videotheque', items: batch })
}); });
if (!response.ok) { if (!res.ok) throw new Error(`Erreur HTTP ${res.status}`);
throw new Error(`Erreur HTTP ${response.status} - ${response.statusText}`); const data = await res.json();
} if (!data.success) throw new Error(data.error || "Échec API ");
const result = await response.json(); totalImp += data.imported || 0;
if (!result.success) { totalSkp += data.skipped || 0;
throw new Error(result.error || "Erreur inconnue lors de l'import.");
}
totalImported += result.imported || 0;
totalSkipped += result.skipped || 0;
processed += batch.length; processed += batch.length;
// Mise à jour de la barre de progression
updateImportModal(processed, items.length); updateImportModal(processed, items.length);
await new Promise(r => setTimeout(r, 500)); // Petit délai réseau
// Petit délai pour éviter de saturer le serveur
await new Promise(resolve => setTimeout(resolve, 200));
} }
// Fermer la modale
closeImportModal();
input.value = ''; input.value = '';
closeImportModal();
const msg = totalSkipped > 0 ? ` (${totalSkipped} ignoré(s))` : ''; const msg = totalSkp > 0 ? ` (${totalSkp} ignoré(s))` : '';
showSuccessModal(`${totalImported} film(s) importé(s) avec succès.${msg}`); showSuccessModal(`${totalImp} édition(s) importée(s).${msg}`);
loadDashboardData(); loadDashboardData();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
closeImportModal(); closeImportModal();
alert(`❌ Échec de l'import : ${err.message}`); alert("❌ Échec import : " + err.message);
input.value = ''; input.value = '';
} }
}; };
@@ -528,7 +509,6 @@ function closeImportModal() { document.getElementById('import-progress-modal').c
async function saveConfigKeys() { async function saveConfigKeys() {
const tmdb = document.getElementById('tmdb-key-input').value.trim(); const tmdb = document.getElementById('tmdb-key-input').value.trim();
const upcmdb = document.getElementById('upcmdb-key-input').value.trim(); const upcmdb = document.getElementById('upcmdb-key-input').value.trim();
try { try {
if (tmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'tmdb_api_key',key_value:tmdb}) }); if (tmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'tmdb_api_key',key_value:tmdb}) });
if (upcmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'upcmdb_api_key',key_value:upcmdb}) }); if (upcmdb) await fetch(`${API_URL}?action=save_config`, { method:'POST', headers:{'Authorization':localStorage.getItem('token'),'Content-Type':'application/json'}, body:JSON.stringify({key_name:'upcmdb_api_key',key_value:upcmdb}) });
@@ -550,15 +530,16 @@ async function saveNewPassword() {
} catch (err) { console.error(err); } } catch (err) { console.error(err); }
} }
// ✅ SÉPARATION CLAIRE DES IMPORTS
function updateImportInterface() { function updateImportInterface() {
const title = document.getElementById('import-title'); const title = document.getElementById('import-title');
const desc = document.getElementById('import-desc'); const desc = document.getElementById('import-desc');
if (currentAdminTab === 'videotheque') { if (currentAdminTab === 'videotheque') {
title.innerHTML = '<strong>Importer ma Vidéothèque</strong>'; title.innerHTML = '<strong>Importer ma Vidéothèque</strong>';
desc.textContent = 'Import CSV (EAN + Titre) : Blu-ray.com pour le physique, TMDB pour l\'affiche, le casting et le synopsis.'; desc.textContent = 'Importez votre CSV (ex: CLC, MediaControl). Le système récupérera les jaquettes HD via MovieCovers/Blu-ray.com et complétera avec TMDB.';
} else { } else {
title.innerHTML = '<strong>Importer Critiques & Notes</strong>'; title.innerHTML = '<strong>Importer Critiques & Notes</strong>';
desc.textContent = 'Sélectionnez vos fichiers "ratings.csv" et "reviews.csv" (Letterboxd).'; desc.textContent = 'Importez vos fichiers CSV de notes et critiques (ex: Letterboxd ou export personnalisé).';
} }
} }