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;
|
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 ───────────────────────────────────────────────── */
|
/* ── Checkbox in video rows ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
.pipeline-video__checkbox {
|
.pipeline-video__checkbox {
|
||||||
|
|
|
||||||
|
|
@ -749,6 +749,7 @@ export default function AdminPipeline() {
|
||||||
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
|
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
|
||||||
const [creators, setCreators] = useState<{ name: string; slug: string }[]>([]);
|
const [creators, setCreators] = useState<{ name: string; slug: string }[]>([]);
|
||||||
const [creatorFilter, setCreatorFilter] = useState<string | null>(null);
|
const [creatorFilter, setCreatorFilter] = useState<string | null>(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||||
const [bulkProgress, setBulkProgress] = useState<BulkProgress | null>(null);
|
const [bulkProgress, setBulkProgress] = useState<BulkProgress | null>(null);
|
||||||
const [bulkLog, setBulkLog] = useState<BulkLogEntry[]>([]);
|
const [bulkLog, setBulkLog] = useState<BulkLogEntry[]>([]);
|
||||||
|
|
@ -763,6 +764,10 @@ export default function AdminPipeline() {
|
||||||
const filteredVideos = videos.filter((v) => {
|
const filteredVideos = videos.filter((v) => {
|
||||||
if (activeFilter !== null && v.processing_status !== activeFilter) return false;
|
if (activeFilter !== null && v.processing_status !== activeFilter) return false;
|
||||||
if (creatorFilter !== null && v.creator_name !== creatorFilter) 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;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -857,7 +862,7 @@ export default function AdminPipeline() {
|
||||||
// Clear selection when filters change
|
// Clear selection when filters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
}, [activeFilter, creatorFilter]);
|
}, [activeFilter, creatorFilter, searchQuery]);
|
||||||
|
|
||||||
const handleTrigger = async (videoId: string) => {
|
const handleTrigger = async (videoId: string) => {
|
||||||
setActionLoading(videoId);
|
setActionLoading(videoId);
|
||||||
|
|
@ -1038,6 +1043,24 @@ export default function AdminPipeline() {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Bulk toolbar */}
|
{/* Bulk toolbar */}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue