diff --git a/app.js b/app.js index e3c3521..78dd731 100644 --- a/app.js +++ b/app.js @@ -61,6 +61,16 @@ const googleRestoreBtn = document.getElementById("googleRestoreBtn"); const quickSearchInput = document.getElementById("quickSearchInput"); const quickSearchResults = document.getElementById("quickSearchResults"); const loanedFilterBtn = document.getElementById("loanedFilterBtn"); +const v2Toolbar = document.getElementById("v2Toolbar"); +const v2SearchInput = document.getElementById("v2SearchInput"); +const v2ConsoleFilter = document.getElementById("v2ConsoleFilter"); +const v2GenreFilter = document.getElementById("v2GenreFilter"); +const v2SortSelect = document.getElementById("v2SortSelect"); +const v2StickyBar = document.getElementById("v2StickyBar"); +const v2StickyCount = document.getElementById("v2StickyCount"); +const v2ToggleFormBtn = document.getElementById("v2ToggleFormBtn"); +const v2QuickBackupBtn = document.getElementById("v2QuickBackupBtn"); +const toastContainer = document.getElementById("toastContainer"); const scannerStatus = document.getElementById("scannerStatus"); const scannerStartBtn = document.getElementById("scannerStartBtn"); const scannerStopBtn = document.getElementById("scannerStopBtn"); @@ -80,6 +90,12 @@ let scannerRunning = false; let scannerLoopId = null; let scannerLastCodeValue = ""; let scannerLastCodeAt = 0; +const uiV2Enabled = new URLSearchParams(window.location.search).get("ui") === "v2"; +let v2SearchTerm = ""; +let v2ConsoleValue = ""; +let v2GenreValue = ""; +let v2SortValue = "title_asc"; +let v2FormCollapsed = false; coverFileInput.addEventListener("change", async (event) => { const input = event.target; @@ -203,6 +219,48 @@ loanedFilterBtn.addEventListener("click", () => { renderGames(); }); +if (v2SearchInput) { + v2SearchInput.addEventListener("input", (event) => { + v2SearchTerm = event.target.value.trim(); + renderGames(); + }); +} + +if (v2ConsoleFilter) { + v2ConsoleFilter.addEventListener("change", (event) => { + v2ConsoleValue = event.target.value; + renderGames(); + }); +} + +if (v2GenreFilter) { + v2GenreFilter.addEventListener("change", (event) => { + v2GenreValue = event.target.value; + renderGames(); + }); +} + +if (v2SortSelect) { + v2SortSelect.addEventListener("change", (event) => { + v2SortValue = event.target.value || "title_asc"; + renderGames(); + }); +} + +if (v2ToggleFormBtn) { + v2ToggleFormBtn.addEventListener("click", () => { + v2FormCollapsed = !v2FormCollapsed; + gameForm.classList.toggle("hidden", v2FormCollapsed); + v2ToggleFormBtn.textContent = v2FormCollapsed ? "Afficher formulaire" : "Ajouter"; + }); +} + +if (v2QuickBackupBtn) { + v2QuickBackupBtn.addEventListener("click", () => { + backupBtn.click(); + }); +} + if (scannerStartBtn) { scannerStartBtn.addEventListener("click", async () => { await startScanner(); @@ -258,6 +316,7 @@ platformForm.addEventListener("submit", async (event) => { gameForm.addEventListener("submit", async (event) => { event.preventDefault(); + const wasEditing = Boolean(editingGameId); const title = titleInput.value.trim(); if (!title || !state.selectedConsole) { @@ -297,6 +356,7 @@ gameForm.addEventListener("submit", async (event) => { resetEditMode(); await refreshFromApi(state.selectedBrand, state.selectedConsole); + showToast(wasEditing ? "Jeu mis a jour." : "Jeu ajoute.", "success"); return; } catch (error) { console.error(error); @@ -350,6 +410,7 @@ gameForm.addEventListener("submit", async (event) => { persist(); markLocalDataForImport(); render(); + showToast(wasEditing ? "Jeu mis a jour." : "Jeu ajoute.", "success"); }); brandTabs.addEventListener("click", (event) => { @@ -496,6 +557,7 @@ gamesList.addEventListener("click", async (event) => { persist(); markLocalDataForImport(); render(); + showToast("Jeu mis a jour.", "success"); return; } @@ -530,6 +592,7 @@ gamesList.addEventListener("click", async (event) => { } await refreshFromApi(state.selectedBrand, state.selectedConsole); + showToast("Action enregistree.", "success"); return; } catch (error) { console.error(error); @@ -564,6 +627,7 @@ gamesList.addEventListener("click", async (event) => { persist(); markLocalDataForImport(); render(); + showToast("Action enregistree.", "success"); }); cancelEditBtn.addEventListener("click", () => { @@ -678,10 +742,12 @@ restoreFileInput.addEventListener("change", async (event) => { }); function render() { + renderV2Chrome(); renderDataMode(); renderGoogleStatus(); renderBrandTabs(); renderConsoleTabs(); + updateV2FilterOptions(); renderGames(); renderSearchResults(); renderCollectionStats(); @@ -837,6 +903,78 @@ function renderCollectionStats() { totalGamesValue.textContent = `${totalValue.toFixed(2)} EUR`; } +function showToast(message, type = "info", timeoutMs = 2600) { + if (!toastContainer || !message) { + return; + } + const toast = document.createElement("div"); + toast.className = `toast ${type}`.trim(); + toast.textContent = message; + toastContainer.append(toast); + window.setTimeout(() => { + toast.remove(); + }, timeoutMs); +} + +function renderV2Chrome() { + if (!uiV2Enabled) { + return; + } + + document.body.classList.add("ui-v2"); + if (v2Toolbar) { + v2Toolbar.classList.remove("hidden"); + } + if (v2StickyBar) { + v2StickyBar.classList.remove("hidden"); + } +} + +function updateV2FilterOptions() { + if (!uiV2Enabled || !v2ConsoleFilter || !v2GenreFilter) { + return; + } + + const allGames = collectAllGames(); + const consoles = Array.from(new Set(allGames.map((game) => normalizeText(game.consoleName)).filter(Boolean))).sort(); + const genres = Array.from(new Set(allGames.flatMap((game) => splitGenres(game.genre)))).sort(); + + v2ConsoleFilter.innerHTML = `${consoles + .map((consoleName) => ``) + .join("")}`; + v2GenreFilter.innerHTML = `${genres + .map((genre) => ``) + .join("")}`; + + v2ConsoleFilter.value = v2ConsoleValue; + v2GenreFilter.value = v2GenreValue; + v2SortSelect.value = v2SortValue; +} + +function splitGenres(genreRaw) { + return normalizeText(genreRaw) + .split(/[\/,|]/) + .map((item) => normalizeText(item)) + .filter(Boolean); +} + +function badgeClassForGenre(genreValue) { + const normalized = normalizeText(genreValue).toLowerCase(); + if (normalized.includes("rpg")) { + return "rpg"; + } + if (normalized.includes("action") || normalized.includes("aventure") || normalized.includes("adventure")) { + return "action"; + } + if (normalized.includes("sport") || normalized.includes("football") || normalized.includes("course")) { + return "sport"; + } + if (normalized.includes("racing") || normalized.includes("kart")) { + return "racing"; + } + return "default"; +} + function renderSearchResults() { if (!quickSearchResults) { return; @@ -941,27 +1079,76 @@ function renderConsoleTabs() { function renderGames() { const selectedConsole = state.selectedConsole; - gameSectionTitle.textContent = showLoanedOnly - ? "Jeux pretes - Toutes consoles" - : selectedConsole - ? `Jeux - ${selectedConsole}` - : "Jeux"; + const inV2 = uiV2Enabled; + gameSectionTitle.textContent = inV2 + ? "Catalogue jeux" + : showLoanedOnly + ? "Jeux pretes - Toutes consoles" + : selectedConsole + ? `Jeux - ${selectedConsole}` + : "Jeux"; gamesList.innerHTML = ""; loanedFilterBtn.textContent = showLoanedOnly ? "Voir tous les jeux" : "Voir jeux pretes"; - if (!showLoanedOnly && !selectedConsole) { + if (!inV2 && !showLoanedOnly && !selectedConsole) { gamesList.innerHTML = '

Ajoute une section pour commencer.

'; return; } - const games = showLoanedOnly - ? collectAllGames().filter((game) => normalizeText(game.loanedTo)) - : state.gamesByConsole[selectedConsole] || []; + let games = inV2 + ? collectAllGames() + : showLoanedOnly + ? collectAllGames().filter((game) => normalizeText(game.loanedTo)) + : state.gamesByConsole[selectedConsole] || []; + + if (showLoanedOnly) { + games = games.filter((game) => normalizeText(game.loanedTo)); + } + + if (inV2) { + const searchTerm = v2SearchTerm.toLowerCase(); + if (searchTerm) { + games = games.filter((game) => { + const fields = [ + game.title, + game.publisher, + game.barcode, + game.consoleName, + game.genre, + ] + .map((value) => normalizeText(value).toLowerCase()) + .join(" "); + return fields.includes(searchTerm); + }); + } + if (v2ConsoleValue) { + games = games.filter((game) => normalizeText(game.consoleName) === v2ConsoleValue); + } + if (v2GenreValue) { + games = games.filter((game) => + splitGenres(game.genre).some((genre) => genre.toLowerCase() === v2GenreValue.toLowerCase()), + ); + } + + games.sort((a, b) => { + if (v2SortValue === "year_desc") { + return Number(b.year || 0) - Number(a.year || 0); + } + if (v2SortValue === "value_desc") { + return Number(b.value || 0) - Number(a.value || 0); + } + return normalizeText(a.title).localeCompare(normalizeText(b.title), "fr", { sensitivity: "base" }); + }); + + if (v2StickyCount) { + v2StickyCount.textContent = `${games.length} jeu${games.length > 1 ? "x" : ""}`; + } + } if (!games.length) { gamesList.innerHTML = showLoanedOnly ? '

Aucun jeu prete actuellement.

' - : '

Aucun jeu sur cette console pour le moment.

'; + : '

Aucun jeu pour ces filtres.

'; return; } @@ -974,20 +1161,38 @@ function renderGames() { card.querySelector(".game-title").textContent = game.title; - const metaParts = [ - showLoanedOnly ? `${game.brand} / ${game.consoleName}` : null, - game.barcode ? `Code: ${game.barcode}` : null, - game.version ? `Version: ${game.version}` : null, - game.genre ? `Genre: ${game.genre}` : null, - game.publisher ? `Editeur: ${game.publisher}` : null, - game.isDuplicate ? "Double: OUI" : null, - game.year ? `Annee: ${game.year}` : null, - game.purchasePrice != null ? `Prix achat: ${game.purchasePrice.toFixed(2)} EUR` : null, - game.value != null ? `Cote: ${game.value.toFixed(2)} EUR` : null, - game.condition != null ? `Etat: ${game.condition}` : null, - ].filter(Boolean); + const metaParts = inV2 + ? [ + game.publisher ? `Editeur: ${game.publisher}` : null, + game.year ? `Annee: ${game.year}` : null, + game.version ? `Version: ${game.version}` : null, + game.value != null ? `Cote: ${Number(game.value).toFixed(2)} EUR` : null, + game.consoleName ? `Console: ${game.consoleName}` : null, + ].filter(Boolean) + : [ + showLoanedOnly ? `${game.brand} / ${game.consoleName}` : null, + game.barcode ? `Code: ${game.barcode}` : null, + game.version ? `Version: ${game.version}` : null, + game.genre ? `Genre: ${game.genre}` : null, + game.publisher ? `Editeur: ${game.publisher}` : null, + game.isDuplicate ? "Double: OUI" : null, + game.year ? `Annee: ${game.year}` : null, + game.purchasePrice != null ? `Prix achat: ${game.purchasePrice.toFixed(2)} EUR` : null, + game.value != null ? `Cote: ${game.value.toFixed(2)} EUR` : null, + game.condition != null ? `Etat: ${game.condition}` : null, + ].filter(Boolean); card.querySelector(".game-meta").textContent = metaParts.join(" | ") || "Aucune information complementaire"; + const badgesContainer = card.querySelector(".game-badges"); + const genres = splitGenres(game.genre); + if (genres.length) { + badgesContainer.innerHTML = genres + .slice(0, 4) + .map((genre) => `${escapeHtml(genre)}`) + .join(""); + } else { + badgesContainer.innerHTML = ""; + } const coverEl = card.querySelector(".game-cover"); const coverUrl = normalizeText(game.coverUrl); if (coverUrl) { diff --git a/index.html b/index.html index 9219c7d..fe7f104 100644 --- a/index.html +++ b/index.html @@ -61,6 +61,15 @@
+ +

Catalogue perso

@@ -94,6 +103,32 @@

Jeux

+
@@ -177,12 +212,14 @@
+