From 29be11cf759ae034bf4646d7c6c68453219fe06e Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 10 Jul 2025 12:28:49 +0700 Subject: [PATCH] add error state filtering --- backend/download/serializers.py | 1 + backend/download/views.py | 6 + frontend/src/api/loader/loadDownloadQueue.ts | 2 + frontend/src/pages/Download.tsx | 165 ++++++++++--------- 4 files changed, 99 insertions(+), 75 deletions(-) diff --git a/backend/download/serializers.py b/backend/download/serializers.py index 1479edad..b838b69c 100644 --- a/backend/download/serializers.py +++ b/backend/download/serializers.py @@ -48,6 +48,7 @@ class DownloadListQuerySerializer( channel = serializers.CharField(required=False, help_text="channel ID") page = serializers.IntegerField(required=False) q = serializers.CharField(required=False, help_text="Search Query") + error = serializers.BooleanField(required=False, allow_null=True) class DownloadListQueueDeleteQuerySerializer(serializers.Serializer): diff --git a/backend/download/views.py b/backend/download/views.py index 71729a5c..bc30d12b 100644 --- a/backend/download/views.py +++ b/backend/download/views.py @@ -77,6 +77,12 @@ class DownloadApiListView(ApiBaseView): if search_query: must_list.append({"match_phrase_prefix": {"title": search_query}}) + if validated_data.get("error") is not None: + operator = "must" if validated_data["error"] else "must_not" + must_list.append( + {"bool": {operator: [{"exists": {"field": "message"}}]}} + ) + self.data["query"] = {"bool": {"must": must_list}} self.get_document_list(request) diff --git a/frontend/src/api/loader/loadDownloadQueue.ts b/frontend/src/api/loader/loadDownloadQueue.ts index 2a2f4087..367638ab 100644 --- a/frontend/src/api/loader/loadDownloadQueue.ts +++ b/frontend/src/api/loader/loadDownloadQueue.ts @@ -5,6 +5,7 @@ const loadDownloadQueue = async ( page: number, channelId: string | null, vid_type: string | null, + errorFilterFromUrl: string | null, showIgnored: boolean, search: string, ) => { @@ -14,6 +15,7 @@ const loadDownloadQueue = async ( if (channelId) searchParams.append('channel', channelId); if (vid_type) searchParams.append('vid_type', vid_type); if (search) searchParams.append('q', encodeURIComponent(search)); + if (errorFilterFromUrl !== null) searchParams.append('error', errorFilterFromUrl); searchParams.append('filter', showIgnored ? 'ignore' : 'pending'); const endpoint = `/api/download/${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; diff --git a/frontend/src/pages/Download.tsx b/frontend/src/pages/Download.tsx index 9d361459..824a97c7 100644 --- a/frontend/src/pages/Download.tsx +++ b/frontend/src/pages/Download.tsx @@ -4,7 +4,6 @@ import iconAdd from '/img/icon-add.svg'; import iconSubstract from '/img/icon-substract.svg'; import iconGridView from '/img/icon-gridview.svg'; import iconListView from '/img/icon-listview.svg'; -import iconSearch from '/img/icon-search.svg'; import { Fragment, useEffect, useState } from 'react'; import { useOutletContext, useSearchParams } from 'react-router-dom'; import { ConfigType } from './Home'; @@ -59,13 +58,13 @@ const Download = () => { const channelFilterFromUrl = searchParams.get('channel'); const ignoredOnlyParam = searchParams.get('ignored'); const vidTypeFilterFromUrl = searchParams.get('vid-type'); + const errorFilterFromUrl = searchParams.get('error'); const [refresh, setRefresh] = useState(false); const [showHiddenForm, setShowHiddenForm] = useState(false); const [addAsAutoStart, setAddAsAutoStart] = useState(false); const [addAsFlat, setAddAsFlat] = useState(false); const [showQueueActions, setShowQueueActions] = useState(false); - const [showSearchInput, setShowSearchInput] = useState(false); const [searchInput, setSearchInput] = useState(''); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [downloadPending, setDownloadPending] = useState(false); @@ -120,6 +119,7 @@ const Download = () => { currentPage, channelFilterFromUrl, vidTypeFilterFromUrl, + errorFilterFromUrl, showIgnored, searchInput, ); @@ -140,7 +140,14 @@ const Download = () => { useEffect(() => { setRefresh(true); - }, [channelFilterFromUrl, vidTypeFilterFromUrl, currentPage, showIgnored, searchInput]); + }, [ + channelFilterFromUrl, + vidTypeFilterFromUrl, + errorFilterFromUrl, + currentPage, + showIgnored, + searchInput, + ]); useEffect(() => { (async () => { @@ -160,11 +167,6 @@ const Download = () => { setRefresh(true); }; - const handleShowSearchInput = () => { - if (showSearchInput) setSearchInput(''); - setShowSearchInput(!showSearchInput); - }; - return ( <> TA | Downloads @@ -318,74 +320,8 @@ const Download = () => {
- {showSearchInput && ( - setSearchInput(e.target.value)} - autoFocus - /> - )} - search-icon - - {channelAggsList && channelAggsList.length > 1 && ( - - )} {isGridView && ( @@ -449,6 +385,85 @@ const Download = () => { {showQueueActions && (
+

Search & Filter

+ + {channelAggsList && channelAggsList.length > 1 && ( + + )} + + setSearchInput(e.target.value)} + /> + {searchInput && } +

Bulk actions

Applied filtered by status '{showIgnoredFilter}'