From 3d00d75863ed28dbb75a171e6c67d86ccb767cbf Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 11 Jul 2026 19:44:53 +0200 Subject: [PATCH] fix(admin-ui): Plex library toggle inversion + Purge-discovery confirm (+ cleanups) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs: - ConfigPanel (CB1): unchecking a Plex library from the "all" (empty) state made that library the ONLY selected one instead of all-except-it, because the toggle ADDED the key to an empty list. Expand "all" to the explicit section set before toggling. E2E-verified: uncheck from all → all-except-one. - Scheduler (SB1): "Purge discovery" bulk-deleted search videos/videos/channels immediately with no confirm (every AdminUsers destructive action uses useConfirm). Wrapped in a danger useConfirm dialog (+ trilingual purgeConfirmTitle key). E2E-verified: dialog appears, Cancel aborts. Cleanups (behavior-neutral): - Register LS.configTab; ConfigPanel uses it instead of the bare "siftlode.configTab". - ConfigPanel + SettingsPanel import SaveState from ui/DraftSaveBar instead of redeclaring the union (PrefsSaveState is now an alias). tsc green, re-review clean, localdev boots. --- frontend/src/components/ConfigPanel.tsx | 17 +++++++++++------ frontend/src/components/Scheduler.tsx | 12 +++++++++++- frontend/src/components/SettingsPanel.tsx | 4 ++-- frontend/src/i18n/locales/de/scheduler.json | 1 + frontend/src/i18n/locales/en/scheduler.json | 1 + frontend/src/i18n/locales/hu/scheduler.json | 1 + frontend/src/lib/storage.ts | 1 + 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/ConfigPanel.tsx b/frontend/src/components/ConfigPanel.tsx index 70f2b3c..680586a 100644 --- a/frontend/src/components/ConfigPanel.tsx +++ b/frontend/src/components/ConfigPanel.tsx @@ -7,7 +7,8 @@ import { notify } from "../lib/notifications"; import Tooltip from "./Tooltip"; import Tabs, { usePersistedTab } from "./Tabs"; import { Switch } from "./ui/form"; -import { DraftSaveBar } from "./ui/DraftSaveBar"; +import { DraftSaveBar, type SaveState } from "./ui/DraftSaveBar"; +import { LS } from "../lib/storage"; // Admin Configuration page: edit DB-overridable operational settings (registry-driven from the // backend — adding a key there makes it appear here automatically). Edits are drafted and @@ -15,7 +16,6 @@ import { DraftSaveBar } from "./ui/DraftSaveBar"; // the server until Save. Group order is fixed where known so logically related settings (e.g. // Email/SMTP) stay together. const GROUP_ORDER = ["access", "email", "youtube", "google", "quota", "backfill", "shorts", "batch", "downloads", "plex"]; -type SaveState = "idle" | "saving" | "saved" | "error"; export default function ConfigPanel() { const { t } = useTranslation(); @@ -40,7 +40,7 @@ export default function ConfigPanel() { const [saveState, setSaveState] = useState("idle"); // One tab per config group (persisted). Called before the loading guard so hook order is // stable; the active id is clamped to a real group at render time once data has loaded. - const [tab, setTab] = usePersistedTab("siftlode.configTab"); + const [tab, setTab] = usePersistedTab(LS.configTab); // Re-seed the draft whenever the server data changes (initial load + after a save/reset). useEffect(() => { @@ -110,9 +110,14 @@ export default function ConfigPanel() { }); const plexLibs = (draft["plex_libraries"] ?? "").split(",").map((s) => s.trim()).filter(Boolean); const togglePlexLib = (key: string) => { - const next = plexLibs.includes(key) - ? plexLibs.filter((k) => k !== key) - : [...plexLibs, key]; + // An empty list means "all libraries" — every box renders checked. Unchecking one from that + // state must select all-except-this, so expand "all" to the explicit section set first; + // otherwise the toggle would ADD the key and leave it as the ONLY selected library. + const allKeys = plexResult?.sections?.map((s) => s.key) ?? []; + const current = plexLibs.length === 0 ? allKeys : plexLibs; + const next = current.includes(key) + ? current.filter((k) => k !== key) + : [...current, key]; setDraft((d) => ({ ...d, plex_libraries: next.join(",") })); }; diff --git a/frontend/src/components/Scheduler.tsx b/frontend/src/components/Scheduler.tsx index f57eddf..e399ccd 100644 --- a/frontend/src/components/Scheduler.tsx +++ b/frontend/src/components/Scheduler.tsx @@ -18,6 +18,7 @@ import { } from "lucide-react"; import { api, type SchedulerJob, type SchedulerStatus } from "../lib/api"; import { useLiveQuery } from "../lib/useLiveQuery"; +import { useConfirm } from "./ConfirmProvider"; import { relativeTime } from "../lib/format"; import { notify } from "../lib/notifications"; import Tooltip from "./Tooltip"; @@ -309,6 +310,7 @@ function Stat({ export default function Scheduler() { const { t } = useTranslation(); const qc = useQueryClient(); + const confirm = useConfirm(); // Poll faster while any job is running so live progress is smooth, then ease back when // idle. Derived from the freshest data by react-query itself (see useLiveQuery). const q = useLiveQuery(["scheduler"], api.schedulerStatus, { @@ -411,7 +413,15 @@ export default function Scheduler() {