From 8a2100c58714c8a42e87c6f8f440cc938ea7c7f3 Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Sun, 14 Jun 2026 22:01:35 -0400 Subject: [PATCH] fix(SearchLocal): add text filter functionality and clear filters option --- src/app/components/SearchLocal.tsx | 75 +++++++++++++++++------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/src/app/components/SearchLocal.tsx b/src/app/components/SearchLocal.tsx index 7d66929..39d816d 100644 --- a/src/app/components/SearchLocal.tsx +++ b/src/app/components/SearchLocal.tsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { flushSync } from 'react-dom'; -import { Search, Loader2, RefreshCw, FolderSearch, SlidersHorizontal, ArrowUp, ArrowDown, ArrowUpDown, HardDrive, FolderOpen } from 'lucide-react'; +import { Search, Loader2, RefreshCw, FolderSearch, SlidersHorizontal, HardDrive, FolderOpen, X } from 'lucide-react'; import { toast } from 'sonner'; import { humanFileSize, splitPath } from '../webdav'; import type { EntryInfo } from '../webdav'; @@ -130,6 +130,7 @@ const _store = { results: [] as SearchResult[], hasSearched: false, showFilter: false, + filterText: '', filterSystem: null as string | null, filterVideo: null as string | null, filterLanguage: null as string | null, @@ -181,6 +182,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } // the user clicks Search the database is almost always ready. const [dbPhase, setDbPhase] = useState<'idle' | 'ready'>('ready'); const [showFilter, setShowFilter] = useState(() => _store.showFilter); + const [filterText, setFilterText] = useState(() => _store.filterText); const [filterSystem, setFilterSystem] = useState(() => _store.filterSystem); const [filterVideo, setFilterVideo] = useState(() => _store.filterVideo); const [filterLanguage, setFilterLanguage] = useState(() => _store.filterLanguage); @@ -196,12 +198,13 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } _store.results = results; _store.hasSearched = hasSearched; _store.showFilter = showFilter; + _store.filterText = filterText; _store.filterSystem = filterSystem; _store.filterVideo = filterVideo; _store.filterLanguage = filterLanguage; _store.sortField = sortField; _store.sortDir = sortDir; - }, [query, results, hasSearched, showFilter, filterSystem, filterVideo, filterLanguage, sortField, sortDir]); + }, [query, results, hasSearched, showFilter, filterText, filterSystem, filterVideo, filterLanguage, sortField, sortDir]); useEffect(() => { if (_store.scrollTop > 0 && scrollRef.current) { @@ -226,6 +229,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } setIsSearching(true); setHasSearched(true); setSearchError(null); + setFilterText(''); setFilterSystem(null); setFilterVideo(null); setFilterLanguage(null); @@ -310,10 +314,10 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } return { systems, videos, langs }; }, [results]); - const hasAnyFacets = facets.systems.length > 0 || facets.videos.length > 0 || facets.langs.length > 0; - const visibleResults = useMemo(() => { + const needle = filterText.trim().toLowerCase(); let list = results.filter(r => + (!needle || r.name.toLowerCase().includes(needle) || r.path.toLowerCase().includes(needle)) && (filterSystem === null || r.system === filterSystem) && (filterVideo === null || r.video === filterVideo) && (filterLanguage === null || r.language === filterLanguage) @@ -330,12 +334,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } else { setSortField(field); setSortDir('asc'); } }; - const SortIcon = ({ field }: { field: SortField }) => { - if (sortField !== field) return ; - return sortDir === 'asc' ? : ; - }; - - const activeFilters = [filterSystem, filterVideo, filterLanguage].filter(Boolean).length; + const activeFilters = [filterText || null, filterSystem, filterVideo, filterLanguage].filter(Boolean).length; return ( <> @@ -360,7 +359,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } > - {hasAnyFacets && ( + {hasSearched && ( - {/* Filter panel */} -
-
- - - -
- Sort - - +
+ + {/* Filter + sort bar — same style as MediaManager */} +
+
+
+
+ + setFilterText(e.target.value)} + placeholder="Filter…" + className="w-full pl-7 pr-6 py-1 text-sm border border-neutral-300 rounded bg-white" + /> + {filterText && ( + + )}
+ {(['name', 'size'] as SortField[]).map(f => ( + + ))}
+ + +
@@ -493,7 +504,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }

{results.length > 0 && (