Feature: improve loan flow and add games quick dashboard
This commit is contained in:
157
app.js
157
app.js
@@ -40,6 +40,11 @@ const storageState = document.getElementById("storageState");
|
||||
const toolsDrawer = document.getElementById("toolsDrawer");
|
||||
const toolsToggleBtn = document.getElementById("toolsToggleBtn");
|
||||
const toolsCloseBtn = document.getElementById("toolsCloseBtn");
|
||||
const gamesDrawer = document.getElementById("gamesDrawer");
|
||||
const gamesToggleBtn = document.getElementById("gamesToggleBtn");
|
||||
const gamesCloseBtn = document.getElementById("gamesCloseBtn");
|
||||
const totalGamesCount = document.getElementById("totalGamesCount");
|
||||
const totalGamesValue = document.getElementById("totalGamesValue");
|
||||
const migrateBtn = document.getElementById("migrateBtn");
|
||||
const backupControls = document.getElementById("backupControls");
|
||||
const backupBtn = document.getElementById("backupBtn");
|
||||
@@ -52,14 +57,17 @@ const googleBackupBtn = document.getElementById("googleBackupBtn");
|
||||
const googleRestoreBtn = document.getElementById("googleRestoreBtn");
|
||||
const quickSearchInput = document.getElementById("quickSearchInput");
|
||||
const quickSearchResults = document.getElementById("quickSearchResults");
|
||||
const loanedFilterBtn = document.getElementById("loanedFilterBtn");
|
||||
const gamesList = document.getElementById("gamesList");
|
||||
const gameCardTemplate = document.getElementById("gameCardTemplate");
|
||||
let editingGameId = null;
|
||||
let pendingRestoreMode = "merge";
|
||||
let quickSearchTerm = "";
|
||||
let googleStatus = { configured: false, connected: false, email: "" };
|
||||
let showLoanedOnly = false;
|
||||
|
||||
toolsToggleBtn.addEventListener("click", () => {
|
||||
gamesDrawer.classList.remove("open");
|
||||
toolsDrawer.classList.toggle("open");
|
||||
});
|
||||
|
||||
@@ -67,22 +75,37 @@ toolsCloseBtn.addEventListener("click", () => {
|
||||
toolsDrawer.classList.remove("open");
|
||||
});
|
||||
|
||||
gamesToggleBtn.addEventListener("click", () => {
|
||||
toolsDrawer.classList.remove("open");
|
||||
gamesDrawer.classList.toggle("open");
|
||||
});
|
||||
|
||||
gamesCloseBtn.addEventListener("click", () => {
|
||||
gamesDrawer.classList.remove("open");
|
||||
});
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
if (!toolsDrawer.classList.contains("open")) {
|
||||
const toolsOpen = toolsDrawer.classList.contains("open");
|
||||
const gamesOpen = gamesDrawer.classList.contains("open");
|
||||
if (!toolsOpen && !gamesOpen) {
|
||||
return;
|
||||
}
|
||||
if (toolsDrawer.contains(event.target) || toolsToggleBtn.contains(event.target)) {
|
||||
const clickedInsideTools = toolsDrawer.contains(event.target) || toolsToggleBtn.contains(event.target);
|
||||
const clickedInsideGames = gamesDrawer.contains(event.target) || gamesToggleBtn.contains(event.target);
|
||||
if (clickedInsideTools || clickedInsideGames) {
|
||||
return;
|
||||
}
|
||||
toolsDrawer.classList.remove("open");
|
||||
gamesDrawer.classList.remove("open");
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape") {
|
||||
toolsDrawer.classList.remove("open");
|
||||
gamesDrawer.classList.remove("open");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -133,6 +156,11 @@ quickSearchInput.addEventListener("input", (event) => {
|
||||
renderSearchResults();
|
||||
});
|
||||
|
||||
loanedFilterBtn.addEventListener("click", () => {
|
||||
showLoanedOnly = !showLoanedOnly;
|
||||
renderGames();
|
||||
});
|
||||
|
||||
platformForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -317,18 +345,22 @@ gamesList.addEventListener("click", async (event) => {
|
||||
|
||||
const action = target.dataset.action;
|
||||
const id = target.dataset.id;
|
||||
if (!action || !id || !state.selectedConsole) {
|
||||
if (!action || !id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const games = state.gamesByConsole[state.selectedConsole] || [];
|
||||
const idx = games.findIndex((game) => game.id === id);
|
||||
if (idx === -1) {
|
||||
const gameRef = findGameById(id);
|
||||
if (!gameRef) {
|
||||
return;
|
||||
}
|
||||
const { game, games, idx, consoleName, brand } = gameRef;
|
||||
|
||||
if (action === "edit") {
|
||||
startEditMode(games[idx]);
|
||||
state.selectedBrand = brand;
|
||||
state.selectedConsole = consoleName;
|
||||
startEditMode(game);
|
||||
persist();
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -342,7 +374,24 @@ gamesList.addEventListener("click", async (event) => {
|
||||
}
|
||||
|
||||
if (action === "toggle-loan") {
|
||||
await apiRequest(`/api/catalog/games/${id}/toggle-loan`, { method: "POST" });
|
||||
if (game.loanedTo) {
|
||||
await apiRequest(`/api/catalog/games/${id}/toggle-loan`, { method: "POST" });
|
||||
} else {
|
||||
const borrower = window.prompt("Nom de la personne a qui tu pretes ce jeu :");
|
||||
if (borrower === null) {
|
||||
return;
|
||||
}
|
||||
const loanedTo = borrower.trim();
|
||||
if (!loanedTo) {
|
||||
alert("Le nom est obligatoire pour marquer le jeu comme prete.");
|
||||
return;
|
||||
}
|
||||
const payload = buildGamePayload(game, brand, consoleName, { loanedTo });
|
||||
await apiRequest(`/api/catalog/games/${id}`, {
|
||||
method: "PUT",
|
||||
body: payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
||||
@@ -361,7 +410,20 @@ gamesList.addEventListener("click", async (event) => {
|
||||
}
|
||||
|
||||
if (action === "toggle-loan") {
|
||||
games[idx].loanedTo = games[idx].loanedTo ? "" : "A renseigner";
|
||||
if (games[idx].loanedTo) {
|
||||
games[idx].loanedTo = "";
|
||||
} else {
|
||||
const borrower = window.prompt("Nom de la personne a qui tu pretes ce jeu :");
|
||||
if (borrower === null) {
|
||||
return;
|
||||
}
|
||||
const loanedTo = borrower.trim();
|
||||
if (!loanedTo) {
|
||||
alert("Le nom est obligatoire pour marquer le jeu comme prete.");
|
||||
return;
|
||||
}
|
||||
games[idx].loanedTo = loanedTo;
|
||||
}
|
||||
}
|
||||
|
||||
persist();
|
||||
@@ -487,6 +549,7 @@ function render() {
|
||||
renderConsoleTabs();
|
||||
renderGames();
|
||||
renderSearchResults();
|
||||
renderCollectionStats();
|
||||
}
|
||||
|
||||
function renderDataMode() {
|
||||
@@ -576,6 +639,40 @@ function findBrandByConsole(consoleName) {
|
||||
return "INCONNUE";
|
||||
}
|
||||
|
||||
function findGameById(id) {
|
||||
for (const [consoleName, games] of Object.entries(state.gamesByConsole)) {
|
||||
const idx = (games || []).findIndex((game) => game.id === id);
|
||||
if (idx !== -1) {
|
||||
return {
|
||||
consoleName,
|
||||
brand: findBrandByConsole(consoleName),
|
||||
games,
|
||||
idx,
|
||||
game: games[idx],
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildGamePayload(game, brand, consoleName, overrides = {}) {
|
||||
return {
|
||||
brand,
|
||||
consoleName,
|
||||
title: game.title || "",
|
||||
version: game.version || "",
|
||||
genre: game.genre || "",
|
||||
publisher: game.publisher || "",
|
||||
isDuplicate: Boolean(game.isDuplicate),
|
||||
year: game.year != null ? Number(game.year) : null,
|
||||
purchasePrice: game.purchasePrice != null ? Number(game.purchasePrice) : null,
|
||||
value: game.value != null ? Number(game.value) : null,
|
||||
condition: game.condition != null ? Number(game.condition) : null,
|
||||
loanedTo: game.loanedTo || "",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function collectAllGames() {
|
||||
const all = [];
|
||||
for (const [consoleName, games] of Object.entries(state.gamesByConsole)) {
|
||||
@@ -587,6 +684,22 @@ function collectAllGames() {
|
||||
return all;
|
||||
}
|
||||
|
||||
function renderCollectionStats() {
|
||||
if (!totalGamesCount || !totalGamesValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allGames = collectAllGames();
|
||||
const totalCount = allGames.length;
|
||||
const totalValue = allGames.reduce((sum, game) => {
|
||||
const value = typeof game.value === "number" ? game.value : Number(game.value);
|
||||
return Number.isFinite(value) ? sum + value : sum;
|
||||
}, 0);
|
||||
|
||||
totalGamesCount.textContent = String(totalCount);
|
||||
totalGamesValue.textContent = `${totalValue.toFixed(2)} EUR`;
|
||||
}
|
||||
|
||||
function renderSearchResults() {
|
||||
if (!quickSearchResults) {
|
||||
return;
|
||||
@@ -691,17 +804,27 @@ function renderConsoleTabs() {
|
||||
|
||||
function renderGames() {
|
||||
const selectedConsole = state.selectedConsole;
|
||||
gameSectionTitle.textContent = selectedConsole ? `Jeux - ${selectedConsole}` : "Jeux";
|
||||
gameSectionTitle.textContent = showLoanedOnly
|
||||
? "Jeux pretes - Toutes consoles"
|
||||
: selectedConsole
|
||||
? `Jeux - ${selectedConsole}`
|
||||
: "Jeux";
|
||||
gamesList.innerHTML = "";
|
||||
loanedFilterBtn.textContent = showLoanedOnly ? "Voir tous les jeux" : "Voir jeux pretes";
|
||||
|
||||
if (!selectedConsole) {
|
||||
if (!showLoanedOnly && !selectedConsole) {
|
||||
gamesList.innerHTML = '<p class="empty">Ajoute une section pour commencer.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
const games = state.gamesByConsole[selectedConsole] || [];
|
||||
const games = showLoanedOnly
|
||||
? collectAllGames().filter((game) => normalizeText(game.loanedTo))
|
||||
: state.gamesByConsole[selectedConsole] || [];
|
||||
|
||||
if (!games.length) {
|
||||
gamesList.innerHTML = '<p class="empty">Aucun jeu sur cette console pour le moment.</p>';
|
||||
gamesList.innerHTML = showLoanedOnly
|
||||
? '<p class="empty">Aucun jeu prete actuellement.</p>'
|
||||
: '<p class="empty">Aucun jeu sur cette console pour le moment.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -715,6 +838,7 @@ function renderGames() {
|
||||
card.querySelector(".game-title").textContent = game.title;
|
||||
|
||||
const metaParts = [
|
||||
showLoanedOnly ? `${game.brand} / ${game.consoleName}` : null,
|
||||
game.version ? `Version: ${game.version}` : null,
|
||||
game.genre ? `Genre: ${game.genre}` : null,
|
||||
game.publisher ? `Editeur: ${game.publisher}` : null,
|
||||
@@ -770,6 +894,13 @@ function resetEditMode() {
|
||||
cancelEditBtn.classList.add("hidden");
|
||||
}
|
||||
|
||||
function normalizeText(value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return String(value).trim();
|
||||
}
|
||||
|
||||
function loadState() {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
Reference in New Issue
Block a user