Actualiser js/admin.js
This commit is contained in:
+134
-72
@@ -17,6 +17,7 @@ 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 = [];
|
||||||
@@ -34,21 +35,23 @@ function parseCSV(text) {
|
|||||||
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 = '';
|
||||||
if (row.length > 1 || row[0] !== '') rows.push(row);
|
if (row.length > 1 || row[0].trim() !== '') rows.push(row);
|
||||||
row = [];
|
row = [];
|
||||||
} else col += c;
|
} else col += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (col !== '' || row.length > 0) { row.push(col); rows.push(row); }
|
if (col !== '' || row.length > 0) { row.push(col); rows.push(row); }
|
||||||
if (rows.length === 0) return [];
|
if (rows.length === 0) return [];
|
||||||
|
|
||||||
const headers = rows[0].map(h => h.trim());
|
const headers = rows[0].map(h => h.trim());
|
||||||
const data = [];
|
const data = [];
|
||||||
for (let i = 1; i < rows.length; i++) {
|
for (let i = 1; i < rows.length; i++) {
|
||||||
if (rows[i].length === headers.length) {
|
if (rows[i].length === 1 && rows[i][0].trim() === '') continue; // Ignore les lignes 100% vides
|
||||||
const obj = {};
|
const obj = {};
|
||||||
headers.forEach((h, idx) => obj[h] = rows[i][idx]);
|
headers.forEach((h, idx) => {
|
||||||
data.push(obj);
|
obj[h] = rows[i][idx] !== undefined ? rows[i][idx] : '';
|
||||||
}
|
});
|
||||||
|
data.push(obj);
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -127,7 +130,6 @@ function renderAdminTable() {
|
|||||||
|
|
||||||
let filtered = allItems.filter(item => item.type === currentAdminTab);
|
let filtered = allItems.filter(item => item.type === currentAdminTab);
|
||||||
|
|
||||||
// Nouveau : Filtrage des formats dématérialisés
|
|
||||||
if (physicalFilter && physicalFilter.checked) {
|
if (physicalFilter && physicalFilter.checked) {
|
||||||
filtered = filtered.filter(f => f.format && !['dématérialisé', 'vod', 'digital', 'streaming'].includes(f.format.toLowerCase()));
|
filtered = filtered.filter(f => f.format && !['dématérialisé', 'vod', 'digital', 'streaming'].includes(f.format.toLowerCase()));
|
||||||
}
|
}
|
||||||
@@ -390,14 +392,9 @@ function closeAdminModal() { document.getElementById('admin-modal').classList.re
|
|||||||
|
|
||||||
async function openConfigModal() {
|
async function openConfigModal() {
|
||||||
document.getElementById('config-modal').classList.add('open');
|
document.getElementById('config-modal').classList.add('open');
|
||||||
|
|
||||||
// Réinitialise les placeholders
|
|
||||||
document.getElementById('tmdb-key-input').placeholder = 'Pour les critiques (réalisateur, streaming)';
|
document.getElementById('tmdb-key-input').placeholder = 'Pour les critiques (réalisateur, streaming)';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}?action=get_config_keys`, {
|
const res = await fetch(`${API_URL}?action=get_config_keys`, { headers: { 'Authorization': localStorage.getItem('token') } });
|
||||||
headers: { 'Authorization': localStorage.getItem('token') }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
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)';
|
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); }
|
} catch(e) { console.error(e); }
|
||||||
@@ -448,54 +445,134 @@ async function saveFilmForm(e) {
|
|||||||
} catch (err) { console.error('Erreur sauvegarde :', err); }
|
} catch (err) { console.error('Erreur sauvegarde :', err); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── IMPORT CSV : INTELLIGENCE ET FUSION ──
|
||||||
async function handleCsvUpload(input) {
|
async function handleCsvUpload(input) {
|
||||||
if (!input.files || input.files.length === 0) return;
|
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;
|
|
||||||
}
|
|
||||||
closeConfigModal();
|
|
||||||
showProgressModal(allData.length);
|
|
||||||
|
|
||||||
const batchSize = 5;
|
closeConfigModal();
|
||||||
let processed = 0;
|
let allData = [];
|
||||||
for (let i = 0; i < allData.length; i += batchSize) {
|
|
||||||
const batch = allData.slice(i, i + batchSize);
|
// 1. Lecture de TOUS les fichiers sélectionnés
|
||||||
try {
|
for (const file of input.files) {
|
||||||
await fetch(`${API_URL}?action=import_batch`, {
|
try {
|
||||||
method: 'POST',
|
const text = await file.text();
|
||||||
headers: {
|
const data = parseCSV(text);
|
||||||
'Authorization': localStorage.getItem('token'),
|
allData = allData.concat(data);
|
||||||
'Content-Type': 'application/json'
|
} catch(e) { console.error('Erreur fichier', e); }
|
||||||
},
|
|
||||||
body: JSON.stringify({ items: batch, type: currentAdminTab })
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Erreur sur un lot:', err);
|
|
||||||
}
|
|
||||||
processed += batch.length;
|
|
||||||
updateProgressModal(processed, allData.length);
|
|
||||||
}
|
|
||||||
closeProgressModal();
|
|
||||||
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.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.value = ''; // Reset de l'input
|
||||||
|
|
||||||
|
if (allData.length === 0) {
|
||||||
|
alert('❌ Fichiers vides ou mal formatés.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Dédoublonnage et fusion intelligente des CSV en mémoire
|
||||||
|
const mergedMap = new Map();
|
||||||
|
allData.forEach(row => {
|
||||||
|
const title = row['title'] || row['Name'] || 'Sans titre';
|
||||||
|
const dateStr = row['publish_date'] || row['Year'] || row['year'] || '';
|
||||||
|
const m = String(dateStr).match(/(\d{4})/);
|
||||||
|
const year = m ? m[1] : '';
|
||||||
|
|
||||||
|
const key = `${title.toLowerCase().trim()}|${year}`;
|
||||||
|
|
||||||
|
if (!mergedMap.has(key)) {
|
||||||
|
mergedMap.set(key, { ...row });
|
||||||
|
} else {
|
||||||
|
const existing = mergedMap.get(key);
|
||||||
|
// Si la ligne actuelle a une note/critique et que l'existante n'en a pas, on complète
|
||||||
|
if ((row['Rating'] || row['rating']) && !(existing['Rating'] || existing['rating'])) {
|
||||||
|
existing['Rating'] = row['Rating'] || row['rating'];
|
||||||
|
}
|
||||||
|
if ((row['Review'] || row['review']) && !(existing['Review'] || existing['review'])) {
|
||||||
|
existing['Review'] = row['Review'] || row['review'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const finalData = Array.from(mergedMap.values());
|
||||||
|
|
||||||
|
// 3. Protection de la Base de Données (Ne pas écraser l'existant)
|
||||||
|
finalData.forEach(row => {
|
||||||
|
const title = row['title'] || row['Name'] || 'Sans titre';
|
||||||
|
const dateStr = row['publish_date'] || row['Year'] || row['year'] || '';
|
||||||
|
const m = String(dateStr).match(/(\d{4})/);
|
||||||
|
const year = m ? m[1] : '';
|
||||||
|
|
||||||
|
// Cherche le film dans le dashboard déjà chargé
|
||||||
|
const existingDb = allItems.find(f =>
|
||||||
|
f.title.toLowerCase().trim() === title.toLowerCase().trim() &&
|
||||||
|
String(f.year) === String(year) &&
|
||||||
|
f.type === currentAdminTab
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingDb) {
|
||||||
|
const csvRating = row['Rating'] || row['rating'];
|
||||||
|
const csvReview = row['Review'] || row['review'];
|
||||||
|
|
||||||
|
// Récupère l'ancienne note si le fichier CSV n'en contient pas
|
||||||
|
if (!csvRating && existingDb.rating) {
|
||||||
|
row['Rating'] = existingDb.rating;
|
||||||
|
}
|
||||||
|
// Récupère l'ancienne critique si le fichier CSV n'en contient pas
|
||||||
|
if (!csvReview && existingDb.review) {
|
||||||
|
row['Review'] = existingDb.review;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showProgressModal(finalData.length);
|
||||||
|
|
||||||
|
// 4. Envoi par lots de 10
|
||||||
|
const batchSize = 10;
|
||||||
|
let processed = 0;
|
||||||
|
let totalStats = { upc_hits: 0, tmdb_hits: 0, no_image: 0 };
|
||||||
|
|
||||||
|
for (let i = 0; i < finalData.length; i += batchSize) {
|
||||||
|
const batch = finalData.slice(i, i + batchSize);
|
||||||
|
try {
|
||||||
|
const response = 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 })
|
||||||
|
});
|
||||||
|
const resData = await response.json();
|
||||||
|
if (resData.stats) {
|
||||||
|
totalStats.upc_hits += resData.stats.upc_hits || 0;
|
||||||
|
totalStats.tmdb_hits += resData.stats.tmdb_hits || 0;
|
||||||
|
totalStats.no_image += resData.stats.no_image || 0;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erreur sur un lot:', err);
|
||||||
|
}
|
||||||
|
processed += batch.length;
|
||||||
|
updateProgressModal(processed, finalData.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeProgressModal();
|
||||||
|
|
||||||
|
// 5. Affichage du bilan
|
||||||
|
let statsMessage = `✅ Import terminé avec succès !\n\n🎬 Films traités : ${finalData.length}\n`;
|
||||||
|
if (totalStats.upc_hits > 0 || totalStats.tmdb_hits > 0 || totalStats.no_image > 0) {
|
||||||
|
statsMessage += `\n📊 Bilan des recherches d'images :\n`;
|
||||||
|
if (currentAdminTab === 'videotheque') {
|
||||||
|
statsMessage += `• 📦 Jaquettes via Code EAN (UPC) : ${totalStats.upc_hits}\n`;
|
||||||
|
}
|
||||||
|
statsMessage += `• 🍿 Affiches via le titre (TMDB) : ${totalStats.tmdb_hits}\n`;
|
||||||
|
statsMessage += `• ❌ Aucune image trouvée : ${totalStats.no_image}\n`;
|
||||||
|
}
|
||||||
|
alert(statsMessage);
|
||||||
|
|
||||||
|
loadDashboardData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveConfigKeys() {
|
async function saveConfigKeys() {
|
||||||
const keys = {
|
const keys = { 'tmdb_api_key': document.getElementById('tmdb-key-input')?.value || '' };
|
||||||
'tmdb_api_key': document.getElementById('tmdb-key-input')?.value || '',
|
|
||||||
};
|
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
for (const [keyName, keyValue] of Object.entries(keys)) {
|
for (const [keyName, keyValue] of Object.entries(keys)) {
|
||||||
if (keyValue) {
|
if (keyValue) {
|
||||||
@@ -510,13 +587,10 @@ async function saveConfigKeys() {
|
|||||||
} catch (err) { console.error(err); }
|
} catch (err) { console.error(err); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (successCount > 0) {
|
if (successCount > 0) {
|
||||||
alert('✅ Clés API sauvegardées !');
|
alert('✅ Clés API sauvegardées !');
|
||||||
closeConfigModal();
|
closeConfigModal();
|
||||||
} else {
|
} else { alert('Aucune nouvelle clé valide à sauvegarder.'); }
|
||||||
alert('Aucune nouvelle clé valide à sauvegarder.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveNewPassword() {
|
async function saveNewPassword() {
|
||||||
@@ -569,17 +643,11 @@ function closeProgressModal() {
|
|||||||
document.getElementById('progress-overlay').classList.remove('open');
|
document.getElementById('progress-overlay').classList.remove('open');
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NOUVELLE FONCTION RECHERCHE EAN (UPCitemDB + TMDB) ---
|
|
||||||
async function fetchInfoFromEAN() {
|
async function fetchInfoFromEAN() {
|
||||||
const eanInput = document.getElementById('f-ean');
|
const eanInput = document.getElementById('f-ean');
|
||||||
const ean = eanInput.value.trim();
|
const ean = eanInput.value.trim();
|
||||||
|
if (!ean || ean.length < 8) { alert("Veuillez saisir un code EAN valide."); return; }
|
||||||
|
|
||||||
if (!ean || ean.length < 8) {
|
|
||||||
alert("Veuillez saisir un code EAN valide.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Feedback visuel de chargement
|
|
||||||
const btn = eanInput.nextElementSibling;
|
const btn = eanInput.nextElementSibling;
|
||||||
const originalHtml = btn.innerHTML;
|
const originalHtml = btn.innerHTML;
|
||||||
btn.innerHTML = '<i class="ti ti-loader"></i>';
|
btn.innerHTML = '<i class="ti ti-loader"></i>';
|
||||||
@@ -590,10 +658,8 @@ async function fetchInfoFromEAN() {
|
|||||||
headers: { 'Authorization': localStorage.getItem('token') }
|
headers: { 'Authorization': localStorage.getItem('token') }
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
|
|
||||||
if (json.success && json.data) {
|
if (json.success && json.data) {
|
||||||
const d = json.data;
|
const d = json.data;
|
||||||
// Remplissage automatique des champs
|
|
||||||
if (d.title) document.getElementById('f-title').value = d.title;
|
if (d.title) document.getElementById('f-title').value = d.title;
|
||||||
if (d.director) document.getElementById('f-director').value = d.director;
|
if (d.director) document.getElementById('f-director').value = d.director;
|
||||||
if (d.year) document.getElementById('f-year').value = d.year;
|
if (d.year) document.getElementById('f-year').value = d.year;
|
||||||
@@ -603,15 +669,11 @@ async function fetchInfoFromEAN() {
|
|||||||
if (d.length) document.getElementById('f-length').value = d.length;
|
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.number_of_discs) document.getElementById('f-discs').value = d.number_of_discs;
|
||||||
if (d.aspect_ratio) document.getElementById('f-aspect').value = d.aspect_ratio;
|
if (d.aspect_ratio) document.getElementById('f-aspect').value = d.aspect_ratio;
|
||||||
|
} else { alert("Aucune information trouvée pour ce code EAN."); }
|
||||||
} else {
|
|
||||||
alert("Aucune information trouvée pour ce code EAN.");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Erreur de recherche EAN:", e);
|
console.error("Erreur de recherche EAN:", e);
|
||||||
alert("Erreur réseau lors de la recherche du code EAN.");
|
alert("Erreur réseau lors de la recherche du code EAN.");
|
||||||
} finally {
|
} finally {
|
||||||
// Restaure le bouton
|
|
||||||
btn.innerHTML = originalHtml;
|
btn.innerHTML = originalHtml;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user