Actualiser js/admin.js
This commit is contained in:
+43
-39
@@ -1,20 +1,25 @@
|
|||||||
const API_URL = '../api.php';
|
const API_URL = '../api.php';
|
||||||
let allItems = [], currentAdminTab = 'critique', currentPage = 1, selectedIds = new Set();
|
let allItems = [];
|
||||||
|
let currentAdminTab = 'critique';
|
||||||
|
let currentPage = 1;
|
||||||
|
const itemsPerPage = 12;
|
||||||
|
let selectedIds = new Set();
|
||||||
|
let pendingDeleteAction = null;
|
||||||
|
|
||||||
// --- Utilitaires ---
|
function getStarsHTML(rating) {
|
||||||
const getStarsHTML = (rating) => {
|
let r = parseFloat(String(rating).replace(',', '.')) || 0;
|
||||||
let r = Math.min(Math.max(parseFloat(rating) || 0, 0), 5);
|
r = Math.min(Math.max(r, 0), 5);
|
||||||
const full = Math.floor(r), hasHalf = (r - full) >= 0.5, empty = Math.max(0, 5 - Math.ceil(r));
|
const full = Math.floor(r);
|
||||||
return '★'.repeat(full) + (hasHalf ? '<span class="half-star">★</span>' : '') + `<span class="stars-muted">${'☆'.repeat(empty)}</span>`;
|
const hasHalf = (r - full) >= 0.5;
|
||||||
};
|
const empty = Math.max(0, 5 - Math.ceil(r));
|
||||||
|
let html = '★'.repeat(full);
|
||||||
|
if (hasHalf) html += '<span class="half-star">★</span>';
|
||||||
|
html += `<span class="stars-muted">${'☆'.repeat(empty)}</span>`;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
function parseCSV(text) {
|
function parseCSV(text) {
|
||||||
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
||||||
|
|
||||||
// ✅ NOUVEAU : Détection automatique du séparateur le plus utilisé sur la première ligne
|
|
||||||
const firstLine = text.split(/[\r\n]+/)[0] || '';
|
|
||||||
const sep = (firstLine.match(/;/g) || []).length > (firstLine.match(/,/g) || []).length ? ';' : ',';
|
|
||||||
|
|
||||||
const rows = [];
|
const rows = [];
|
||||||
let col = '', row = [], inQuotes = false;
|
let col = '', row = [], inQuotes = false;
|
||||||
for (let i = 0; i < text.length; i++) {
|
for (let i = 0; i < text.length; i++) {
|
||||||
@@ -26,7 +31,7 @@ function parseCSV(text) {
|
|||||||
} else col += c;
|
} else col += c;
|
||||||
} else {
|
} else {
|
||||||
if (c === '"') inQuotes = true;
|
if (c === '"') inQuotes = true;
|
||||||
else if (c === sep) { row.push(col); col = ''; } // ✅ On utilise le séparateur détecté
|
else if (c === ',') { row.push(col); col = ''; }
|
||||||
else if (c === '\n' || c === '\r') {
|
else if (c === '\n' || c === '\r') {
|
||||||
if (c === '\r' && text[i+1] === '\n') i++;
|
if (c === '\r' && text[i+1] === '\n') i++;
|
||||||
row.push(col); col = '';
|
row.push(col); col = '';
|
||||||
@@ -104,9 +109,9 @@ function renderAdminTable() {
|
|||||||
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') {
|
||||||
@@ -115,22 +120,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' : '';
|
||||||
@@ -149,9 +154,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,21 +197,21 @@ function renderPagination(totalPages, totalItems) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (totalPages <= 1) return;
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
const info = document.createElement('span');
|
const info = document.createElement('span');
|
||||||
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';
|
||||||
@@ -218,7 +223,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;
|
||||||
@@ -226,7 +231,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');
|
||||||
@@ -238,7 +243,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;
|
||||||
@@ -327,7 +332,6 @@ function openEditModal(id) {
|
|||||||
|
|
||||||
function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); }
|
function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); }
|
||||||
|
|
||||||
// Nettoyage Config Modal (Suppression Fanart)
|
|
||||||
async function openConfigModal() {
|
async function openConfigModal() {
|
||||||
document.getElementById('config-modal').classList.add('open');
|
document.getElementById('config-modal').classList.add('open');
|
||||||
try {
|
try {
|
||||||
@@ -407,19 +411,20 @@ async function handleCritiqueUpload(input) {
|
|||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ CORRECTION : Retourne l'EAN ET le titre du CSV
|
||||||
function normalizeVideothequeRow(row) {
|
function normalizeVideothequeRow(row) {
|
||||||
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'] || '';
|
||||||
if (ean) ean = String(ean).replace(/[^0-9]/g, '');
|
if (ean) ean = String(ean).replace(/[^0-9]/g, '');
|
||||||
|
|
||||||
// ✅ Tolérance sur le nom de la colonne
|
// ✅ Récupérer le titre du CSV
|
||||||
let title = row['Title'] || row['title'] || row['Titre'] || row['titre'] || row['Name'] || row['name'] || '';
|
let title = row['title'] || row['Title'] || row['Titre'] || '';
|
||||||
title = String(title).trim();
|
title = String(title).trim();
|
||||||
|
|
||||||
if (!ean && !title) return null;
|
if (!ean && !title) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ean: ean,
|
ean: ean,
|
||||||
title: title
|
title: title // ✅ Titre envoyé au backend
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,9 +437,10 @@ function handleVideothequeUpload(input) {
|
|||||||
const parsed = parseCSV(text);
|
const parsed = parseCSV(text);
|
||||||
if (!parsed.length) { alert("❌ CSV vide."); return; }
|
if (!parsed.length) { alert("❌ CSV vide."); return; }
|
||||||
|
|
||||||
const items = parsed.map(row => normalizeVideothequeRow(row)).filter(Boolean);
|
// ✅ CORRECTION : Utiliser normalizeVideothequeRow qui retourne EAN + titre
|
||||||
|
const items = parsed.map(row => normalizeVideothequeRow(row)).filter(Boolean);
|
||||||
if (!items.length) { alert("❌ Aucun titre ou EAN valide trouvé."); return; }
|
|
||||||
|
if (!items.length) { alert("❌ Aucun titre ou EAN valide trouvé."); return; }
|
||||||
|
|
||||||
showImportModal(items.length, 'videotheque');
|
showImportModal(items.length, 'videotheque');
|
||||||
let processed = 0, totalImp = 0, totalSkp = 0;
|
let processed = 0, totalImp = 0, totalSkp = 0;
|
||||||
@@ -443,8 +449,6 @@ function handleVideothequeUpload(input) {
|
|||||||
// ✅ Lots de 1 seul pour éviter le blocage Blu-ray.com
|
// ✅ Lots de 1 seul pour éviter le blocage Blu-ray.com
|
||||||
for (let i = 0; i < items.length; i += 1) {
|
for (let i = 0; i < items.length; i += 1) {
|
||||||
const batch = items.slice(i, i + 1);
|
const batch = items.slice(i, i + 1);
|
||||||
// ✅ AJOUTEZ CECI POUR VOIR QUEL FILM BLOQUE
|
|
||||||
console.log("Tentative d'import de : ", batch[0].title);
|
|
||||||
const res = await fetch(`${API_URL}?action=import_batch`, {
|
const res = await fetch(`${API_URL}?action=import_batch`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
||||||
@@ -459,7 +463,7 @@ function handleVideothequeUpload(input) {
|
|||||||
totalSkp += data.skipped || 0;
|
totalSkp += data.skipped || 0;
|
||||||
processed += batch.length;
|
processed += batch.length;
|
||||||
updateImportModal(processed, items.length);
|
updateImportModal(processed, items.length);
|
||||||
await new Promise(r => setTimeout(r, 500)); // ✅ Délai de 5.5s pour éviter le blocage
|
await new Promise(r => setTimeout(r, 500)); // Délai de sécurité
|
||||||
}
|
}
|
||||||
|
|
||||||
input.value = '';
|
input.value = '';
|
||||||
@@ -523,7 +527,7 @@ function updateImportInterface() {
|
|||||||
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 seul) : UPCitemdb/UPCMDB pour le physique, TMDB pour l\'affiche, le casting et le synopsis.';
|
desc.textContent = 'Import CSV (EAN + Titre) : Blu-ray.com pour le physique, TMDB pour l\'affiche, le casting et le synopsis.';
|
||||||
} 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 = 'Sélectionnez vos fichiers "ratings.csv" et "reviews.csv" (Letterboxd).';
|
||||||
|
|||||||
Reference in New Issue
Block a user