Actualiser js/admin.js
This commit is contained in:
+8
-35
@@ -256,7 +256,7 @@ if (editingFilmId !== null) {
|
|||||||
poster: document.getElementById('f-poster').value.trim(),
|
poster: document.getElementById('f-poster').value.trim(),
|
||||||
rating: type === 'critique' ? (currentRating || 1) : 0,
|
rating: type === 'critique' ? (currentRating || 1) : 0,
|
||||||
review: type === 'critique' ? document.getElementById('f-review').value.trim() : '',
|
review: type === 'critique' ? document.getElementById('f-review').value.trim() : '',
|
||||||
streaming: films[index].streaming || "Disponible au cinéma ou support physique", // Conserve ou ajoute par défaut
|
streaming: films[index].streaming || "Disponible au cinéma ou support physique",
|
||||||
...metadataVideotheque
|
...metadataVideotheque
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -270,7 +270,7 @@ if (editingFilmId !== null) {
|
|||||||
poster: document.getElementById('f-poster').value.trim(),
|
poster: document.getElementById('f-poster').value.trim(),
|
||||||
rating: type === 'critique' ? (currentRating || 1) : 0,
|
rating: type === 'critique' ? (currentRating || 1) : 0,
|
||||||
review: type === 'critique' ? document.getElementById('f-review').value.trim() : '',
|
review: type === 'critique' ? document.getElementById('f-review').value.trim() : '',
|
||||||
streaming: "Disponible au cinéma ou support physique", // Valeur par défaut à la création
|
streaming: "Disponible au cinéma ou support physique",
|
||||||
...metadataVideotheque
|
...metadataVideotheque
|
||||||
};
|
};
|
||||||
films.unshift(film);
|
films.unshift(film);
|
||||||
@@ -295,7 +295,6 @@ function renderAdminTable() {
|
|||||||
|
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
|
|
||||||
// Reset select-all + bulk-delete button BEFORE rebuilding DOM
|
|
||||||
const selectAll = document.getElementById('th-select-all');
|
const selectAll = document.getElementById('th-select-all');
|
||||||
if (selectAll) selectAll.checked = false;
|
if (selectAll) selectAll.checked = false;
|
||||||
const btnBulkReset = document.getElementById('btn-bulk-delete');
|
const btnBulkReset = document.getElementById('btn-bulk-delete');
|
||||||
@@ -321,7 +320,6 @@ function renderAdminTable() {
|
|||||||
|
|
||||||
if (emptyState) emptyState.style.display = 'none';
|
if (emptyState) emptyState.style.display = 'none';
|
||||||
|
|
||||||
// Update film count display
|
|
||||||
const countEl = document.getElementById('films-count');
|
const countEl = document.getElementById('films-count');
|
||||||
if (countEl) countEl.textContent = filteredFilms.length;
|
if (countEl) countEl.textContent = filteredFilms.length;
|
||||||
|
|
||||||
@@ -395,7 +393,6 @@ function deleteSelectedFilms() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Letterboxd CSV parser (handles quoted commas correctly) ──────────────
|
|
||||||
function parseLetterboxdCSV(text) {
|
function parseLetterboxdCSV(text) {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const lines = text.split(/\r\n|\n/);
|
const lines = text.split(/\r\n|\n/);
|
||||||
@@ -420,7 +417,6 @@ function parseLetterboxdCSV(text) {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Show import progress overlay ─────────────────────────────────────────
|
|
||||||
function showImportProgress(total) {
|
function showImportProgress(total) {
|
||||||
let overlay = document.getElementById('import-progress-overlay');
|
let overlay = document.getElementById('import-progress-overlay');
|
||||||
if (!overlay) {
|
if (!overlay) {
|
||||||
@@ -464,7 +460,6 @@ function hideImportProgress() {
|
|||||||
if (el) el.remove();
|
if (el) el.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── TMDB poster lookup (single film) ─────────────────────────────────────
|
|
||||||
async function fetchTmdbPoster(title, year) {
|
async function fetchTmdbPoster(title, year) {
|
||||||
const key = localStorage.getItem('tmdb-api-key');
|
const key = localStorage.getItem('tmdb-api-key');
|
||||||
if (!key) return '';
|
if (!key) return '';
|
||||||
@@ -477,7 +472,6 @@ async function fetchTmdbPoster(title, year) {
|
|||||||
if (data.results && data.results.length > 0 && data.results[0].poster_path) {
|
if (data.results && data.results.length > 0 && data.results[0].poster_path) {
|
||||||
return `https://image.tmdb.org/t/p/w500${data.results[0].poster_path}`;
|
return `https://image.tmdb.org/t/p/w500${data.results[0].poster_path}`;
|
||||||
}
|
}
|
||||||
// Retry without year if no result
|
|
||||||
if (year) {
|
if (year) {
|
||||||
const res2 = await fetch(
|
const res2 = await fetch(
|
||||||
`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR`
|
`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR`
|
||||||
@@ -491,9 +485,7 @@ async function fetchTmdbPoster(title, year) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── DVD/Blu-ray cover: Open Library (EAN) → TMDB fallback ────────────────
|
|
||||||
async function fetchDvdCover(ean, title, year) {
|
async function fetchDvdCover(ean, title, year) {
|
||||||
// Step 1 — Open Library ISBN/EAN lookup (no key needed)
|
|
||||||
if (ean) {
|
if (ean) {
|
||||||
try {
|
try {
|
||||||
const olRes = await fetch(
|
const olRes = await fetch(
|
||||||
@@ -503,7 +495,6 @@ async function fetchDvdCover(ean, title, year) {
|
|||||||
const key = `ISBN:${ean}`;
|
const key = `ISBN:${ean}`;
|
||||||
if (olData[key]) {
|
if (olData[key]) {
|
||||||
const book = olData[key];
|
const book = olData[key];
|
||||||
// Prefer large cover, fall back to medium/small
|
|
||||||
if (book.cover) {
|
if (book.cover) {
|
||||||
const coverUrl = book.cover.large || book.cover.medium || book.cover.small || '';
|
const coverUrl = book.cover.large || book.cover.medium || book.cover.small || '';
|
||||||
if (coverUrl) return coverUrl;
|
if (coverUrl) return coverUrl;
|
||||||
@@ -511,7 +502,6 @@ async function fetchDvdCover(ean, title, year) {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
// Step 1b — Open Library cover API directly by ISBN (faster path)
|
|
||||||
try {
|
try {
|
||||||
const testUrl = `https://covers.openlibrary.org/b/isbn/${ean}-L.jpg?default=false`;
|
const testUrl = `https://covers.openlibrary.org/b/isbn/${ean}-L.jpg?default=false`;
|
||||||
const testRes = await fetch(testUrl);
|
const testRes = await fetch(testUrl);
|
||||||
@@ -520,12 +510,9 @@ async function fetchDvdCover(ean, title, year) {
|
|||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2 — TMDB fallback (uses existing key)
|
|
||||||
return await fetchTmdbPoster(title, year);
|
return await fetchTmdbPoster(title, year);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Main Letterboxd import (reviews.csv OR ratings.csv) ──────────────────
|
|
||||||
function importFromCSV(input) {
|
function importFromCSV(input) {
|
||||||
const file = input.files[0];
|
const file = input.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
@@ -542,10 +529,8 @@ function importFromCSV(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const headers = rows[0].map(h => h.trim().toLowerCase());
|
const headers = rows[0].map(h => h.trim().toLowerCase());
|
||||||
|
|
||||||
// Detect file type: reviews.csv has a "Review" column, ratings.csv doesn't
|
|
||||||
const isReviews = headers.includes('review');
|
const isReviews = headers.includes('review');
|
||||||
const iCol = { // column indices by header name
|
const iCol = {
|
||||||
name: headers.indexOf('name'),
|
name: headers.indexOf('name'),
|
||||||
year: headers.indexOf('year'),
|
year: headers.indexOf('year'),
|
||||||
rating: headers.indexOf('rating'),
|
rating: headers.indexOf('rating'),
|
||||||
@@ -553,7 +538,6 @@ function importFromCSV(input) {
|
|||||||
watchedDate: headers.indexOf('watched date'),
|
watchedDate: headers.indexOf('watched date'),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect rows to import (skip duplicates first pass)
|
|
||||||
const toImport = [];
|
const toImport = [];
|
||||||
let duplicateCount = 0;
|
let duplicateCount = 0;
|
||||||
|
|
||||||
@@ -564,7 +548,6 @@ function importFromCSV(input) {
|
|||||||
|
|
||||||
const year = iCol.year >= 0 ? (cols[iCol.year]?.trim() || '') : '';
|
const year = iCol.year >= 0 ? (cols[iCol.year]?.trim() || '') : '';
|
||||||
const ratingRaw = iCol.rating >= 0 ? parseFloat(cols[iCol.rating]) : 0;
|
const ratingRaw = iCol.rating >= 0 ? parseFloat(cols[iCol.rating]) : 0;
|
||||||
// Letterboxd uses 0.5–5 scale; convert to our 1–5 integer scale
|
|
||||||
const rating = Math.round(Math.min(5, Math.max(1, ratingRaw)));
|
const rating = Math.round(Math.min(5, Math.max(1, ratingRaw)));
|
||||||
const review = (isReviews && iCol.review >= 0) ? (cols[iCol.review]?.trim() || '') : '';
|
const review = (isReviews && iCol.review >= 0) ? (cols[iCol.review]?.trim() || '') : '';
|
||||||
|
|
||||||
@@ -594,11 +577,10 @@ function importFromCSV(input) {
|
|||||||
let streaming = "Disponible au cinéma ou support physique";
|
let streaming = "Disponible au cinéma ou support physique";
|
||||||
|
|
||||||
if (hasTmdb) {
|
if (hasTmdb) {
|
||||||
// On récupère les deux infos en un seul traitement
|
// Double appel unifié (Affiche + Streaming)
|
||||||
const details = await fetchTmdbDetails(title, year);
|
const details = await fetchTmdbDetails(title, year);
|
||||||
poster = details.poster;
|
poster = details.poster;
|
||||||
streaming = details.streaming;
|
streaming = details.streaming;
|
||||||
// Augmentation légère du délai pour respecter les limites de l'API avec le double appel
|
|
||||||
await new Promise(r => setTimeout(r, 350));
|
await new Promise(r => setTimeout(r, 350));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,8 +593,9 @@ function importFromCSV(input) {
|
|||||||
rating,
|
rating,
|
||||||
review,
|
review,
|
||||||
poster,
|
poster,
|
||||||
streaming, // Ajout du nouveau champ
|
streaming,
|
||||||
format: '', length: '', publisher: '', ean_isbn13: '', number_of_discs: '1', aspect_ratio: '', description: ''
|
format: '',
|
||||||
|
length: '', publisher: '', ean_isbn13: '', number_of_discs: '1', aspect_ratio: '', description: ''
|
||||||
});
|
});
|
||||||
importedCount++;
|
importedCount++;
|
||||||
}
|
}
|
||||||
@@ -626,7 +609,7 @@ function importFromCSV(input) {
|
|||||||
renderAdminTable();
|
renderAdminTable();
|
||||||
|
|
||||||
const typeLabel = isReviews ? 'reviews.csv (avec critiques)' : 'ratings.csv (notes seules)';
|
const typeLabel = isReviews ? 'reviews.csv (avec critiques)' : 'ratings.csv (notes seules)';
|
||||||
const posterInfo = hasTmdb ? ' · Affiches récupérées via TMDB' : ' · ⚠️ Clé TMDB absente, affiches non récupérées';
|
const posterInfo = hasTmdb ? ' · Affiches et plateformes récupérées via TMDB' : ' · ⚠️ Clé TMDB absente';
|
||||||
alert(
|
alert(
|
||||||
`Import Letterboxd — ${typeLabel}\n` +
|
`Import Letterboxd — ${typeLabel}\n` +
|
||||||
`✅ ${importedCount} film(s) importé(s)${posterInfo}\n` +
|
`✅ ${importedCount} film(s) importé(s)${posterInfo}\n` +
|
||||||
@@ -670,7 +653,6 @@ function importVideothequeFromInput(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function importVideothequeCSV(text) {
|
async function importVideothequeCSV(text) {
|
||||||
// ── Parse CSV (handles quoted commas) ──
|
|
||||||
const lines = [];
|
const lines = [];
|
||||||
let row = [""];
|
let row = [""];
|
||||||
let inQuotes = false;
|
let inQuotes = false;
|
||||||
@@ -691,7 +673,6 @@ async function importVideothequeCSV(text) {
|
|||||||
if (row.length > 1 || row[0] !== '') lines.push(row);
|
if (row.length > 1 || row[0] !== '') lines.push(row);
|
||||||
if (lines.length <= 1) return;
|
if (lines.length <= 1) return;
|
||||||
|
|
||||||
// ── Collect rows to import ──
|
|
||||||
const toImport = [];
|
const toImport = [];
|
||||||
let duplicateCount = 0;
|
let duplicateCount = 0;
|
||||||
|
|
||||||
@@ -734,7 +715,6 @@ async function importVideothequeCSV(text) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Progress bar ──
|
|
||||||
showImportProgress(toImport.length);
|
showImportProgress(toImport.length);
|
||||||
let importedCount = 0;
|
let importedCount = 0;
|
||||||
let coversFound = 0;
|
let coversFound = 0;
|
||||||
@@ -745,14 +725,12 @@ async function importVideothequeCSV(text) {
|
|||||||
const sourceLabel = ean ? `EAN ${ean}` : title;
|
const sourceLabel = ean ? `EAN ${ean}` : title;
|
||||||
updateImportProgress(i, toImport.length, `📀 ${sourceLabel}`);
|
updateImportProgress(i, toImport.length, `📀 ${sourceLabel}`);
|
||||||
|
|
||||||
// ── Fetch cover: Open Library (EAN) → TMDB ──
|
|
||||||
let poster = '';
|
let poster = '';
|
||||||
try {
|
try {
|
||||||
poster = await fetchDvdCover(ean, title, year);
|
poster = await fetchDvdCover(ean, title, year);
|
||||||
if (poster) coversFound++;
|
if (poster) coversFound++;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// Respectful delay
|
|
||||||
await new Promise(r => setTimeout(r, 300));
|
await new Promise(r => setTimeout(r, 300));
|
||||||
|
|
||||||
films.push({
|
films.push({
|
||||||
@@ -824,7 +802,6 @@ async function fetchTmdbDetails(title, year) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const yearParam = year ? `&year=${year}` : '';
|
const yearParam = year ? `&year=${year}` : '';
|
||||||
// Step 1 : Recherche du film pour obtenir son ID TMDB
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR${yearParam}`
|
`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${encodeURIComponent(title)}&language=fr-FR${yearParam}`
|
||||||
);
|
);
|
||||||
@@ -836,7 +813,6 @@ async function fetchTmdbDetails(title, year) {
|
|||||||
const posterPath = movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : '';
|
const posterPath = movie.poster_path ? `https://image.tmdb.org/t/p/w500${movie.poster_path}` : '';
|
||||||
let streamingInfo = streamingDefault;
|
let streamingInfo = streamingDefault;
|
||||||
|
|
||||||
// Step 2 : Requête sur l'endpoint "watch/providers" pour avoir le streaming en France (FR)
|
|
||||||
try {
|
try {
|
||||||
const watchRes = await fetch(
|
const watchRes = await fetch(
|
||||||
`https://api.themoviedb.org/3/movie/${movieId}/watch/providers?api_key=${key}`
|
`https://api.themoviedb.org/3/movie/${movieId}/watch/providers?api_key=${key}`
|
||||||
@@ -845,13 +821,10 @@ async function fetchTmdbDetails(title, year) {
|
|||||||
|
|
||||||
if (watchData.results && watchData.results.FR) {
|
if (watchData.results && watchData.results.FR) {
|
||||||
const frProviders = watchData.results.FR;
|
const frProviders = watchData.results.FR;
|
||||||
// On privilégie les plateformes incluses dans un abonnement (flatrate), sinon à la location/achat
|
|
||||||
const providers = frProviders.flatrate || frProviders.buy || frProviders.rent || [];
|
const providers = frProviders.flatrate || frProviders.buy || frProviders.rent || [];
|
||||||
|
|
||||||
if (providers.length > 0) {
|
if (providers.length > 0) {
|
||||||
// Extrait les noms des plateformes trouvées (ex: Netflix, Canal+, Disney Plus)
|
|
||||||
const names = providers.map(p => p.provider_name);
|
const names = providers.map(p => p.provider_name);
|
||||||
// On supprime les doublons potentiels et on limite aux 3 premières pour l'affichage
|
|
||||||
const uniqueNames = [...new Set(names)].slice(0, 3);
|
const uniqueNames = [...new Set(names)].slice(0, 3);
|
||||||
streamingInfo = `Disponible sur : ${uniqueNames.join(', ')}`;
|
streamingInfo = `Disponible sur : ${uniqueNames.join(', ')}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user