diff --git a/apps/channels/migrations/0017_alter_channel_channel_number_alter_channelgroup_name.py b/apps/channels/migrations/0017_alter_channelgroup_name.py similarity index 52% rename from apps/channels/migrations/0017_alter_channel_channel_number_alter_channelgroup_name.py rename to apps/channels/migrations/0017_alter_channelgroup_name.py index 1bb7d2e7..03043d65 100644 --- a/apps/channels/migrations/0017_alter_channel_channel_number_alter_channelgroup_name.py +++ b/apps/channels/migrations/0017_alter_channelgroup_name.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.6 on 2025-04-19 12:08 +# Generated by Django 5.1.6 on 2025-04-21 20:47 from django.db import migrations, models @@ -10,14 +10,9 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AlterField( - model_name='channel', - name='channel_number', - field=models.IntegerField(db_index=True), - ), migrations.AlterField( model_name='channelgroup', name='name', - field=models.CharField(db_index=True, max_length=100, unique=True), + field=models.TextField(db_index=True, unique=True), ), ] diff --git a/apps/channels/models.py b/apps/channels/models.py index 249343e9..0b66c468 100644 --- a/apps/channels/models.py +++ b/apps/channels/models.py @@ -27,7 +27,7 @@ def get_total_viewers(channel_id): return 0 class ChannelGroup(models.Model): - name = models.CharField(max_length=100, unique=True, db_index=True) + name = models.TextField(unique=True, db_index=True) def related_channels(self): # local import if needed to avoid cyc. Usually fine in a single file though diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 1bb276d3..8568b52c 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -58,18 +58,12 @@ import { UnstyledButton, CopyButton, } from '@mantine/core'; -import { - useReactTable, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - getFilteredRowModel, - flexRender, -} from '@tanstack/react-table'; +import { getCoreRowModel, flexRender } from '@tanstack/react-table'; import './table.css'; import useChannelsTableStore from '../../store/channelsTable'; import ChannelTableStreams from './ChannelTableStreams'; import useLocalStorage from '../../hooks/useLocalStorage'; +import { CustomTable, useTable } from './CustomTable'; const m3uUrlBase = `${window.location.protocol}//${window.location.host}/output/m3u`; const epgUrlBase = `${window.location.protocol}//${window.location.host}/output/epg`; @@ -292,6 +286,7 @@ const ChannelsTable = ({}) => { const env_mode = useSettingsStore((s) => s.environment.env_mode); + const [allRowIds, setAllRowIds] = useState([]); const [channel, setChannel] = useState(null); const [channelModalOpen, setChannelModalOpen] = useState(false); const [recordingModalOpen, setRecordingModalOpen] = useState(false); @@ -299,12 +294,9 @@ const ChannelsTable = ({}) => { const [selectedProfile, setSelectedProfile] = useState( profiles[selectedProfileId] ); + const pagination = useChannelsTableStore((s) => s.pagination); + const setPagination = useChannelsTableStore((s) => s.setPagination); const [paginationString, setPaginationString] = useState(''); - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: tablePrefs.pageSize, - }); - const [initialDataCount, setInitialDataCount] = useState(null); const [filters, setFilters] = useState({ name: '', channel_group: '', @@ -312,10 +304,9 @@ const ChannelsTable = ({}) => { const debouncedFilters = useDebounce(filters, 500); const [isLoading, setIsLoading] = useState(true); const [selectedChannelIds, setSelectedChannelIds] = useState([]); - const [sorting, setSorting] = useState([ - { id: 'channel_number', desc: false }, - ]); - const [expandedRowId, setExpandedRowId] = useState(null); + const sorting = useChannelsTableStore((s) => s.sorting); + const setSorting = useChannelsTableStore((s) => s.setSorting); + const [expandedRowIds, setExpandedRowIds] = useState([]); const [hdhrUrl, setHDHRUrl] = useState(hdhrUrlBase); const [epgUrl, setEPGUrl] = useState(epgUrlBase); @@ -339,6 +330,7 @@ const ChannelsTable = ({}) => { }); const results = await API.queryChannels(params); + const ids = await API.getAllChannelIds(params); const startItem = pagination.pageIndex * pagination.pageSize + 1; // +1 to start from 1, not 0 const endItem = Math.min( @@ -346,15 +338,12 @@ const ChannelsTable = ({}) => { results.count ); - if (initialDataCount === null) { - setInitialDataCount(results.count); - } - // Generate the string setPaginationString(`${startItem} to ${endItem} of ${results.count}`); setTablePrefs({ pageSize: pagination.pageSize, }); + setAllRowIds(ids); }, [pagination, sorting, debouncedFilters]); useEffect(() => { @@ -386,10 +375,6 @@ const ChannelsTable = ({}) => { ...prev, [name]: value, })); - setPagination({ - pageIndex: 0, - pageSize: pagination.pageSize, - }); }, []); const handleGroupChange = (value) => { @@ -397,10 +382,6 @@ const ChannelsTable = ({}) => { ...prev, channel_group: value ? value : '', })); - setPagination({ - pageIndex: 0, - pageSize: pagination.pageSize, - }); }; const hdhrUrlRef = useRef(null); @@ -440,49 +421,49 @@ const ChannelsTable = ({}) => { showVideo(getChannelURL(channel)); } - const onRowSelectionChange = (updater) => { - setRowSelection((prevRowSelection) => { - const newRowSelection = - typeof updater === 'function' ? updater(prevRowSelection) : updater; + // const onRowSelectionChange = (updater) => { + // setRowSelection((prevRowSelection) => { + // const newRowSelection = + // typeof updater === 'function' ? updater(prevRowSelection) : updater; - const updatedSelected = new Set([...selectedChannelIds]); - getRowModel().rows.forEach((row) => { - if (newRowSelection[row.id] === undefined || !newRowSelection[row.id]) { - updatedSelected.delete(row.original.id); - } else { - updatedSelected.add(row.original.id); - } - }); - const newSelection = [...updatedSelected]; - setSelectedChannelIds(newSelection); - setSelectedTableIds(newSelection); + // const updatedSelected = new Set([...selectedChannelIds]); + // getRowModel().rows.forEach((row) => { + // if (newRowSelection[row.id] === undefined || !newRowSelection[row.id]) { + // updatedSelected.delete(row.original.id); + // } else { + // updatedSelected.add(row.original.id); + // } + // }); + // const newSelection = [...updatedSelected]; + // setSelectedChannelIds(newSelection); + // setSelectedTableIds(newSelection); - return newRowSelection; - }); - }; + // return newRowSelection; + // }); + // }; - const onSelectAllChange = async (e) => { - const selectAll = e.target.checked; - if (selectAll) { - // Get all channel IDs for current view - const params = new URLSearchParams(); - Object.entries(debouncedFilters).forEach(([key, value]) => { - if (value) params.append(key, value); - }); - const ids = await API.getAllChannelIds(params); - setSelectedTableIds(ids); - setSelectedChannelIds(ids); - } else { - setSelectedTableIds([]); - setSelectedChannelIds([]); - } + // const onSelectAllChange = async (e) => { + // const selectAll = e.target.checked; + // if (selectAll) { + // // Get all channel IDs for current view + // const params = new URLSearchParams(); + // Object.entries(debouncedFilters).forEach(([key, value]) => { + // if (value) params.append(key, value); + // }); + // const ids = await API.getAllChannelIds(params); + // setSelectedTableIds(ids); + // setSelectedChannelIds(ids); + // } else { + // setSelectedTableIds([]); + // setSelectedChannelIds([]); + // } - const newSelection = {}; - getRowModel().rows.forEach((item, index) => { - newSelection[index] = selectAll; - }); - setRowSelection(newSelection); - }; + // const newSelection = {}; + // getRowModel().rows.forEach((item, index) => { + // newSelection[index] = selectAll; + // }); + // setRowSelection(newSelection); + // }; const onPageSizeChange = (e) => { setPagination({ @@ -798,50 +779,9 @@ const ChannelsTable = ({}) => { enableSorting: false, }, ], - [selectedProfileId, data] + [selectedProfileId, data, channelGroups] ); - const { getHeaderGroups, getRowModel } = useReactTable({ - data, - columns: columns, - defaultColumn: { - size: undefined, - minSize: 0, - }, - pageCount, - state: { - data, - rowCount, - sorting, - filters, - pagination, - rowSelection, - }, - manualPagination: true, - manualSorting: true, - manualFiltering: true, - enableRowSelection: true, - onRowSelectionChange: onRowSelectionChange, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - // debugTable: true, - }); - - const rows = getRowModel().rows; - - const onRowExpansion = (row) => { - let isExpanded = false; - setExpandedRowId((prev) => { - isExpanded = prev === row.original.id ? null : row.original.id; - return isExpanded; - }); - setRowSelection({ [row.index]: true }); - setSelectedChannelIds([row.original.id]); - setSelectedTableIds([row.original.id]); - }; - const renderHeaderCell = (header) => { let sortingIcon = ArrowUpDown; if (sorting[0]?.id == header.id) { @@ -853,11 +793,6 @@ const ChannelsTable = ({}) => { } switch (header.id) { - case 'select': - return ChannelRowSelectHeader({ - selectedChannelIds, - }); - case 'enabled': if (selectedProfileId !== '0' && selectedChannelIds.length > 0) { // return EnabledHeaderSwitch(); @@ -923,22 +858,85 @@ const ChannelsTable = ({}) => { } }; - const renderBodyCell = (cell) => { - switch (cell.column.id) { - case 'select': - return ChannelRowSelectCell({ row: cell.row }); + const table = useTable({ + data, + columns, + allRowIds, + defaultColumn: { + size: undefined, + minSize: 0, + }, + pageCount, + // state: { + // data, + // rowCount, + // sorting, + // filters, + // pagination, + // rowSelection, + // }, + filters, + pagination, + sorting, + expandedRowIds, + manualPagination: true, + manualSorting: true, + manualFiltering: true, + enableRowSelection: true, + // onRowSelectionChange: onRowSelectionChange, + getCoreRowModel: getCoreRowModel(), + // getFilteredRowModel: getFilteredRowModel(), + // getSortedRowModel: getSortedRowModel(), + // getPaginationRowModel: getPaginationRowModel(), + // debugTable: true, + expandedRowRenderer: ({ row }) => { + return ( + + + + ); + }, + headerCellRenderFns: { + name: renderHeaderCell, + enabled: () => ( +
+ +
+ ), + }, + }); - case 'expand': - return ChannelExpandCell({ row: cell.row }); - - default: - return flexRender(cell.column.columnDef.cell, cell.getContext()); - } + const onRowExpansion = (row) => { + let isExpanded = false; + setExpandedRowIds((prev) => { + isExpanded = prev === row.original.id ? null : row.original.id; + return isExpanded; + }); + setRowSelection({ [row.index]: true }); + setSelectedChannelIds([row.original.id]); + setSelectedTableIds([row.original.id]); }; + // const renderBodyCell = (cell) => { + // switch (cell.column.id) { + // case 'select': + // return ChannelRowSelectCell({ row: cell.row }); + + // case 'expand': + // return ChannelExpandCell({ row: cell.row }); + + // default: + // return flexRender(cell.column.columnDef.cell, cell.getContext()); + // } + // }; + const ChannelExpandCell = useCallback( ({ row }) => { - const isExpanded = expandedRowId === row.original.id; + const isExpanded = expandedRowIds === row.original.id; return (
{
); }, - [expandedRowId] + [expandedRowIds] ); - const ChannelRowSelectCell = useCallback( - ({ row }) => { - return ( -
- -
- ); - }, - [rows] - ); + // const ChannelRowSelectCell = useCallback( + // ({ row }) => { + // return ( + //
+ // + //
+ // ); + // }, + // [rows] + // ); - const ChannelRowSelectHeader = useCallback( - ({ selectedChannelIds }) => { - return ( -
- 0 && - selectedChannelIds.length !== rowCount - } - onChange={onSelectAllChange} - /> -
- ); - }, - [rows] - ); + // const ChannelRowSelectHeader = useCallback( + // ({ selectedChannelIds }) => { + // return ( + //
+ // 0 && + // selectedChannelIds.length !== rowCount + // } + // onChange={onSelectAllChange} + // /> + //
+ // ); + // }, + // [rows] + // ); return ( @@ -1218,7 +1216,7 @@ const ChannelsTable = ({}) => { {/* Table or ghost empty state inside Paper */} - {initialDataCount === 0 && data.length === 0 && ( + {Object.keys(channels).length === 0 && ( { )} - {data.length > 0 && ( + {Object.keys(channels).length > 0 && ( { borderRadius: 'var(--mantine-radius-default)', }} > - - - {getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - const width = header.getSize(); - return ( - - - {renderHeaderCell(header)} - - - ); - })} - - ))} - - - {getRowModel().rows.map((row) => ( - - - {row.getVisibleCells().map((cell) => { - const width = cell.column.getSize(); - return ( - - - {renderBodyCell(cell)} - - - ); - })} - - {row.original.id === expandedRowId && ( - - - - )} - - ))} - - + { - const [expandedRowId, setExpandedRowId] = useState(null); - - const rows = table.getRowModel().rows; - - const ChannelExpandCell = useCallback( - ({ row }) => { - const isExpanded = expandedRowId === row.original.id; - - return ( -
{ - setExpandedRowId((prev) => - prev === row.original.id ? null : row.original.id - ); - }} - > - {isExpanded ? : } -
- ); - }, - [expandedRowId] - ); - - const ChannelRowSelectCell = useCallback( - ({ row }) => { - return ( -
- -
- ); - }, - [rows] - ); - - const bodyCellRenderer = (cell) => { - if (bodyCellRenderFns[cell.column.id]) { - return bodyCellRenderFns(cell); - } - - switch (cell.column.id) { - case 'select': - return ChannelRowSelectCell({ row: cell.row }); - - case 'expand': - return ChannelExpandCell({ row: cell.row }); - - default: - return flexRender(cell.column.columnDef.cell, cell.getContext()); - } - }; - +const CustomTable = ({ table }) => { return ( + - - {table.getRowModel().rows.map((row) => ( - - - {row.getVisibleCells().map((cell) => { - return ( - - - {bodyCellRenderer(cell)} - - - ); - })} - - {row.original.id === expandedRowId && ( - - - - )} - - ))} - ); }; diff --git a/frontend/src/components/tables/CustomTable/CustomTableBody.jsx b/frontend/src/components/tables/CustomTable/CustomTableBody.jsx new file mode 100644 index 00000000..c2a26f2d --- /dev/null +++ b/frontend/src/components/tables/CustomTable/CustomTableBody.jsx @@ -0,0 +1,65 @@ +import { Box, Flex } from '@mantine/core'; +import { flexRender } from '@tanstack/react-table'; + +const CustomTableBody = ({ + getRowModel, + bodyCellRenderFns, + expandedRowIds, + expandedRowRenderer, +}) => { + const renderExpandedRow = (row) => { + if (expandedRowRenderer) { + return expandedRowRenderer({ row }); + } + + return <>; + }; + + return ( + + {getRowModel().rows.map((row) => ( + + + {row.getVisibleCells().map((cell) => { + return ( + + + {bodyCellRenderFns[cell.column.id] + ? bodyCellRenderFns[cell.column.id](cell) + : flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + + ); + })} + + {expandedRowIds.includes(row.original.id) && renderExpandedRow(row)} + + ))} + + ); +}; + +export default CustomTableBody; diff --git a/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx b/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx index 50a173d2..7f71e04d 100644 --- a/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx +++ b/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx @@ -1,120 +1,39 @@ -import { Box, Flex } from '@mantine/core'; -import { - ArrowDownWideNarrow, - ArrowUpDown, - ArrowUpNarrowWide, -} from 'lucide-react'; +import { Box, Center, Checkbox, Flex } from '@mantine/core'; +import { flexRender } from '@tanstack/react-table'; import { useCallback } from 'react'; const CustomTableHeader = ({ - table, + getHeaderGroups, + allRowIds, + selectedTableIds, headerCellRenderFns, - rowCount, onSelectAllChange, }) => { - const ChannelRowSelectHeader = useCallback( - ({ selectedChannelIds }) => { - return ( -
- 0 && - selectedChannelIds.length !== rowCount - } - onChange={onSelectAllChange} - /> -
- ); - }, - [rows, rowCount] - ); - - const onSelectAll = (e) => { - if (onSelectAllChange) { - onSelectAllChange(e); - } - }; - - const headerCellRenderer = (header) => { - let sortingIcon = ArrowUpDown; - if (sorting[0]?.id == header.id) { - if (sorting[0].desc === false) { - sortingIcon = ArrowUpNarrowWide; - } else { - sortingIcon = ArrowDownWideNarrow; - } + const renderHeaderCell = (header) => { + if (headerCellRenderFns[header.id]) { + return headerCellRenderFns[header.id](header); } switch (header.id) { case 'select': - return ChannelRowSelectHeader({ - selectedChannelIds, - }); - - case 'enabled': - if (selectedProfileId !== '0' && selectedChannelIds.length > 0) { - // return EnabledHeaderSwitch(); - } return (
- + 0 && + selectedTableIds.length !== allRowIds.length + } + onChange={onSelectAllChange} + />
); - // case 'channel_number': - // return ( - // - // # - // {/*
- // {React.createElement(sortingIcon, { - // onClick: () => onSortingChange('name'), - // size: 14, - // })} - //
*/} - //
- // ); - - // case 'name': - // return ( - // - // e.stopPropagation()} - // onChange={handleFilterChange} - // size="xs" - // variant="unstyled" - // className="table-input-header" - // /> - //
- // {React.createElement(sortingIcon, { - // onClick: () => onSortingChange('name'), - // size: 14, - // })} - //
- //
- // ); - - // case 'channel_group': - // return ( - // - // ); - default: return flexRender(header.column.columnDef.header, header.getContext()); } @@ -130,7 +49,7 @@ const CustomTableHeader = ({ zIndex: 10, }} > - {table.getHeaderGroups().map((headerGroup) => ( + {getHeaderGroups().map((headerGroup) => ( - {headerCellRenderer(header)} + {renderHeaderCell(header)} ); diff --git a/frontend/src/components/tables/CustomTable/index.jsx b/frontend/src/components/tables/CustomTable/index.jsx new file mode 100644 index 00000000..514938cd --- /dev/null +++ b/frontend/src/components/tables/CustomTable/index.jsx @@ -0,0 +1,202 @@ +import { Center, Checkbox } from '@mantine/core'; +import CustomTable from './CustomTable'; +import CustomTableHeader from './CustomTableHeader'; + +import { + useReactTable, + getCoreRowModel, + flexRender, +} from '@tanstack/react-table'; +import { useCallback, useMemo, useState } from 'react'; +import { ChevronDown, ChevronRight } from 'lucide-react'; + +const useTable = ({ + allRowIds, + headerCellRenderFns = {}, + filters = {}, + pagination = {}, + sorting = [], + expandedRowRenderer = () => <>, + ...options +}) => { + const [selectedTableIds, setSelectedTableIds] = useState([]); + const [expandedRowIds, setExpandedRowIds] = useState([]); + + const rowCount = allRowIds.length; + + const onRowSelectionChange = (updater) => { + const newRowSelection = + typeof updater === 'function' ? updater(rowSelection) : updater; + + const updatedSelected = new Set(selectedTableIds); + + const allChangedRowIds = new Set([ + ...Object.keys(rowSelection), + ...Object.keys(newRowSelection), + ]); + + for (const rowId of allChangedRowIds) { + const wasSelected = !!rowSelection[rowId]; + const isSelected = !!newRowSelection[rowId]; + + if (wasSelected !== isSelected) { + const row = table.getRow(rowId); + if (!row) continue; + + const originalId = row.original.id; + if (isSelected) { + updatedSelected.add(originalId); + } else { + updatedSelected.delete(originalId); + } + } + } + + setSelectedTableIds([...updatedSelected]); + }; + + const table = useReactTable({ + ...options, + state: { + data: options.data, + selectedTableIds, + }, + onRowSelectionChange, + getCoreRowModel: options.getCoreRowModel ?? getCoreRowModel(), + }); + + const selectedTableIdsSet = useMemo( + () => new Set(selectedTableIds), + [selectedTableIds] + ); + + const rowSelection = useMemo(() => { + const selection = {}; + table.getRowModel().rows.forEach((row) => { + if (selectedTableIdsSet.has(row.original.id)) { + selection[row.id] = true; + } + }); + return selection; + }, [selectedTableIdsSet, table.getRowModel().rows]); + + const onSelectAllChange = async (e) => { + const selectAll = e.target.checked; + if (selectAll) { + setSelectedTableIds(allRowIds); + } else { + setSelectedTableIds([]); + } + }; + + const rows = table.getRowModel().rows; + + const onRowExpansion = (row) => { + let isExpanded = false; + setExpandedRowIds((prev) => { + isExpanded = prev.includes(row.original.id) ? [] : [row.original.id]; + return isExpanded; + }); + setSelectedTableIds([row.original.id]); + }; + + const renderHeaderCell = useCallback( + (header) => { + if (table.headerCellRenderFns && table.headerCellRenderFns[header.id]) { + return table.headerCellRenderFns[header.id](header); + } + + switch (header.id) { + case 'select': + return ( +
+ 0 && + selectedTableIds.length !== rowCount + } + onChange={onSelectAllChange} + /> +
+ ); + + default: + return flexRender( + header.column.columnDef.header, + header.getContext() + ); + } + }, + [filters, selectedTableIds, rowCount, onSelectAllChange, sorting] + ); + + const bodyCellRenderFns = { + select: useCallback( + ({ row }) => { + return ( +
+ { + const newSet = new Set(selectedTableIds); + if (e.target.checked) { + newSet.add(row.original.id); + } else { + newSet.delete(row.original.id); + } + setSelectedTableIds([...newSet]); + }} + /> +
+ ); + }, + [rows, selectedTableIdsSet] + ), + expand: useCallback(({ row }) => { + const isExpanded = expandedRowIds.includes(row.original.id); + + return ( +
{ + onRowExpansion(row); + }} + > + {isExpanded ? : } +
+ ); + }), + }; + + // Return both the table instance and your custom methods + const tableInstance = useMemo( + () => ({ + ...table, + ...options, + sorting, + selectedTableIds, + setSelectedTableIds, + rowSelection, + allRowIds, + onSelectAllChange, + selectedTableIdsSet, + expandedRowIds, + expandedRowRenderer, + }), + [selectedTableIdsSet, expandedRowIds] + ); + + return { + ...tableInstance, + headerCellRenderFns, + renderHeaderCell, + bodyCellRenderFns, + }; +}; + +export { useTable, CustomTable, CustomTableHeader }; diff --git a/frontend/src/components/tables/table.css b/frontend/src/components/tables/table.css index c1c43f20..044c5c87 100644 --- a/frontend/src/components/tables/table.css +++ b/frontend/src/components/tables/table.css @@ -41,7 +41,7 @@ } .td { - height: 21px; + height: 28px; border-bottom: solid 1px rgb(68,68,68); } diff --git a/frontend/src/store/channelsTable b/frontend/src/store/channelsTable.jsx similarity index 72% rename from frontend/src/store/channelsTable rename to frontend/src/store/channelsTable.jsx index 2a230e84..76941e4f 100644 --- a/frontend/src/store/channelsTable +++ b/frontend/src/store/channelsTable.jsx @@ -6,7 +6,11 @@ import API from '../api'; const useChannelsTableStore = create((set, get) => ({ channels: [], count: 0, - pageCount: 0, + sorting: [{ id: 'channel_number', desc: false }], + pagination: { + pageIndex: 0, + pageCount: 50, + }, selectedChannelIds: [], queryChannels: ({ results, count }, params) => { @@ -29,6 +33,18 @@ const useChannelsTableStore = create((set, get) => ({ const channel = get().channels.find((c) => c.id === id); return channel?.streams ?? []; }, + + setPagination: (pagination) => { + set((state) => ({ + pagination, + })); + }, + + setSorting: (sorting) => { + set((state) => ({ + sorting, + })); + }, })); export default useChannelsTableStore;