UI V3: add top navigation, sidebar layout, and dedicated loans/stats views

This commit is contained in:
Ponte
2026-03-05 19:36:03 +01:00
parent e98d8c5717
commit afabdce17c
3 changed files with 268 additions and 42 deletions

116
app.js
View File

@@ -60,6 +60,15 @@ const googleBackupBtn = document.getElementById("googleBackupBtn");
const googleRestoreBtn = document.getElementById("googleRestoreBtn");
const quickSearchInput = document.getElementById("quickSearchInput");
const quickSearchResults = document.getElementById("quickSearchResults");
const topNavButtons = document.querySelectorAll(".topnav-btn[data-view]");
const gamesPanel = document.getElementById("gamesPanel");
const statsView = document.getElementById("statsView");
const scannerZone = document.getElementById("scannerZone");
const searchZone = document.getElementById("searchZone");
const statsTotalGames = document.getElementById("statsTotalGames");
const statsTotalValue = document.getElementById("statsTotalValue");
const statsLoanedGames = document.getElementById("statsLoanedGames");
const statsConsoleCount = document.getElementById("statsConsoleCount");
const loanedFilterBtn = document.getElementById("loanedFilterBtn");
const bulkActionsBar = document.getElementById("bulkActionsBar");
const bulkSelectPage = document.getElementById("bulkSelectPage");
@@ -105,6 +114,7 @@ let selectedGameIds = new Set();
let currentPage = 1;
let currentPageGameIds = [];
let currentTotalPages = 1;
let currentView = "catalogue";
// V2 is now the default UI. Use ?ui=v1 to force legacy mode if needed.
const uiParam = new URLSearchParams(window.location.search).get("ui");
const uiV2Enabled = uiParam !== "v1";
@@ -232,12 +242,19 @@ quickSearchInput.addEventListener("input", (event) => {
});
loanedFilterBtn.addEventListener("click", () => {
showLoanedOnly = !showLoanedOnly;
resetPaging();
selectedGameIds.clear();
renderGames();
setCurrentView(currentView === "loans" ? "catalogue" : "loans");
});
for (const navButton of topNavButtons) {
navButton.addEventListener("click", () => {
const view = navButton.dataset.view;
if (!view) {
return;
}
setCurrentView(view);
});
}
if (bulkSelectPage) {
bulkSelectPage.addEventListener("change", (event) => {
const checked = Boolean(event.target.checked);
@@ -885,6 +902,7 @@ restoreFileInput.addEventListener("change", async (event) => {
function render() {
renderV2Chrome();
renderViewChrome();
renderDataMode();
renderGoogleStatus();
renderBrandTabs();
@@ -895,6 +913,59 @@ function render() {
renderCollectionStats();
}
function setCurrentView(view) {
const allowed = new Set(["catalogue", "loans", "stats"]);
if (!allowed.has(view)) {
return;
}
currentView = view;
showLoanedOnly = view === "loans";
resetPaging();
selectedGameIds.clear();
render();
}
function renderViewChrome() {
const isStats = currentView === "stats";
const isLoans = currentView === "loans";
for (const navButton of topNavButtons) {
navButton.classList.toggle("active", navButton.dataset.view === currentView);
}
if (gamesPanel) {
gamesPanel.classList.toggle("hidden", isStats);
}
if (statsView) {
statsView.classList.toggle("hidden", !isStats);
}
if (gameForm) {
gameForm.classList.toggle("hidden", isLoans || isStats || v2FormCollapsed);
}
if (scannerZone) {
scannerZone.classList.toggle("hidden", isLoans || isStats);
}
if (searchZone) {
searchZone.classList.toggle("hidden", isStats);
}
if (bulkActionsBar) {
bulkActionsBar.classList.toggle("hidden", isStats);
}
if (paginationBar) {
paginationBar.classList.toggle("hidden", isStats);
}
if (v2ToggleFormBtn) {
v2ToggleFormBtn.classList.toggle("hidden", isLoans || isStats);
}
if (v2QuickBackupBtn) {
v2QuickBackupBtn.classList.toggle("hidden", isStats);
}
if (loanedFilterBtn) {
loanedFilterBtn.textContent = isLoans ? "Retour catalogue" : "Aller a la vue prets";
loanedFilterBtn.classList.toggle("hidden", isStats);
}
}
function renderDataMode() {
if (!dataModeInfo) {
return;
@@ -1040,9 +1111,24 @@ function renderCollectionStats() {
const value = typeof game.value === "number" ? game.value : Number(game.value);
return Number.isFinite(value) ? sum + value : sum;
}, 0);
const loanedCount = allGames.filter((game) => normalizeText(game.loanedTo)).length;
const activeConsoles = Object.values(state.gamesByConsole).filter((games) => Array.isArray(games) && games.length > 0)
.length;
totalGamesCount.textContent = String(totalCount);
totalGamesValue.textContent = `${totalValue.toFixed(2)} EUR`;
if (statsTotalGames) {
statsTotalGames.textContent = String(totalCount);
}
if (statsTotalValue) {
statsTotalValue.textContent = `${totalValue.toFixed(2)} EUR`;
}
if (statsLoanedGames) {
statsLoanedGames.textContent = String(loanedCount);
}
if (statsConsoleCount) {
statsConsoleCount.textContent = String(activeConsoles);
}
}
function showToast(message, type = "info", timeoutMs = 2600) {
@@ -1211,10 +1297,12 @@ function updateBulkAndPaginationUi(pageGames, totalFilteredCount) {
nextPageBtn.disabled = currentPage >= currentTotalPages;
}
if (paginationBar) {
paginationBar.classList.toggle("hidden", totalFilteredCount <= pageSizeForViewport());
const shouldHidePagination = currentView === "stats" || totalFilteredCount <= pageSizeForViewport();
paginationBar.classList.toggle("hidden", shouldHidePagination);
}
if (bulkActionsBar) {
bulkActionsBar.classList.toggle("hidden", totalFilteredCount === 0);
const shouldHideBulk = currentView === "stats" || totalFilteredCount === 0;
bulkActionsBar.classList.toggle("hidden", shouldHideBulk);
}
}
@@ -1412,15 +1500,15 @@ async function performBulkAction(action) {
function renderGames() {
const selectedConsole = state.selectedConsole;
const inV2 = uiV2Enabled;
gameSectionTitle.textContent = inV2
? "Catalogue jeux"
: showLoanedOnly
? "Jeux pretes - Toutes consoles"
: selectedConsole
? `Jeux - ${selectedConsole}`
: "Jeux";
gameSectionTitle.textContent =
currentView === "loans"
? "Jeux pretes"
: inV2
? "Catalogue jeux"
: selectedConsole
? `Jeux - ${selectedConsole}`
: "Jeux";
gamesList.innerHTML = "";
loanedFilterBtn.textContent = showLoanedOnly ? "Voir tous les jeux" : "Voir jeux pretes";
if (!inV2 && !showLoanedOnly && !selectedConsole) {
gamesList.innerHTML = '<p class="empty">Ajoute une section pour commencer.</p>';