From ea38c0b4b88bac1d89c186f4d17cd9f1dde0ef6d Mon Sep 17 00:00:00 2001 From: dekzter Date: Thu, 11 Dec 2025 11:54:41 -0500 Subject: [PATCH] advanced filtering for hiding disabled channels and viewing only empty channels --- apps/channels/api_views.py | 32 ++++++----- .../src/components/tables/ChannelsTable.jsx | 16 +++++- .../ChannelsTable/ChannelTableHeader.jsx | 53 +++++++++++++++---- 3 files changed, 78 insertions(+), 23 deletions(-) diff --git a/apps/channels/api_views.py b/apps/channels/api_views.py index 4cfe9777..40063245 100644 --- a/apps/channels/api_views.py +++ b/apps/channels/api_views.py @@ -8,6 +8,7 @@ from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from django.shortcuts import get_object_or_404, get_list_or_404 from django.db import transaction +from django.db.models import Q import os, json, requests, logging from apps.accounts.permissions import ( Authenticated, @@ -419,31 +420,36 @@ class ChannelViewSet(viewsets.ModelViewSet): group_names = channel_group.split(",") qs = qs.filter(channel_group__name__in=group_names) + filters = {} + q_filters = Q() + channel_profile_id = self.request.query_params.get("channel_profile_id") show_disabled_param = self.request.query_params.get("show_disabled", None) + only_streamless = self.request.query_params.get("only_streamless", None) if channel_profile_id: try: profile_id_int = int(channel_profile_id) - # If show_disabled is present, include all memberships for that profile. - # If absent, restrict to enabled=True. + filters["channelprofilemembership__channel_profile_id"] = profile_id_int + if show_disabled_param is None: - qs = qs.filter( - channelprofilemembership__channel_profile_id=profile_id_int, - channelprofilemembership__enabled=True, - ) - else: - qs = qs.filter( - channelprofilemembership__channel_profile_id=profile_id_int - ) + filters["channelprofilemembership__enabled"] = True except (ValueError, TypeError): # Ignore invalid profile id values pass - if self.request.user.user_level < 10: - qs = qs.filter(user_level__lte=self.request.user.user_level) + if only_streamless: + q_filters &= Q(streams__isnull=True) - return qs + if self.request.user.user_level < 10: + filters["user_level__lte"] = self.request.user.user_level + + if filters: + qs = qs.filter(**filters) + if q_filters: + qs = qs.filter(q_filters) + + return qs.distinct() def get_serializer_context(self): context = super().get_serializer_context() diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 949b9760..7f82140f 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -294,6 +294,8 @@ const ChannelsTable = ({}) => { profiles[selectedProfileId] ); const [showDisabled, setShowDisabled] = useState(true); + const [showOnlyStreamlessChannels, setShowOnlyStreamlessChannels] = + useState(false); const [paginationString, setPaginationString] = useState(''); const [filters, setFilters] = useState({ @@ -380,6 +382,9 @@ const ChannelsTable = ({}) => { if (showDisabled === true) { params.append('show_disabled', true); } + if (showOnlyStreamlessChannels === true) { + params.append('only_streamless', true); + } // Apply sorting if (sorting.length > 0) { @@ -412,7 +417,14 @@ const ChannelsTable = ({}) => { pageSize: pagination.pageSize, }); setAllRowIds(ids); - }, [pagination, sorting, debouncedFilters, showDisabled, selectedProfileId]); + }, [ + pagination, + sorting, + debouncedFilters, + showDisabled, + selectedProfileId, + showOnlyStreamlessChannels, + ]); const stopPropagation = useCallback((e) => { e.stopPropagation(); @@ -1340,6 +1352,8 @@ const ChannelsTable = ({}) => { table={table} showDisabled={showDisabled} setShowDisabled={setShowDisabled} + showOnlyStreamlessChannels={showOnlyStreamlessChannels} + setShowOnlyStreamlessChannels={setShowOnlyStreamlessChannels} /> {/* Table or ghost empty state inside Paper */} diff --git a/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx b/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx index d3376b4d..460ab12a 100644 --- a/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx +++ b/frontend/src/components/tables/ChannelsTable/ChannelTableHeader.jsx @@ -12,15 +12,12 @@ import { Text, TextInput, Tooltip, - UnstyledButton, useMantineTheme, } from '@mantine/core'; import { ArrowDown01, Binary, - Check, CircleCheck, - Ellipsis, EllipsisVertical, SquareMinus, SquarePen, @@ -28,6 +25,9 @@ import { Settings, Eye, EyeOff, + Filter, + Square, + SquareCheck, } from 'lucide-react'; import API from '../../../api'; import { notifications } from '@mantine/notifications'; @@ -106,6 +106,8 @@ const ChannelTableHeader = ({ selectedTableIds, showDisabled, setShowDisabled, + showOnlyStreamlessChannels, + setShowOnlyStreamlessChannels, }) => { const theme = useMantineTheme(); @@ -216,6 +218,10 @@ const ChannelTableHeader = ({ setShowDisabled(!showDisabled); }; + const toggleShowOnlyStreamlessChannels = () => { + setShowOnlyStreamlessChannels(!showOnlyStreamlessChannels); + }; + return ( @@ -234,12 +240,6 @@ const ChannelTableHeader = ({ - - - - + + + + + + + : + } + disabled={selectedProfileId === '0'} + > + + {showDisabled ? 'Hide Disabled' : 'Show Disabled'} + + + + + ) : ( + + ) + } + > + Only Empty Channels + + + +