Actualiser js/admin.js
This commit is contained in:
+102
-373
@@ -17,7 +17,6 @@ function getStarsHTML(rating) {
|
|||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── PARSER CSV RENFORCÉ ──
|
|
||||||
function parseCSV(text) {
|
function parseCSV(text) {
|
||||||
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
|
||||||
const rows = [];
|
const rows = [];
|
||||||
@@ -47,9 +46,7 @@ function parseCSV(text) {
|
|||||||
for (let i = 1; i < rows.length; i++) {
|
for (let i = 1; i < rows.length; i++) {
|
||||||
if (rows[i].length === 1 && rows[i][0].trim() === '') continue;
|
if (rows[i].length === 1 && rows[i][0].trim() === '') continue;
|
||||||
const obj = {};
|
const obj = {};
|
||||||
headers.forEach((h, idx) => {
|
headers.forEach((h, idx) => { obj[h] = rows[i][idx] !== undefined ? rows[i][idx] : ''; });
|
||||||
obj[h] = rows[i][idx] !== undefined ? rows[i][idx] : '';
|
|
||||||
});
|
|
||||||
data.push(obj);
|
data.push(obj);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
@@ -70,15 +67,22 @@ 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);
|
||||||
|
|
||||||
|
// 🔥 DISPATCHER D'IMPORT : Appelle la bonne fonction selon l'onglet actif
|
||||||
const csvInput = document.getElementById('csv-file');
|
const csvInput = document.getElementById('csv-file');
|
||||||
if (csvInput) csvInput.addEventListener('change', (e) => handleCsvUpload(e.target));
|
if (csvInput) {
|
||||||
|
csvInput.addEventListener('change', (e) => {
|
||||||
|
if (currentAdminTab === 'critique') {
|
||||||
|
handleCritiqueUpload(e.target);
|
||||||
|
} else {
|
||||||
|
handleVideothequeUpload(e.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
if (searchInput) {
|
if (searchInput) {
|
||||||
searchInput.addEventListener('input', () => {
|
searchInput.addEventListener('input', () => { currentPage = 1; renderAdminTable(); });
|
||||||
currentPage = 1;
|
|
||||||
renderAdminTable();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectAll = document.getElementById('select-all-checkbox');
|
const selectAll = document.getElementById('select-all-checkbox');
|
||||||
@@ -89,17 +93,12 @@ function initEventListeners() {
|
|||||||
const overlay = e.target.closest('.overlay');
|
const overlay = e.target.closest('.overlay');
|
||||||
if (overlay) overlay.classList.remove('open');
|
if (overlay) overlay.classList.remove('open');
|
||||||
}
|
}
|
||||||
if (e.target.classList.contains('overlay')) {
|
if (e.target.classList.contains('overlay')) e.target.classList.remove('open');
|
||||||
e.target.classList.remove('open');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const physicalFilter = document.getElementById('admin-physical-checkbox');
|
const physicalFilter = document.getElementById('admin-physical-checkbox');
|
||||||
if (physicalFilter) {
|
if (physicalFilter) {
|
||||||
physicalFilter.addEventListener('change', () => {
|
physicalFilter.addEventListener('change', () => { currentPage = 1; renderAdminTable(); });
|
||||||
currentPage = 1;
|
|
||||||
renderAdminTable();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,12 +147,8 @@ function renderAdminTable() {
|
|||||||
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' : '';
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td style="text-align:center;">
|
<td style="text-align:center;"><input type="checkbox" class="film-checkbox" value="${f.id}" ${isChecked} onclick="toggleSingleSelect('${f.id}', this)"></td>
|
||||||
<input type="checkbox" class="film-checkbox" value="${f.id}" ${isChecked} onclick="toggleSingleSelect('${f.id}', this)">
|
<td style="text-align:center;">${f.poster ? `<img src="${f.poster}" class="thumb" alt="Affiche">` : '<div class="thumb-ph"><i class="ti ti-photo"></i></div>'}</td>
|
||||||
</td>
|
|
||||||
<td style="text-align:center;">
|
|
||||||
${f.poster ? `<img src="${f.poster}" class="thumb" alt="Affiche">` : '<div class="thumb-ph"><i class="ti ti-photo"></i></div>'}
|
|
||||||
</td>
|
|
||||||
<td><strong>${f.title}</strong></td>
|
<td><strong>${f.title}</strong></td>
|
||||||
<td>${f.year || '-'}</td>
|
<td>${f.year || '-'}</td>
|
||||||
<td>${f.director || '-'}</td>
|
<td>${f.director || '-'}</td>
|
||||||
@@ -168,34 +163,21 @@ function renderAdminTable() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
renderPagination(totalPages, filtered.length);
|
renderPagination(totalPages, filtered.length);
|
||||||
|
|
||||||
const selectAll = document.getElementById('select-all-checkbox');
|
const selectAll = document.getElementById('select-all-checkbox');
|
||||||
if (selectAll) {
|
if (selectAll) selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id)));
|
||||||
selectAll.checked = pageItems.length > 0 && pageItems.every(f => selectedIds.has(String(f.id)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSingleSelect(id, checkbox) {
|
function toggleSingleSelect(id, checkbox) {
|
||||||
if (checkbox.checked) selectedIds.add(String(id));
|
if (checkbox.checked) selectedIds.add(String(id));
|
||||||
else selectedIds.delete(String(id));
|
else selectedIds.delete(String(id));
|
||||||
updateBulkBar();
|
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) {
|
function toggleSelectAll(source) {
|
||||||
const filtered = allItems.filter(item => item.type === currentAdminTab);
|
const filtered = allItems.filter(item => item.type === currentAdminTab);
|
||||||
if (source.checked) {
|
if (source.checked) filtered.forEach(f => selectedIds.add(String(f.id)));
|
||||||
filtered.forEach(f => selectedIds.add(String(f.id)));
|
else filtered.forEach(f => selectedIds.delete(String(f.id)));
|
||||||
} else {
|
document.querySelectorAll('.film-checkbox').forEach(cb => { cb.checked = selectedIds.has(cb.value); });
|
||||||
filtered.forEach(f => selectedIds.delete(String(f.id)));
|
|
||||||
}
|
|
||||||
document.querySelectorAll('.film-checkbox').forEach(cb => {
|
|
||||||
cb.checked = selectedIds.has(cb.value);
|
|
||||||
});
|
|
||||||
updateBulkBar();
|
updateBulkBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,10 +198,7 @@ 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>'; return; }
|
||||||
container.innerHTML = '<p style="color:var(--muted); text-align:center; width:100%;">Aucun élément trouvé.</p>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (totalPages <= 1) return;
|
if (totalPages <= 1) return;
|
||||||
|
|
||||||
const info = document.createElement('span');
|
const info = document.createElement('span');
|
||||||
@@ -233,22 +212,12 @@ function renderPagination(totalPages, totalItems) {
|
|||||||
prevBtn.onclick = () => { currentPage--; renderAdminTable(); };
|
prevBtn.onclick = () => { currentPage--; renderAdminTable(); };
|
||||||
container.appendChild(prevBtn);
|
container.appendChild(prevBtn);
|
||||||
|
|
||||||
const maxButtons = 5;
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
let startPage = Math.max(1, currentPage - Math.floor(maxButtons / 2));
|
const btn = document.createElement('button');
|
||||||
let endPage = Math.min(totalPages, startPage + maxButtons - 1);
|
btn.textContent = i;
|
||||||
if (endPage - startPage + 1 < maxButtons) {
|
if (i === currentPage) btn.classList.add('active');
|
||||||
startPage = Math.max(1, endPage - maxButtons + 1);
|
btn.onclick = () => { currentPage = i; renderAdminTable(); };
|
||||||
}
|
container.appendChild(btn);
|
||||||
if (startPage > 1) {
|
|
||||||
container.appendChild(createPageBtn(1));
|
|
||||||
if (startPage > 2) container.appendChild(createEllipsis());
|
|
||||||
}
|
|
||||||
for (let i = startPage; i <= endPage; i++) {
|
|
||||||
container.appendChild(createPageBtn(i));
|
|
||||||
}
|
|
||||||
if (endPage < totalPages) {
|
|
||||||
if (endPage < totalPages - 1) container.appendChild(createEllipsis());
|
|
||||||
container.appendChild(createPageBtn(totalPages));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextBtn = document.createElement('button');
|
const nextBtn = document.createElement('button');
|
||||||
@@ -258,70 +227,28 @@ function renderPagination(totalPages, totalItems) {
|
|||||||
container.appendChild(nextBtn);
|
container.appendChild(nextBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPageBtn(num) {
|
function showConfirmModal(actionFn) { pendingDeleteAction = actionFn; document.getElementById('confirm-modal')?.classList.add('open'); }
|
||||||
const btn = document.createElement('button');
|
function closeConfirmModal() { document.getElementById('confirm-modal')?.classList.remove('open'); pendingDeleteAction = null; }
|
||||||
btn.textContent = num;
|
|
||||||
if (num === currentPage) btn.classList.add('active');
|
|
||||||
btn.onclick = () => { currentPage = num; renderAdminTable(); };
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEllipsis() {
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.textContent = '...';
|
|
||||||
span.style.color = 'var(--muted)';
|
|
||||||
span.style.padding = '0 0.5rem';
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showConfirmModal(actionFn) {
|
|
||||||
pendingDeleteAction = 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');
|
|
||||||
pendingDeleteAction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeBulkDelete() {
|
async function executeBulkDelete() {
|
||||||
const ids = Array.from(selectedIds);
|
const ids = Array.from(selectedIds);
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
showConfirmModal(async () => {
|
showConfirmModal(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}?action=bulk_delete`, {
|
await fetch(`${API_URL}?action=bulk_delete`, { method: 'POST', headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' }, body: JSON.stringify({ ids, type: currentAdminTab }) });
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ ids, type: currentAdminTab })
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error("Erreur serveur.");
|
|
||||||
selectedIds.clear();
|
selectedIds.clear();
|
||||||
document.getElementById('bulk-actions-bar').style.display = 'none';
|
document.getElementById('bulk-actions-bar').style.display = 'none';
|
||||||
const selectAll = document.getElementById('select-all-checkbox');
|
|
||||||
if (selectAll) selectAll.checked = false;
|
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
} catch (err) {
|
} catch (err) { alert("Erreur serveur."); }
|
||||||
console.error('Erreur bulk delete :', err);
|
|
||||||
alert("Une erreur est survenue.");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSingleFilm(id) {
|
async function deleteSingleFilm(id) {
|
||||||
showConfirmModal(async () => {
|
showConfirmModal(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}?action=delete_film&id=${id}&type=${currentAdminTab}`, {
|
await fetch(`${API_URL}?action=delete_film&id=${id}&type=${currentAdminTab}`, { method: 'DELETE', headers: { 'Authorization': localStorage.getItem('token') } });
|
||||||
method: 'DELETE',
|
|
||||||
headers: { 'Authorization': localStorage.getItem('token') }
|
|
||||||
});
|
|
||||||
if (!res.ok) throw new Error("Erreur serveur.");
|
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
} catch (err) {
|
} catch (err) { alert("Erreur serveur."); }
|
||||||
console.error('Erreur delete :', err);
|
|
||||||
alert("Une erreur est survenue.");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,28 +265,20 @@ function switchAdminTab(tabName) {
|
|||||||
selectedIds.clear();
|
selectedIds.clear();
|
||||||
const searchInput = document.getElementById('search-input');
|
const searchInput = document.getElementById('search-input');
|
||||||
if (searchInput) searchInput.value = '';
|
if (searchInput) searchInput.value = '';
|
||||||
|
|
||||||
const physicalFilter = document.getElementById('admin-physical-checkbox');
|
const physicalFilter = document.getElementById('admin-physical-checkbox');
|
||||||
if (physicalFilter) {
|
if (physicalFilter) {
|
||||||
physicalFilter.checked = false; // On réinitialise le filtre à chaque changement d'onglet
|
physicalFilter.checked = false;
|
||||||
|
|
||||||
// On cherche le conteneur parent (le <label> ou la <div> qui entoure la case et le texte)
|
|
||||||
const wrapper = physicalFilter.closest('label') || physicalFilter.parentElement;
|
const wrapper = physicalFilter.closest('label') || physicalFilter.parentElement;
|
||||||
|
if (wrapper) wrapper.style.display = (tabName === 'videotheque') ? 'none' : 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
if (wrapper) {
|
|
||||||
// Si on est en mode Vidéothèque, on cache tout. Sinon, on réaffiche.
|
|
||||||
wrapper.style.display = (tabName === 'videotheque') ? 'none' : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
const btn = document.getElementById(`btn-tab-${tabName}`);
|
document.getElementById(`btn-tab-${tabName}`)?.classList.add('active');
|
||||||
if (btn) btn.classList.add('active');
|
|
||||||
|
|
||||||
updateImportInterface();
|
updateImportInterface();
|
||||||
toggleFormFields();
|
toggleFormFields();
|
||||||
renderAdminTable();
|
renderAdminTable();
|
||||||
updateImportInterface();
|
|
||||||
toggleFormFields();
|
|
||||||
renderAdminTable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openAddModal() {
|
function openAddModal() {
|
||||||
@@ -395,304 +314,114 @@ function openEditModal(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); }
|
function closeAdminModal() { document.getElementById('admin-modal').classList.remove('open'); }
|
||||||
|
|
||||||
async function openConfigModal() {
|
|
||||||
document.getElementById('config-modal').classList.add('open');
|
|
||||||
document.getElementById('tmdb-key-input').placeholder = 'Pour les critiques (réalisateur, streaming)';
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}?action=get_config_keys`, { headers: { 'Authorization': localStorage.getItem('token') } });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.tmdb_api_key) document.getElementById('tmdb-key-input').placeholder = '✅ Clé configurée (laisser vide pour ne pas changer)';
|
|
||||||
} 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() {
|
|
||||||
document.getElementById('pwd-error').style.display = 'none';
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveFilmForm(e) {
|
async function saveFilmForm(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const payload = {
|
const payload = {
|
||||||
type: currentAdminTab,
|
type: currentAdminTab, id: document.getElementById('f-id').value,
|
||||||
id: document.getElementById('f-id').value,
|
title: document.getElementById('f-title').value, year: document.getElementById('f-year').value,
|
||||||
title: document.getElementById('f-title').value,
|
director: document.getElementById('f-director').value, poster: document.getElementById('f-poster').value,
|
||||||
year: document.getElementById('f-year').value,
|
rating: document.getElementById('f-rating')?.value || '', review: document.getElementById('f-review')?.value || '',
|
||||||
director: document.getElementById('f-director').value,
|
streaming: document.getElementById('f-streaming')?.value || '', format: document.getElementById('f-format')?.value || '',
|
||||||
poster: document.getElementById('f-poster').value,
|
length: document.getElementById('f-length')?.value || '', publisher: document.getElementById('f-publisher')?.value || '',
|
||||||
rating: document.getElementById('f-rating') ? document.getElementById('f-rating').value : '',
|
aspect_ratio: document.getElementById('f-aspect')?.value || '', ean_isbn13: document.getElementById('f-ean')?.value || '',
|
||||||
review: document.getElementById('f-review') ? document.getElementById('f-review').value : '',
|
number_of_discs: document.getElementById('f-discs')?.value || 1, description: document.getElementById('f-description')?.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 {
|
try {
|
||||||
await fetch(`${API_URL}?action=save_film`, {
|
await fetch(`${API_URL}?action=save_film`, { method: 'POST', headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
||||||
method: 'POST',
|
closeAdminModal(); loadDashboardData();
|
||||||
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
} catch (err) { console.error(err); }
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
closeAdminModal();
|
|
||||||
loadDashboardData();
|
|
||||||
} catch (err) { console.error('Erreur sauvegarde :', err); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- FONCTION D'IMPORT INTELLIGENTE (CORRIGÉE) ---
|
// ══════════════════════════════════════════════════════════════
|
||||||
async function handleCsvUpload(input) {
|
// 🔥 FONCTION 1 : IMPORT CRITIQUE (Letterboxd)
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
async function handleCritiqueUpload(input) {
|
||||||
if (!input.files || input.files.length === 0) return;
|
if (!input.files || input.files.length === 0) return;
|
||||||
let allData = [];
|
let allData = [];
|
||||||
|
|
||||||
// 1. Lecture de tous les fichiers
|
|
||||||
for (const file of input.files) {
|
for (const file of input.files) {
|
||||||
try {
|
try { allData = allData.concat(parseCSV(await file.text())); } catch(e) {}
|
||||||
const text = await file.text();
|
|
||||||
const data = parseCSV(text);
|
|
||||||
allData = allData.concat(data);
|
|
||||||
} catch(e) {
|
|
||||||
console.error('Erreur lecture fichier', file.name, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
input.value = '';
|
||||||
|
if (allData.length === 0) return alert('❌ Fichier vide.');
|
||||||
|
|
||||||
input.value = ''; // Reset l'input file
|
showImportModal(allData.length, 'critique');
|
||||||
|
|
||||||
if (allData.length === 0) {
|
|
||||||
alert('❌ Fichiers vides ou mal formatés.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 🔥 CORRECTION : Mapping intelligent pour votre format CSV spécifique
|
|
||||||
let finalData = allData.map(row => {
|
|
||||||
// 1. Titre (gestion des guillemets et espaces)
|
|
||||||
const rawTitle = (row['title'] || row['Title'] || row['Name'] || '').trim();
|
|
||||||
|
|
||||||
// 2. Année (extraction depuis publish_date au format YYYY-MM-DD)
|
|
||||||
let year = '';
|
|
||||||
const dateStr = (row['publish_date'] || row['Date'] || row['Year'] || '').toString();
|
|
||||||
const yearMatch = dateStr.match(/(\d{4})/);
|
|
||||||
if (yearMatch) year = yearMatch[1];
|
|
||||||
|
|
||||||
// 3. Nettoyage du titre (enlever les mentions [Blu-ray], (DVD), etc.)
|
|
||||||
let cleanTitle = rawTitle.replace(/\s*\[(Blu-ray|DVD|4K|Coffret|Combo).*?\]/gi, '').trim();
|
|
||||||
cleanTitle = cleanTitle.replace(/\s*\(.*?DVD.*?\)/gi, '').trim();
|
|
||||||
if (!cleanTitle) cleanTitle = rawTitle;
|
|
||||||
|
|
||||||
// 4. Déduction automatique du format physique
|
|
||||||
let format = 'Support Physique';
|
|
||||||
if (rawTitle.match(/Blu-ray|Blu-Ray/i)) format = 'Blu-ray';
|
|
||||||
else if (rawTitle.match(/DVD/i)) format = 'DVD';
|
|
||||||
else if (rawTitle.match(/4K|UHD/i)) format = '4K UHD';
|
|
||||||
|
|
||||||
// 5. Extraction du Réalisateur / Casting principal
|
|
||||||
let director = '';
|
|
||||||
if (row['director']) {
|
|
||||||
director = row['director'];
|
|
||||||
} else if (row['first_name'] && row['last_name']) {
|
|
||||||
director = `${row['first_name']} ${row['last_name']}`;
|
|
||||||
} else if (row['creators']) {
|
|
||||||
// Prendre le premier nom de la liste des créateurs/acteurs
|
|
||||||
director = row['creators'].split(',')[0].trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Construction de l'objet final avec les NOMS DE CHAMPS ATTENDUS PAR LE BACKEND
|
|
||||||
return {
|
|
||||||
title: cleanTitle,
|
|
||||||
year: year,
|
|
||||||
director: director,
|
|
||||||
format: format,
|
|
||||||
length: row['length'] || '',
|
|
||||||
publisher: row['publisher'] || '',
|
|
||||||
aspect_ratio: row['aspect_ratio'] || '',
|
|
||||||
ean_isbn13: row['ean_isbn13'] || '',
|
|
||||||
number_of_discs: row['number_of_discs'] || 1,
|
|
||||||
description: row['description'] || ''
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtrer les lignes sans titre
|
|
||||||
finalData = finalData.filter(item => item.title !== '');
|
|
||||||
|
|
||||||
if (finalData.length === 0) {
|
|
||||||
alert('❌ Aucun titre valide trouvé dans le fichier.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dédoublonnage en mémoire
|
|
||||||
const mergedMap = new Map();
|
|
||||||
finalData.forEach(row => {
|
|
||||||
const key = `${row.title.toLowerCase()}|${row.year}`;
|
|
||||||
if (!mergedMap.has(key)) {
|
|
||||||
mergedMap.set(key, row);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
finalData = Array.from(mergedMap.values());
|
|
||||||
|
|
||||||
// Lancement de la modale de progression
|
|
||||||
showImportModal(finalData.length, currentAdminTab);
|
|
||||||
|
|
||||||
// Envoi par lots de 10
|
|
||||||
const batchSize = 10;
|
|
||||||
let processed = 0;
|
let processed = 0;
|
||||||
|
for (let i = 0; i < allData.length; i += 10) {
|
||||||
for (let i = 0; i < finalData.length; i += batchSize) {
|
const batch = allData.slice(i, i + 10);
|
||||||
const batch = finalData.slice(i, i + batchSize);
|
|
||||||
try {
|
try {
|
||||||
await fetch(`${API_URL}?action=import_batch`, {
|
await fetch(`${API_URL}?action=import_batch`, {
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': localStorage.getItem('token'),
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ items: batch, type: currentAdminTab })
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Erreur lot import:', err);
|
|
||||||
}
|
|
||||||
processed += batch.length;
|
|
||||||
updateImportModal(processed, finalData.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalisation
|
|
||||||
closeImportModal();
|
|
||||||
alert(`✅ Import terminé ! ${finalData.length} élément(s) traité(s).`);
|
|
||||||
loadDashboardData();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveConfigKeys() {
|
|
||||||
const keys = { 'tmdb_api_key': document.getElementById('tmdb-key-input')?.value || '' };
|
|
||||||
let successCount = 0;
|
|
||||||
for (const [keyName, keyValue] of Object.entries(keys)) {
|
|
||||||
if (keyValue) {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}?action=save_config`, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ key_name: keyName, key_value: keyValue })
|
body: JSON.stringify({ items: batch, type: 'critique' }) // 🔥 Envoi strict vers Critiques
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
|
||||||
if (data.success) successCount++;
|
|
||||||
} catch (err) { console.error(err); }
|
} catch (err) { console.error(err); }
|
||||||
|
processed += batch.length;
|
||||||
|
updateImportModal(processed, allData.length);
|
||||||
}
|
}
|
||||||
}
|
closeImportModal();
|
||||||
if (successCount > 0) {
|
alert(`✅ ${allData.length} critique(s) importée(s).`);
|
||||||
alert('✅ Clés API sauvegardées !');
|
|
||||||
closeConfigModal();
|
|
||||||
} else { alert('Aucune nouvelle clé valide à sauvegarder.'); }
|
|
||||||
}
|
|
||||||
|
|
||||||
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";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pwdInput.value.length < 4) {
|
|
||||||
errorMsg.textContent = "Le mot de passe doit contenir au moins 4 caractères.";
|
|
||||||
errorMsg.style.display = "block";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_URL}?action=update_password`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ new_password: pwdInput.value })
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.success) {
|
|
||||||
pwdInput.value = '';
|
|
||||||
pwdConfirm.value = '';
|
|
||||||
errorMsg.style.display = "none";
|
|
||||||
closePasswordModal();
|
|
||||||
alert('Mot de passe mis à jour.');
|
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
}
|
}
|
||||||
} catch (err) { console.error('Erreur mise à jour mot de passe :', err); }
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// 🔥 FONCTION 2 : IMPORT VIDÉOTHÈQUE (Physique / EAN)
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
async function handleVideothequeUpload(input) {
|
||||||
|
if (!input.files || input.files.length === 0) return;
|
||||||
|
let allData = [];
|
||||||
|
for (const file of input.files) {
|
||||||
|
try { allData = allData.concat(parseCSV(await file.text())); } catch(e) {}
|
||||||
|
}
|
||||||
|
input.value = '';
|
||||||
|
if (allData.length === 0) return alert('❌ Fichier vide.');
|
||||||
|
|
||||||
|
showImportModal(allData.length, 'videotheque');
|
||||||
|
let processed = 0;
|
||||||
|
for (let i = 0; i < allData.length; i += 10) {
|
||||||
|
const batch = allData.slice(i, i + 10);
|
||||||
|
try {
|
||||||
|
await fetch(`${API_URL}?action=import_batch`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': localStorage.getItem('token'), 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ items: batch, type: 'videotheque' }) // 🔥 Envoi strict vers Vidéothèque
|
||||||
|
});
|
||||||
|
} catch (err) { console.error(err); }
|
||||||
|
processed += batch.length;
|
||||||
|
updateImportModal(processed, allData.length);
|
||||||
|
}
|
||||||
|
closeImportModal();
|
||||||
|
alert(`✅ ${allData.length} support(s) physique(s) importé(s).`);
|
||||||
|
loadDashboardData();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- IMPORT PROGRESSIVE DYNAMIQUE ---
|
|
||||||
function showImportModal(total, type) {
|
function showImportModal(total, type) {
|
||||||
const modal = document.getElementById('import-progress-modal');
|
const modal = document.getElementById('import-progress-modal');
|
||||||
const title = document.getElementById('import-modal-title');
|
document.getElementById('import-modal-title').innerHTML = type === 'critique' ? '<i class="ti ti-star"></i> Import des Critiques' : '<i class="ti ti-video"></i> Import Vidéothèque';
|
||||||
const desc = document.getElementById('import-modal-desc');
|
document.getElementById('import-modal-desc').textContent = `Traitement de ${total} élément(s)...`;
|
||||||
title.innerHTML = type === 'critique'
|
|
||||||
? '<i class="ti ti-star"></i> Import des Critiques'
|
|
||||||
: '<i class="ti ti-video"></i> Import Vidéothèque';
|
|
||||||
desc.textContent = `Traitement de ${total} élément(s)...`;
|
|
||||||
document.getElementById('import-progress-bar').style.width = '0%';
|
document.getElementById('import-progress-bar').style.width = '0%';
|
||||||
document.getElementById('import-modal-counter').textContent = '0%';
|
document.getElementById('import-modal-counter').textContent = '0%';
|
||||||
modal.classList.add('open');
|
modal.classList.add('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImportModal(current, total) {
|
function updateImportModal(current, total) {
|
||||||
const pct = Math.round((current / total) * 100);
|
const pct = Math.round((current / total) * 100);
|
||||||
document.getElementById('import-progress-bar').style.width = pct + '%';
|
document.getElementById('import-progress-bar').style.width = pct + '%';
|
||||||
document.getElementById('import-modal-counter').textContent = `${pct}%`;
|
document.getElementById('import-modal-counter').textContent = `${pct}%`;
|
||||||
}
|
}
|
||||||
|
function closeImportModal() { document.getElementById('import-progress-modal').classList.remove('open'); }
|
||||||
function closeImportModal() {
|
|
||||||
document.getElementById('import-progress-modal').classList.remove('open');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchInfoFromEAN() {
|
|
||||||
const eanInput = document.getElementById('f-ean');
|
|
||||||
const ean = eanInput.value.trim();
|
|
||||||
if (!ean || ean.length < 8) { alert("Veuillez saisir un code EAN valide."); return; }
|
|
||||||
const btn = eanInput.nextElementSibling;
|
|
||||||
const originalHtml = btn.innerHTML;
|
|
||||||
btn.innerHTML = '<i class="ti ti-loader"></i>';
|
|
||||||
btn.disabled = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${API_URL}?action=search_ean_full&ean=${ean}`, {
|
|
||||||
headers: { 'Authorization': localStorage.getItem('token') }
|
|
||||||
});
|
|
||||||
const json = await res.json();
|
|
||||||
if (json.success && json.data) {
|
|
||||||
const d = json.data;
|
|
||||||
if (d.title) document.getElementById('f-title').value = d.title;
|
|
||||||
if (d.director) document.getElementById('f-director').value = d.director;
|
|
||||||
if (d.year) document.getElementById('f-year').value = d.year;
|
|
||||||
if (d.poster) document.getElementById('f-poster').value = d.poster;
|
|
||||||
if (d.publisher) document.getElementById('f-publisher').value = d.publisher;
|
|
||||||
if (d.format) document.getElementById('f-format').value = d.format;
|
|
||||||
if (d.length) document.getElementById('f-length').value = d.length;
|
|
||||||
if (d.number_of_discs) document.getElementById('f-discs').value = d.number_of_discs;
|
|
||||||
if (d.aspect_ratio) document.getElementById('f-aspect').value = d.aspect_ratio;
|
|
||||||
} else { alert("Aucune information trouvée pour ce code EAN."); }
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Erreur de recherche EAN: ", e);
|
|
||||||
alert("Erreur réseau lors de la recherche du code EAN.");
|
|
||||||
} finally {
|
|
||||||
btn.innerHTML = originalHtml;
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 === 'critique') {
|
if (currentAdminTab === 'critique') {
|
||||||
title.innerHTML = '<strong>Importer Critiques & Notes</strong>';
|
title.innerHTML = '<strong>Importer Critiques & Notes</strong>';
|
||||||
desc.textContent = 'Glissez-déposez vos fichiers "ratings.csv" et "reviews.csv" (Letterboxd).';
|
desc.textContent = 'Sélectionnez vos fichiers "ratings.csv" et "reviews.csv" (Letterboxd).';
|
||||||
} else {
|
} else {
|
||||||
title.innerHTML = '<strong>Importer ma Vidéothèque</strong>';
|
title.innerHTML = '<strong>Importer ma Vidéothèque</strong>';
|
||||||
desc.textContent = 'Glissez-déposez vos listes CSV de supports physiques (Blu-ray, DVD, 4K).';
|
desc.textContent = 'Sélectionnez vos listes CSV de supports physiques (Blu-ray, DVD, 4K).';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function logout() { localStorage.removeItem('token'); window.location.href = 'login.html'; }
|
||||||
Reference in New Issue
Block a user