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:
parent
11f7ca4fbf
commit
9bc253f2c9
2 changed files with 70 additions and 1 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue