feat(web): dashboard All Libraries view saves the global scope
The All Libraries entry in the library switcher could render the
cross-library dashboard but rejected its own settings: the modal
refused to open without a library ('Select a specific library...') and
a save sent library_id: '' (server 400). The server now stores a
global scope (separate table; null/absent library_id, empty string
still 400), so:
- openDashboardSettings allows the All view and fetches prefs without
the library param (reads the global row; previously a loose NULL
OR-clause matched an arbitrary library's row).
- saveDashboardSettings sends library_id: null from All Libraries.
- The settings modal gains a scope label (#dashboard-scope-label)
showing which library — or All Libraries — a save will customize
(user requirement: always know the scope).
Verified end-to-end in headless Chromium: reorder on All Libraries →
save (null) → reload → modal re-opens with the persisted order and the
All Libraries scope label; per-library save lands in its own row
without touching global (SQL-checked).
This commit is contained in:
1 parent
25d44452c0
commit
b1de0da512
3 files changed
+29
-19
No files matched your search
+20
-11
@@ -20,14 +20,13 @@ async function openDashboardSettings(): Promise<void> {
|
||||
const librarySelect = document.getElementById(
|
||||
"library-select",
|
||||
) as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value;
|
||||
if (!libraryId) {
|
||||
showToast("Select a specific library to customize preferences", "error");
|
||||
return;
|
||||
}
|
||||
const libraryId = librarySelect?.value || "";
|
||||
|
||||
// Empty select = the All Libraries view: its preferences are the GLOBAL
|
||||
// scope — the server returns that row when library_id is absent.
|
||||
const query = libraryId ? `?library_id=${encodeURIComponent(libraryId)}` : "";
|
||||
const response = await fetch(
|
||||
`/api/dashboard/preferences?library_id=${libraryId}`,
|
||||
`/api/dashboard/preferences${query}`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` },
|
||||
},
|
||||
@@ -36,6 +35,17 @@ async function openDashboardSettings(): Promise<void> {
|
||||
if (response.ok) {
|
||||
const prefs = await response.json();
|
||||
applyPreferencesToModal(prefs);
|
||||
// Scope indicator: make it unmistakable what a save will customize.
|
||||
const scopeEl = document.getElementById("dashboard-scope-label");
|
||||
if (scopeEl) {
|
||||
const optionText =
|
||||
librarySelect?.selectedOptions[0]?.textContent || libraryId;
|
||||
scopeEl.textContent = (
|
||||
libraryId ? optionText : optionText || "All Libraries"
|
||||
)
|
||||
.replace(/\s*\(\d+\)$/, "")
|
||||
.trim();
|
||||
}
|
||||
} else {
|
||||
showToast("Failed to load library preferences", "error");
|
||||
console.error("API Error:", response.status, response.statusText);
|
||||
@@ -142,7 +152,10 @@ async function saveDashboardSettings(): Promise<void> {
|
||||
) as HTMLSelectElement;
|
||||
const libraryId = librarySelect?.value || "";
|
||||
const response = await apiPut("/dashboard/preferences", {
|
||||
library_id: libraryId,
|
||||
// Empty select = the All Libraries view: an explicit JSON null is
|
||||
// the global scope (empty string is rejected by the server as a
|
||||
// lost value — never a scope).
|
||||
library_id: libraryId || null,
|
||||
hidden_collections: hiddenCollections,
|
||||
collection_order: collectionOrder,
|
||||
items_per_section: parseInt(itemsPerCollection),
|
||||
@@ -150,10 +163,6 @@ async function saveDashboardSettings(): Promise<void> {
|
||||
if (response.ok) {
|
||||
showToast("Dashboard settings saved", "success");
|
||||
closeDashboardSettings();
|
||||
if (!libraryId) {
|
||||
showToast("Select a specific library to customize preferences", "error");
|
||||
return;
|
||||
}
|
||||
await fetchAndRenderSections(libraryId);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user