Add TVG-ID column with filter and sort to Streams table

Changes:

- frontend/src/components/tables/StreamsTable.jsx

Added TVG-ID column to streams table
Added search filter input in column header
Added sort button for ascending/descending order
Column follows same pattern as Name, Group, and M3U columns

- apps/channels/api_views.py

Added TVG-ID to filterable fields
Added TVG-ID to sortable fields
Enables backend support for search and sorting

Users can now view, search, and sort streams by their TVG-ID directly in the Streams table. Fulfills #866
This commit is contained in:
None 2026-01-27 22:16:33 -06:00
parent bdcc865b9d
commit f8ec970561
2 changed files with 51 additions and 1 deletions

View file

@ -106,6 +106,7 @@ class StreamFilter(django_filters.FilterSet):
m3u_account_is_active = django_filters.BooleanFilter(
field_name="m3u_account__is_active"
)
tvg_id = django_filters.CharFilter(lookup_expr="icontains")
class Meta:
model = Stream
@ -115,6 +116,7 @@ class StreamFilter(django_filters.FilterSet):
"m3u_account",
"m3u_account_name",
"m3u_account_is_active",
"tvg_id",
]
@ -129,7 +131,7 @@ class StreamViewSet(viewsets.ModelViewSet):
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_class = StreamFilter
search_fields = ["name", "channel_group__name"]
ordering_fields = ["name", "channel_group__name", "m3u_account__name"]
ordering_fields = ["name", "channel_group__name", "m3u_account__name", "tvg_id"]
ordering = ["-name"]
def get_permissions(self):

View file

@ -370,6 +370,25 @@ const StreamsTable = ({ onReady }) => {
</Tooltip>
),
},
{
header: 'TVG-ID',
id: 'tvg_id',
accessorKey: 'tvg_id',
size: columnSizing.tvg_id || 120,
cell: ({ getValue }) => (
<Tooltip label={getValue()} openDelay={500}>
<Box
style={{
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{getValue()}
</Box>
</Tooltip>
),
},
],
[channelGroups, playlists, columnSizing]
);
@ -427,6 +446,7 @@ const StreamsTable = ({ onReady }) => {
name: 'name',
group: 'channel_group__name',
m3u: 'm3u_account__name',
tvg_id: 'tvg_id',
};
const sortField = fieldMapping[columnId] || columnId;
const sortDirection = sorting[0].desc ? '-' : '';
@ -967,6 +987,33 @@ const StreamsTable = ({ onReady }) => {
</Flex>
);
}
case 'tvg_id':
return (
<Flex align="center" style={{ width: '100%', flex: 1 }}>
<TextInput
name="tvg_id"
placeholder="TVG-ID"
value={filters.tvg_id || ''}
onClick={(e) => e.stopPropagation()}
onChange={handleFilterChange}
size="xs"
variant="unstyled"
className="table-input-header"
leftSection={<Search size={14} opacity={0.5} />}
style={{ flex: 1, minWidth: 0 }}
rightSectionPointerEvents="auto"
rightSection={React.createElement(sortingIcon, {
onClick: (e) => {
e.stopPropagation();
onSortingChange('tvg_id');
},
size: 14,
style: { cursor: 'pointer' },
})}
/>
</Flex>
);
}
};
@ -1019,6 +1066,7 @@ const StreamsTable = ({ onReady }) => {
name: renderHeaderCell,
group: renderHeaderCell,
m3u: renderHeaderCell,
tvg_id: renderHeaderCell,
},
bodyCellRenderFns: {
actions: renderBodyCell,