From 9bc253f2c91db0187c7517a7380e0d4b6ee20b1b Mon Sep 17 00:00:00 2001 From: jlightner Date: Tue, 31 Mar 2026 17:38:31 +0000 Subject: [PATCH] feat: Add real-time text search filter on pipeline admin page Filters video list by filename or creator name as you type. Works alongside the existing status and creator dropdown filters. Includes a clear button when text is entered. --- frontend/src/App.css | 46 ++++++++++++++++++++++++++++ frontend/src/pages/AdminPipeline.tsx | 25 ++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 067fa42..eb5d1fc 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -3387,6 +3387,52 @@ a.app-footer__repo:hover { outline-offset: 1px; } +/* ── Pipeline Search ──────────────────────────────────────────────────────── */ + +.pipeline-search { + position: relative; + flex: 1; + min-width: 180px; + max-width: 320px; +} + +.pipeline-search__input { + width: 100%; + padding: 0.4rem 2rem 0.4rem 0.75rem; + border-radius: 6px; + border: 1px solid var(--color-border); + background: var(--color-surface); + color: var(--color-text); + font-size: 0.85rem; +} + +.pipeline-search__input:focus { + outline: 2px solid var(--color-accent); + outline-offset: 1px; +} + +.pipeline-search__input::placeholder { + color: var(--color-text-muted); +} + +.pipeline-search__clear { + position: absolute; + right: 0.4rem; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--color-text-muted); + cursor: pointer; + font-size: 0.8rem; + padding: 0.2rem; + line-height: 1; +} + +.pipeline-search__clear:hover { + color: var(--color-text); +} + /* ── Checkbox in video rows ───────────────────────────────────────────────── */ .pipeline-video__checkbox { diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx index 85777f0..a92dc9f 100644 --- a/frontend/src/pages/AdminPipeline.tsx +++ b/frontend/src/pages/AdminPipeline.tsx @@ -749,6 +749,7 @@ export default function AdminPipeline() { const [debugMode, setDebugModeState] = useState(null); const [creators, setCreators] = useState<{ name: string; slug: string }[]>([]); const [creatorFilter, setCreatorFilter] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkProgress, setBulkProgress] = useState(null); const [bulkLog, setBulkLog] = useState([]); @@ -763,6 +764,10 @@ export default function AdminPipeline() { const filteredVideos = videos.filter((v) => { if (activeFilter !== null && v.processing_status !== activeFilter) return false; if (creatorFilter !== null && v.creator_name !== creatorFilter) return false; + if (searchQuery) { + const q = searchQuery.toLowerCase(); + if (!v.filename.toLowerCase().includes(q) && !v.creator_name.toLowerCase().includes(q)) return false; + } return true; }); @@ -857,7 +862,7 @@ export default function AdminPipeline() { // Clear selection when filters change useEffect(() => { setSelectedIds(new Set()); - }, [activeFilter, creatorFilter]); + }, [activeFilter, creatorFilter, searchQuery]); const handleTrigger = async (videoId: string) => { setActionLoading(videoId); @@ -1038,6 +1043,24 @@ export default function AdminPipeline() { )} +
+ setSearchQuery(e.target.value)} + /> + {searchQuery && ( + + )} +
{/* Bulk toolbar */}