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.
This commit is contained in:
jlightner 2026-03-31 17:38:31 +00:00
parent 11f7ca4fbf
commit 9bc253f2c9
2 changed files with 70 additions and 1 deletions

View file

@ -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 {

View file

@ -749,6 +749,7 @@ export default function AdminPipeline() {
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
const [creators, setCreators] = useState<{ name: string; slug: string }[]>([]);
const [creatorFilter, setCreatorFilter] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [bulkProgress, setBulkProgress] = useState<BulkProgress | null>(null);
const [bulkLog, setBulkLog] = useState<BulkLogEntry[]>([]);
@ -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() {
</select>
</div>
)}
<div className="pipeline-search">
<input
type="text"
className="pipeline-search__input"
placeholder="Filter by filename or creator…"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
{searchQuery && (
<button
className="pipeline-search__clear"
onClick={() => setSearchQuery("")}
aria-label="Clear search"
>
</button>
)}
</div>
</div>
{/* Bulk toolbar */}