Enhancement: Status filter for M3U group and VOD category filter modals: A new **All / Enabled / Disabled** segmented control is now shown alongside the text search input in the Live, VOD - Movies, and VOD - Series tabs of the M3U Group Filter modal. The status filter works in combination with the text search and also scopes the "Select Visible" / "Deselect Visible" buttons so they only act on the currently visible subset. (Closes #312)

This commit is contained in:
SergeantPanda 2026-03-20 10:23:52 -05:00
parent 161150f0f8
commit ba0036445c
3 changed files with 58 additions and 20 deletions

View file

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Status filter for M3U group and VOD category filter modals: A new **All / Enabled / Disabled** segmented control is now shown alongside the text search input in the Live, VOD - Movies, and VOD - Series tabs of the M3U Group Filter modal. The status filter works in combination with the text search and also scopes the "Select Visible" / "Deselect Visible" buttons so they only act on the currently visible subset. (Closes #312)
## [0.21.1] - 2026-03-18
### Fixed

View file

@ -19,6 +19,7 @@ import {
Popover,
ScrollArea,
Center,
SegmentedControl,
} from '@mantine/core';
import { Info } from 'lucide-react';
import useChannelsStore from '../../store/channels';
@ -54,6 +55,7 @@ const LiveGroupFilter = ({
const streamProfiles = useStreamProfilesStore((s) => s.profiles);
const fetchStreamProfiles = useStreamProfilesStore((s) => s.fetchProfiles);
const [groupFilter, setGroupFilter] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [epgSources, setEpgSources] = useState([]);
// Logo selection functionality
@ -177,13 +179,22 @@ const LiveGroupFilter = ({
setCurrentEditingGroupId(null);
};
const isVisible = (group) => {
const matchesText = group.name
.toLowerCase()
.includes(groupFilter.toLowerCase());
const matchesStatus =
statusFilter === 'all' ||
(statusFilter === 'enabled' && group.enabled) ||
(statusFilter === 'disabled' && !group.enabled);
return matchesText && matchesStatus;
};
const selectAll = () => {
setGroupStates(
groupStates.map((state) => ({
...state,
enabled: state.name.toLowerCase().includes(groupFilter.toLowerCase())
? true
: state.enabled,
enabled: isVisible(state) ? true : state.enabled,
}))
);
};
@ -192,9 +203,7 @@ const LiveGroupFilter = ({
setGroupStates(
groupStates.map((state) => ({
...state,
enabled: state.name.toLowerCase().includes(groupFilter.toLowerCase())
? false
: state.enabled,
enabled: isVisible(state) ? false : state.enabled,
}))
);
};
@ -220,7 +229,7 @@ const LiveGroupFilter = ({
description="When disabled, new groups from the M3U source will be created but disabled by default. You can enable them manually later."
/>
<Flex gap="sm">
<Flex gap="sm" align="center">
<TextInput
placeholder="Filter groups..."
value={groupFilter}
@ -228,6 +237,16 @@ const LiveGroupFilter = ({
style={{ flex: 1 }}
size="xs"
/>
<SegmentedControl
value={statusFilter}
onChange={setStatusFilter}
size="xs"
data={[
{ label: 'All', value: 'all' },
{ label: 'Enabled', value: 'enabled' },
{ label: 'Disabled', value: 'disabled' },
]}
/>
<Button variant="default" size="xs" onClick={selectAll}>
Select Visible
</Button>
@ -245,9 +264,7 @@ const LiveGroupFilter = ({
verticalSpacing="xs"
>
{groupStates
.filter((group) =>
group.name.toLowerCase().includes(groupFilter.toLowerCase())
)
.filter((group) => isVisible(group))
.sort((a, b) => a.name.localeCompare(b.name))
.map((group) => (
<Group

View file

@ -11,6 +11,7 @@ import {
Divider,
Box,
Checkbox,
SegmentedControl,
} from '@mantine/core';
import { CircleCheck, CircleX } from 'lucide-react';
import useVODStore from '../../store/useVODStore';
@ -25,6 +26,7 @@ const VODCategoryFilter = ({
}) => {
const categories = useVODStore((s) => s.categories);
const [filter, setFilter] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
useEffect(() => {
if (Object.keys(categories).length === 0) {
@ -64,13 +66,22 @@ const VODCategoryFilter = ({
);
};
const isVisible = (category) => {
const matchesText = category.name
.toLowerCase()
.includes(filter.toLowerCase());
const matchesStatus =
statusFilter === 'all' ||
(statusFilter === 'enabled' && category.enabled) ||
(statusFilter === 'disabled' && !category.enabled);
return matchesText && matchesStatus;
};
const selectAll = () => {
setCategoryStates(
categoryStates.map((state) => ({
...state,
enabled: state.name.toLowerCase().includes(filter.toLowerCase())
? true
: state.enabled,
enabled: isVisible(state) ? true : state.enabled,
}))
);
};
@ -79,9 +90,7 @@ const VODCategoryFilter = ({
setCategoryStates(
categoryStates.map((state) => ({
...state,
enabled: state.name.toLowerCase().includes(filter.toLowerCase())
? false
: state.enabled,
enabled: isVisible(state) ? false : state.enabled,
}))
);
};
@ -98,7 +107,7 @@ const VODCategoryFilter = ({
description="When disabled, new categories from the provider will be created but disabled by default. You can enable them manually later."
/>
<Flex gap="sm">
<Flex gap="sm" align="center">
<TextInput
placeholder="Filter categories..."
value={filter}
@ -106,6 +115,16 @@ const VODCategoryFilter = ({
style={{ flex: 1 }}
size="xs"
/>
<SegmentedControl
value={statusFilter}
onChange={setStatusFilter}
size="xs"
data={[
{ label: 'All', value: 'all' },
{ label: 'Enabled', value: 'enabled' },
{ label: 'Disabled', value: 'disabled' },
]}
/>
<Button variant="default" size="xs" onClick={selectAll}>
Select Visible
</Button>
@ -121,9 +140,7 @@ const VODCategoryFilter = ({
verticalSpacing="xs"
>
{categoryStates
.filter((category) => {
return category.name.toLowerCase().includes(filter.toLowerCase());
})
.filter((category) => isVisible(category))
.sort((a, b) => a.name.localeCompare(b.name))
.map((category) => (
<Group