UX: remove auto-cover feature and add inline game editing
This commit is contained in:
143
app.js
143
app.js
@@ -51,7 +51,6 @@ const totalGamesValue = document.getElementById("totalGamesValue");
|
||||
const migrateBtn = document.getElementById("migrateBtn");
|
||||
const backupControls = document.getElementById("backupControls");
|
||||
const backupBtn = document.getElementById("backupBtn");
|
||||
const autoCoverBtn = document.getElementById("autoCoverBtn");
|
||||
const restoreMergeBtn = document.getElementById("restoreMergeBtn");
|
||||
const restoreReplaceBtn = document.getElementById("restoreReplaceBtn");
|
||||
const restoreFileInput = document.getElementById("restoreFileInput");
|
||||
@@ -70,6 +69,7 @@ const scannerLastCode = document.getElementById("scannerLastCode");
|
||||
const gamesList = document.getElementById("gamesList");
|
||||
const gameCardTemplate = document.getElementById("gameCardTemplate");
|
||||
let editingGameId = null;
|
||||
let inlineEditingGameId = null;
|
||||
let pendingRestoreMode = "merge";
|
||||
let quickSearchTerm = "";
|
||||
let googleStatus = { configured: false, connected: false, email: "" };
|
||||
@@ -416,10 +416,85 @@ gamesList.addEventListener("click", async (event) => {
|
||||
const { game, games, idx, consoleName, brand } = gameRef;
|
||||
|
||||
if (action === "edit") {
|
||||
state.selectedBrand = brand;
|
||||
state.selectedConsole = consoleName;
|
||||
startEditMode(game);
|
||||
inlineEditingGameId = inlineEditingGameId === id ? null : id;
|
||||
render();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "inline-cancel") {
|
||||
inlineEditingGameId = null;
|
||||
renderGames();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "inline-save") {
|
||||
const article = target.closest(".game-card");
|
||||
if (!(article instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getInlineValue = (key) => {
|
||||
const input = article.querySelector(`[data-inline="${key}"]`);
|
||||
return input instanceof HTMLInputElement ? input.value.trim() : "";
|
||||
};
|
||||
const getInlineNumber = (key) => {
|
||||
const value = getInlineValue(key);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
const getInlineChecked = (key) => {
|
||||
const input = article.querySelector(`[data-inline="${key}"]`);
|
||||
return input instanceof HTMLInputElement ? input.checked : false;
|
||||
};
|
||||
|
||||
const title = getInlineValue("title");
|
||||
if (!title) {
|
||||
alert("Le titre est obligatoire.");
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedFields = {
|
||||
title,
|
||||
barcode: getInlineValue("barcode"),
|
||||
version: getInlineValue("version"),
|
||||
genre: getInlineValue("genre"),
|
||||
publisher: getInlineValue("publisher"),
|
||||
year: getInlineNumber("year"),
|
||||
purchasePrice: getInlineNumber("purchasePrice"),
|
||||
value: getInlineNumber("value"),
|
||||
condition: getInlineNumber("condition"),
|
||||
loanedTo: getInlineValue("loanedTo"),
|
||||
isDuplicate: getInlineChecked("isDuplicate"),
|
||||
coverUrl: game.coverUrl || "",
|
||||
};
|
||||
|
||||
if (apiReachable && dataMode !== "local-pending-import") {
|
||||
try {
|
||||
const payload = buildGamePayload(game, brand, consoleName, updatedFields);
|
||||
await apiRequest(`/api/catalog/games/${id}`, {
|
||||
method: "PUT",
|
||||
body: payload,
|
||||
});
|
||||
inlineEditingGameId = null;
|
||||
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("Mise a jour impossible via l'API.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
games[idx] = {
|
||||
...games[idx],
|
||||
...updatedFields,
|
||||
};
|
||||
inlineEditingGameId = null;
|
||||
persist();
|
||||
markLocalDataForImport();
|
||||
render();
|
||||
return;
|
||||
}
|
||||
@@ -546,41 +621,6 @@ backupBtn.addEventListener("click", async () => {
|
||||
}
|
||||
});
|
||||
|
||||
autoCoverBtn.addEventListener("click", async () => {
|
||||
if (!apiReachable) {
|
||||
alert("API indisponible. Enrichissement des pochettes impossible.");
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = window.confirm(
|
||||
"Lancer la recuperation automatique des pochettes depuis internet pour les jeux sans image ?",
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoCoverBtn.disabled = true;
|
||||
const originalLabel = autoCoverBtn.textContent;
|
||||
autoCoverBtn.textContent = "Traitement en cours...";
|
||||
try {
|
||||
const result = await apiRequest("/api/covers/autofill", {
|
||||
method: "POST",
|
||||
body: { limit: 350, overwrite: false },
|
||||
timeoutMs: 180000,
|
||||
});
|
||||
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
||||
alert(
|
||||
`Pochettes maj: ${result.updated || 0} / ${result.scanned || 0} jeu(x). Non trouves: ${result.notFound || 0}.`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert(`Echec auto-pochettes: ${error.message}`);
|
||||
} finally {
|
||||
autoCoverBtn.disabled = false;
|
||||
autoCoverBtn.textContent = originalLabel;
|
||||
}
|
||||
});
|
||||
|
||||
restoreMergeBtn.addEventListener("click", () => {
|
||||
pendingRestoreMode = "merge";
|
||||
restoreFileInput.click();
|
||||
@@ -928,7 +968,7 @@ function renderGames() {
|
||||
for (const game of games) {
|
||||
const card = gameCardTemplate.content.cloneNode(true);
|
||||
const article = card.querySelector(".game-card");
|
||||
if (editingGameId === game.id) {
|
||||
if (inlineEditingGameId === game.id) {
|
||||
article.classList.add("editing");
|
||||
}
|
||||
|
||||
@@ -972,6 +1012,29 @@ function renderGames() {
|
||||
|
||||
deleteBtn.dataset.id = game.id;
|
||||
|
||||
if (inlineEditingGameId === game.id) {
|
||||
const editor = document.createElement("div");
|
||||
editor.className = "inline-editor";
|
||||
editor.innerHTML = `
|
||||
<label>Titre<input data-inline="title" value="${escapeHtml(game.title || "")}" /></label>
|
||||
<label>Code-barres<input data-inline="barcode" value="${escapeHtml(game.barcode || "")}" /></label>
|
||||
<label>Version<input data-inline="version" value="${escapeHtml(game.version || "")}" /></label>
|
||||
<label>Genre<input data-inline="genre" value="${escapeHtml(game.genre || "")}" /></label>
|
||||
<label>Editeur<input data-inline="publisher" value="${escapeHtml(game.publisher || "")}" /></label>
|
||||
<label>Annee<input data-inline="year" type="number" min="1970" max="2100" value="${game.year != null ? escapeHtml(String(game.year)) : ""}" /></label>
|
||||
<label>Prix achat (EUR)<input data-inline="purchasePrice" type="number" min="0" step="0.01" value="${game.purchasePrice != null ? escapeHtml(String(game.purchasePrice)) : ""}" /></label>
|
||||
<label>Cote (EUR)<input data-inline="value" type="number" min="0" step="0.01" value="${game.value != null ? escapeHtml(String(game.value)) : ""}" /></label>
|
||||
<label>Etat (0-10)<input data-inline="condition" type="number" min="0" max="10" step="0.1" value="${game.condition != null ? escapeHtml(String(game.condition)) : ""}" /></label>
|
||||
<label>Prete a<input data-inline="loanedTo" value="${escapeHtml(game.loanedTo || "")}" /></label>
|
||||
<label class="checkbox-row"><input data-inline="isDuplicate" type="checkbox" ${game.isDuplicate ? "checked" : ""} />Jeu en double</label>
|
||||
<div class="inline-editor-actions">
|
||||
<button type="button" class="btn-inline" data-action="inline-save" data-id="${game.id}">Enregistrer</button>
|
||||
<button type="button" class="btn-inline danger" data-action="inline-cancel" data-id="${game.id}">Annuler</button>
|
||||
</div>
|
||||
`;
|
||||
card.querySelector(".game-main").append(editor);
|
||||
}
|
||||
|
||||
gamesList.append(card);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user