const STORAGE_KEY = "video_game_collection_v1"; const initialState = { brands: { SONY: ["PlayStation", "PlayStation 2", "PlayStation 3", "PlayStation 4", "PlayStation 5"], MICROSOFT: ["Xbox", "Xbox 360", "Xbox One", "Xbox One X", "Xbox Series X"], }, gamesByConsole: {}, selectedBrand: "SONY", selectedConsole: "PlayStation", }; const state = loadState(); let dataMode = "local"; const platformForm = document.getElementById("platformForm"); const gameForm = document.getElementById("gameForm"); const brandInput = document.getElementById("brandInput"); const consoleInput = document.getElementById("consoleInput"); const titleInput = document.getElementById("titleInput"); const genreInput = document.getElementById("genreInput"); const publisherInput = document.getElementById("publisherInput"); const yearInput = document.getElementById("yearInput"); const valueInput = document.getElementById("valueInput"); const loanedToInput = document.getElementById("loanedToInput"); const gameSubmitBtn = document.getElementById("gameSubmitBtn"); const cancelEditBtn = document.getElementById("cancelEditBtn"); const brandTabs = document.getElementById("brandTabs"); const consoleTabs = document.getElementById("consoleTabs"); const gameSectionTitle = document.getElementById("gameSectionTitle"); const dataModeInfo = document.getElementById("dataModeInfo"); const gamesList = document.getElementById("gamesList"); const gameCardTemplate = document.getElementById("gameCardTemplate"); let editingGameId = null; platformForm.addEventListener("submit", (event) => { event.preventDefault(); const brand = brandInput.value.trim().toUpperCase(); const consoleName = consoleInput.value.trim(); if (!brand || !consoleName) { return; } state.brands[brand] = state.brands[brand] || []; if (!state.brands[brand].includes(consoleName)) { state.brands[brand].push(consoleName); } state.selectedBrand = brand; state.selectedConsole = consoleName; state.gamesByConsole[consoleName] = state.gamesByConsole[consoleName] || []; platformForm.reset(); persist(); render(); }); gameForm.addEventListener("submit", (event) => { event.preventDefault(); const title = titleInput.value.trim(); if (!title || !state.selectedConsole) { return; } state.gamesByConsole[state.selectedConsole] = state.gamesByConsole[state.selectedConsole] || []; if (editingGameId) { const games = state.gamesByConsole[state.selectedConsole]; const idx = games.findIndex((game) => game.id === editingGameId); if (idx !== -1) { games[idx] = { ...games[idx], title, genre: genreInput.value.trim(), publisher: publisherInput.value.trim(), year: yearInput.value ? Number(yearInput.value) : null, value: valueInput.value ? Number(valueInput.value) : null, loanedTo: loanedToInput.value.trim(), }; } } else { const game = { id: crypto.randomUUID(), title, genre: genreInput.value.trim(), publisher: publisherInput.value.trim(), year: yearInput.value ? Number(yearInput.value) : null, value: valueInput.value ? Number(valueInput.value) : null, loanedTo: loanedToInput.value.trim(), createdAt: new Date().toISOString(), }; state.gamesByConsole[state.selectedConsole].unshift(game); } resetEditMode(); persist(); render(); }); brandTabs.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } const target = event.target.closest("button[data-brand]"); if (!(target instanceof HTMLButtonElement)) { return; } const brand = target.dataset.brand; if (!brand) { return; } state.selectedBrand = brand; const consoles = state.brands[brand] || []; state.selectedConsole = consoles[0] || ""; resetEditMode(); persist(); render(); }); consoleTabs.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } const target = event.target.closest("button[data-console]"); if (!(target instanceof HTMLButtonElement)) { return; } const consoleName = target.dataset.console; if (!consoleName) { return; } state.selectedConsole = consoleName; resetEditMode(); persist(); render(); }); gamesList.addEventListener("click", (event) => { if (!(event.target instanceof Element)) { return; } const target = event.target.closest("button[data-action]"); if (!(target instanceof HTMLButtonElement)) { return; } const action = target.dataset.action; const id = target.dataset.id; if (!action || !id || !state.selectedConsole) { return; } const games = state.gamesByConsole[state.selectedConsole] || []; const idx = games.findIndex((game) => game.id === id); if (idx === -1) { return; } if (action === "delete") { games.splice(idx, 1); if (editingGameId === id) { resetEditMode(); } } if (action === "toggle-loan") { games[idx].loanedTo = games[idx].loanedTo ? "" : "A renseigner"; } if (action === "edit") { startEditMode(games[idx]); return; } persist(); render(); }); cancelEditBtn.addEventListener("click", () => { resetEditMode(); }); function render() { renderDataMode(); renderBrandTabs(); renderConsoleTabs(); renderGames(); } function renderDataMode() { if (!dataModeInfo) { return; } if (dataMode === "api") { dataModeInfo.textContent = "Source: API (lecture). Ecriture DB prevue a l'etape 4."; return; } if (dataMode === "local-fallback") { dataModeInfo.textContent = "Source: localStorage (fallback). API indisponible ou vide."; return; } dataModeInfo.textContent = "Source: localStorage"; } function renderBrandTabs() { const brands = Object.keys(state.brands); brandTabs.innerHTML = ""; for (const brand of brands) { const button = document.createElement("button"); button.type = "button"; button.className = `tab ${state.selectedBrand === brand ? "active" : ""}`; button.textContent = brand; button.dataset.brand = brand; brandTabs.append(button); } } function renderConsoleTabs() { const consoles = state.brands[state.selectedBrand] || []; if (!consoles.includes(state.selectedConsole)) { state.selectedConsole = consoles[0] || ""; } consoleTabs.innerHTML = ""; for (const consoleName of consoles) { const button = document.createElement("button"); button.type = "button"; button.className = `tab ${state.selectedConsole === consoleName ? "active" : ""}`; button.dataset.console = consoleName; const count = (state.gamesByConsole[consoleName] || []).length; button.innerHTML = `${consoleName}${count}`; consoleTabs.append(button); } } function renderGames() { const selectedConsole = state.selectedConsole; gameSectionTitle.textContent = selectedConsole ? `Jeux - ${selectedConsole}` : "Jeux"; gamesList.innerHTML = ""; if (!selectedConsole) { gamesList.innerHTML = '
Ajoute une section pour commencer.
'; return; } const games = state.gamesByConsole[selectedConsole] || []; if (!games.length) { gamesList.innerHTML = 'Aucun jeu sur cette console pour le moment.
'; return; } for (const game of games) { const card = gameCardTemplate.content.cloneNode(true); const article = card.querySelector(".game-card"); if (editingGameId === game.id) { article.classList.add("editing"); } card.querySelector(".game-title").textContent = game.title; const metaParts = [ game.genre ? `Genre: ${game.genre}` : null, game.publisher ? `Editeur: ${game.publisher}` : null, game.year ? `Annee: ${game.year}` : null, game.value != null ? `Cote: ${game.value.toFixed(2)} EUR` : null, ].filter(Boolean); card.querySelector(".game-meta").textContent = metaParts.join(" | ") || "Aucune information complementaire"; card.querySelector(".game-loan").textContent = game.loanedTo ? `Pret en cours: ${game.loanedTo}` : "Disponible dans ta collection"; const editBtn = card.querySelector('[data-action="edit"]'); const toggleBtn = card.querySelector('[data-action="toggle-loan"]'); const deleteBtn = card.querySelector('[data-action="delete"]'); editBtn.dataset.id = game.id; toggleBtn.dataset.id = game.id; toggleBtn.textContent = game.loanedTo ? "Marquer comme rendu" : "Marquer comme prete"; deleteBtn.dataset.id = game.id; gamesList.append(card); } } function startEditMode(game) { editingGameId = game.id; titleInput.value = game.title || ""; genreInput.value = game.genre || ""; publisherInput.value = game.publisher || ""; yearInput.value = game.year || ""; valueInput.value = game.value != null ? game.value : ""; loanedToInput.value = game.loanedTo || ""; gameSubmitBtn.textContent = "Mettre a jour le jeu"; cancelEditBtn.classList.remove("hidden"); renderGames(); } function resetEditMode() { editingGameId = null; gameForm.reset(); gameSubmitBtn.textContent = "Ajouter le jeu"; cancelEditBtn.classList.add("hidden"); } function loadState() { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) { return structuredClone(initialState); } return JSON.parse(raw); } catch { return structuredClone(initialState); } } function normalizeState() { state.brands = state.brands || structuredClone(initialState.brands); state.gamesByConsole = state.gamesByConsole || {}; const brands = Object.keys(state.brands); if (!brands.length) { state.brands = structuredClone(initialState.brands); } if (!state.selectedBrand || !state.brands[state.selectedBrand]) { state.selectedBrand = Object.keys(state.brands)[0]; } const consoles = state.brands[state.selectedBrand] || []; if (!state.selectedConsole || !consoles.includes(state.selectedConsole)) { state.selectedConsole = consoles[0] || ""; } } function persist() { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } function payloadHasCatalogData(payload) { if (!payload || typeof payload !== "object") { return false; } const brands = payload.brands && typeof payload.brands === "object" ? payload.brands : {}; const gamesByConsole = payload.gamesByConsole && typeof payload.gamesByConsole === "object" ? payload.gamesByConsole : {}; const consolesCount = Object.values(brands).reduce((count, consoles) => { if (!Array.isArray(consoles)) { return count; } return count + consoles.length; }, 0); const gamesCount = Object.values(gamesByConsole).reduce((count, games) => { if (!Array.isArray(games)) { return count; } return count + games.length; }, 0); return consolesCount > 0 || gamesCount > 0; } async function hydrateFromApi() { try { const response = await fetch("/api/catalog/full"); if (!response.ok) { throw new Error(`API error ${response.status}`); } const payload = await response.json(); if (!payloadHasCatalogData(payload)) { dataMode = "local-fallback"; return; } state.brands = payload.brands || {}; state.gamesByConsole = payload.gamesByConsole || {}; state.selectedBrand = Object.keys(state.brands)[0] || ""; state.selectedConsole = (state.brands[state.selectedBrand] || [])[0] || ""; dataMode = "api"; persist(); } catch { dataMode = "local-fallback"; } } async function bootstrap() { await hydrateFromApi(); normalizeState(); render(); } bootstrap();