From 9a0a39d3c18aafff72fb2d888339278d1dd06c17 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 26 Dec 2025 10:40:41 +0000 Subject: [PATCH] refactor(backups): simplify filter bar layout - Made search bar full-width and prominent - Removed redundant Status Filter (can use search) - Removed redundant Type Filter (overlaps with Source) - Removed Time Format toggle (rarely used) - Integrated ColumnPicker inline with filter groups - Layout now matches Dashboard/Overview pattern Reduces filter controls from ~10 groups to 5 for cleaner UX. --- .../src/components/Backups/BackupsFilter.tsx | 596 +++++++----------- .../src/components/Backups/UnifiedBackups.tsx | 19 +- 2 files changed, 241 insertions(+), 374 deletions(-) diff --git a/frontend-modern/src/components/Backups/BackupsFilter.tsx b/frontend-modern/src/components/Backups/BackupsFilter.tsx index 69f1c431de..72eecbb883 100644 --- a/frontend-modern/src/components/Backups/BackupsFilter.tsx +++ b/frontend-modern/src/components/Backups/BackupsFilter.tsx @@ -1,6 +1,8 @@ import { Component, Show, For, createSignal, onMount, createEffect, onCleanup } from 'solid-js'; import { Card } from '@/components/shared/Card'; import { SearchTipsPopover } from '@/components/shared/SearchTipsPopover'; +import { ColumnPicker } from '@/components/shared/ColumnPicker'; +import type { ColumnDef } from '@/hooks/useColumnVisibility'; import { STORAGE_KEYS } from '@/utils/localStorage'; import { createSearchHistoryManager } from '@/utils/searchHistory'; @@ -12,34 +14,27 @@ interface BackupsFilterProps { groupBy: () => 'date' | 'guest'; setGroupBy: (value: 'date' | 'guest') => void; searchInputRef?: (el: HTMLInputElement) => void; - typeFilter?: () => 'all' | 'VM' | 'LXC' | 'Host'; - setTypeFilter?: (value: 'all' | 'VM' | 'LXC' | 'Host') => void; - hasHostBackups?: () => boolean; sortKey: () => string; setSortKey: (value: string) => void; sortDirection: () => 'asc' | 'desc'; setSortDirection: (value: 'asc' | 'desc') => void; sortOptions?: { value: string; label: string }[]; onReset?: () => void; - statusFilter?: () => 'all' | 'verified' | 'unverified'; - setStatusFilter?: (value: 'all' | 'verified' | 'unverified') => void; - // Time format toggle - useRelativeTime?: () => boolean; - setUseRelativeTime?: (value: boolean) => void; + // Column visibility (optional) + columnVisibility?: { + availableToggles: () => ColumnDef[]; + isHiddenByUser: (id: string) => boolean; + toggle: (id: string) => void; + resetToDefaults: () => void; + }; } export const BackupsFilter: Component = (props) => { const sortOptions = props.sortOptions ?? [ { value: 'backupTime', label: 'Time' }, - { value: 'name', label: 'Guest Name' }, - { value: 'node', label: 'Node' }, + { value: 'name', label: 'Name' }, { value: 'vmid', label: 'VMID' }, - { value: 'backupType', label: 'Backup Type' }, { value: 'size', label: 'Size' }, - { value: 'storage', label: 'Storage' }, - { value: 'verified', label: 'Verified' }, - { value: 'type', label: 'Guest Type' }, - { value: 'owner', label: 'Owner' }, ]; const historyManager = createSearchHistoryManager(STORAGE_KEYS.BACKUPS_SEARCH_HISTORY); const [searchHistory, setSearchHistory] = createSignal([]); @@ -106,204 +101,202 @@ export const BackupsFilter: Component = (props) => { suppressBlurCommit = false; }); }; + + const hasActiveFilters = () => + props.search().trim() !== '' || + props.viewMode() !== 'all' || + props.groupBy() !== 'date' || + props.sortKey() !== 'backupTime' || + props.sortDirection() !== 'desc'; + return ( -
- {/* Search Bar */} -
-
- { - searchInputEl = el; - props.searchInputRef?.(el); - }} - type="text" - placeholder="Search backups or node:nodename" - value={props.search()} - onInput={(e) => props.setSearch(e.currentTarget.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - commitSearchToHistory(e.currentTarget.value); - closeHistory(); - } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { - e.preventDefault(); - setIsHistoryOpen(true); - } - }} - onBlur={(e) => { - if (suppressBlurCommit) { - return; - } - const next = e.relatedTarget as HTMLElement | null; - const interactingWithHistory = next - ? historyMenuRef?.contains(next) || historyToggleRef?.contains(next) - : false; - const interactingWithTips = - next?.getAttribute('aria-controls') === 'backups-search-help'; - if (!interactingWithHistory && !interactingWithTips) { - commitSearchToHistory(e.currentTarget.value); - } - }} - class="w-full pl-9 pr-16 py-1.5 text-sm border border-gray-300 dark:border-gray-600 rounded-lg - bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 - focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 dark:focus:border-blue-400 outline-none transition-all" - title="Search backups by name or filter by node" +
+ {/* Row 1: Search Bar - Full width */} +
+ { + searchInputEl = el; + props.searchInputRef?.(el); + }} + type="text" + placeholder="Search backups... (e.g., media, vm-104, node:pve1)" + value={props.search()} + onInput={(e) => props.setSearch(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + commitSearchToHistory(e.currentTarget.value); + closeHistory(); + } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { + e.preventDefault(); + setIsHistoryOpen(true); + } + }} + onBlur={(e) => { + if (suppressBlurCommit) { + return; + } + const next = e.relatedTarget as HTMLElement | null; + const interactingWithHistory = next + ? historyMenuRef?.contains(next) || historyToggleRef?.contains(next) + : false; + const interactingWithTips = + next?.getAttribute('aria-controls') === 'backups-search-help'; + if (!interactingWithHistory && !interactingWithTips) { + commitSearchToHistory(e.currentTarget.value); + } + }} + class="w-full pl-9 pr-20 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg + bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200 placeholder-gray-400 dark:placeholder-gray-500 + focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 dark:focus:border-blue-400 outline-none transition-all" + title="Search backups by name, VMID, or filter by node" + /> + + - + + - - - -
- - -
- -
(historyMenuRef = el)} - class="absolute left-0 right-0 top-full z-50 mt-2 w-full overflow-hidden rounded-lg border border-gray-200 bg-white text-sm shadow-xl dark:border-gray-700 dark:bg-gray-800" - role="listbox" - > - 0} - fallback={ -
- Your recent backup searches will show here. -
+ + + + +
+
+ - -
- )} - -
- -
-
- + return next; + }) + } + onMouseDown={markSuppressCommit} + aria-haspopup="listbox" + aria-expanded={isHistoryOpen()} + title={ + searchHistory().length > 0 + ? 'Show recent searches' + : 'No recent searches yet' + } + > + + + + Show search history + +
+ +
(historyMenuRef = el)} + class="absolute left-0 right-0 top-full z-50 mt-2 w-full overflow-hidden rounded-lg border border-gray-200 bg-white text-sm shadow-xl dark:border-gray-700 dark:bg-gray-800" + role="listbox" + > + 0} + fallback={ +
+ Your recent backup searches will show here. +
+ } + > +
+ + {(entry) => ( +
+ + +
+ )} +
+
+ +
+
+
- {/* Filters */} + {/* Row 2: Filters - Compact horizontal layout */}
{/* Source Filter */}
@@ -351,60 +344,6 @@ export const BackupsFilter: Component = (props) => { - {/* Type Filter - Only show when there are Host backups */} - -
- - - - -
- -
- {/* Group By Filter */}