Step 3 migration: frontend reads API with local fallback

This commit is contained in:
Ponte
2026-02-11 15:00:40 +01:00
parent 89d9275e1a
commit adc7ec193e
7 changed files with 177 additions and 2 deletions

80
app.js
View File

@@ -11,7 +11,7 @@ const initialState = {
};
const state = loadState();
normalizeState();
let dataMode = "local";
const platformForm = document.getElementById("platformForm");
const gameForm = document.getElementById("gameForm");
@@ -29,6 +29,7 @@ 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;
@@ -188,11 +189,30 @@ cancelEditBtn.addEventListener("click", () => {
});
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 = "";
@@ -334,4 +354,60 @@ function persist() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}
render();
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();