+<<<<<<< Updated upstream
)} */}
@@ -418,6 +520,15 @@ const RowActions = React.memo(({
});
const ChannelsTable = React.memo(({ }) => {
+=======
+
+
+ );
+ }
+);
+
+const ChannelsTable = React.memo(({}) => {
+>>>>>>> Stashed changes
const {
channels,
isLoading: channelsLoading,
@@ -426,7 +537,7 @@ const ChannelsTable = React.memo(({ }) => {
profiles,
selectedProfileId,
setSelectedProfileId,
- selectedProfileChannels,
+ selectedProfileChannelIds,
channelsPageSelection,
} = useChannelsStore();
@@ -451,14 +562,13 @@ const ChannelsTable = React.memo(({ }) => {
const [paginationString, setPaginationString] = useState('');
const [pagination, setPagination] = useState({
pageIndex: 0,
- pageSize: 250,
+ pageSize: 50,
});
const [groupOptions, setGroupOptions] = useState([]);
const [initialDataCount, setInitialDataCount] = useState(null);
const [filters, setFilters] = useState({
name: '',
channel_group: '',
- m3u_account: '',
});
const debouncedFilters = useDebounce(filters, 500);
const [isLoading, setIsLoading] = useState(true);
@@ -499,6 +609,13 @@ const ChannelsTable = React.memo(({ }) => {
]);
}, [data]);
+<<<<<<< Updated upstream
+=======
+ const stopPropagation = (e) => {
+ e.stopPropagation();
+ };
+
+>>>>>>> Stashed changes
const handleFilterChange = (e) => {
const { name, value } = e.target;
setFilters((prev) => ({
@@ -518,6 +635,32 @@ const ChannelsTable = React.memo(({ }) => {
const m3uUrlRef = useRef(null);
const epgUrlRef = useRef(null);
+ const editChannel = async (ch = null) => {
+ setChannel(ch);
+ setChannelModalOpen(true);
+ };
+
+ const deleteChannel = async (id) => {
+ setRowSelection([]);
+ if (channelsPageSelection.length > 0) {
+ return deleteChannels();
+ }
+ await API.deleteChannel(id);
+ };
+
+ const createRecording = (channel) => {
+ setChannel(channel);
+ setRecordingModalOpen(true);
+ };
+
+ function handleWatchStream(channelNumber) {
+ let vidUrl = `/proxy/ts/stream/${channelNumber}`;
+ if (env_mode == 'dev') {
+ vidUrl = `${window.location.protocol}//${window.location.hostname}:5656${vidUrl}`;
+ }
+ showVideo(vidUrl);
+ }
+
const fetchData = useCallback(async () => {
setIsLoading(true);
@@ -576,8 +719,8 @@ const ChannelsTable = React.memo(({ }) => {
}, [pagination, sorting, debouncedFilters]);
useEffect(() => {
- fetchData()
- }, [fetchData])
+ fetchData();
+ }, [fetchData]);
const onRowSelectionChange = (updater) => {
setRowSelection((prevRowSelection) => {
@@ -637,20 +780,21 @@ const ChannelsTable = React.memo(({ }) => {
});
};
- const toggleChannelEnabled = async (channelIds, enabled) => {
- if (channelIds.length == 1) {
- await API.updateProfileChannel(channelIds[0], selectedProfileId, enabled);
- } else {
- await API.updateProfileChannels(channelIds, selectedProfileId, enabled);
- setChannelsEnabledHeaderSwitch(enabled);
- }
- };
-
- const enabledChannelSet = useMemo(() => {
- return new Set(
- selectedProfileChannels.filter((c) => c.enabled).map((c) => c.id)
- );
- }, [selectedProfileChannels]);
+ const toggleChannelEnabled = useCallback(
+ async (channelIds, enabled) => {
+ if (channelIds.length == 1) {
+ await API.updateProfileChannel(
+ channelIds[0],
+ selectedProfileId,
+ enabled
+ );
+ } else {
+ await API.updateProfileChannels(channelIds, selectedProfileId, enabled);
+ setChannelsEnabledHeaderSwitch(enabled);
+ }
+ },
+ [selectedProfileId]
+ );
const EnabledHeaderSwitch = React.memo(({ isActive, toggle, disabled }) => (
{
selectedProfileId,
]);
+<<<<<<< Updated upstream
// Configure columns
const columns = useMemo(
() => [
@@ -848,6 +993,201 @@ const ChannelsTable = React.memo(({ }) => {
// channelsEnabledHeaderSwitch,
]
);
+=======
+ // // Configure columns
+ // const columns = useMemo(
+ // () => [
+ // {
+ // id: 'enabled',
+ // // Header: renderEnabledHeader,
+ // enableSorting: false,
+ // accessorFn: (row) => {
+ // return selectedProfileId == '0'
+ // ? true
+ // : enabledChannelSet.has(row.id);
+ // },
+ // mantineTableHeadCellProps: {
+ // align: 'right',
+ // style: {
+ // backgroundColor: '#3F3F46',
+ // width: '40px',
+ // minWidth: '40px',
+ // maxWidth: '40px',
+ // // // minWidth: '20px',
+ // // // width: '50px !important',
+ // // // justifyContent: 'center',
+ // padding: 0,
+ // // // paddingLeft: 8,
+ // // // paddingRight: 0,
+ // },
+ // },
+ // mantineTableBodyCellProps: {
+ // align: 'right',
+ // style: {
+ // width: '40px',
+ // minWidth: '40px',
+ // maxWidth: '40px',
+ // // // minWidth: '20px',
+ // // // justifyContent: 'center',
+ // // // paddingLeft: 0,
+ // // // paddingRight: 0,
+ // padding: 0,
+ // },
+ // },
+ // Cell: ({ row, cell }) => {
+ // const memoizedCellValue = useMemo(() => cell.getValue(), [cell]);
+ // const handleSwitchChange = useCallback(() => {
+ // toggleChannelEnabled([row.original.id], !memoizedCellValue);
+ // }, [memoizedCellValue, row.original.id, toggleChannelEnabled]);
+
+ // return (
+ //
+ // );
+ // },
+ // },
+ // {
+ // header: '#',
+ // size: 50,
+ // maxSize: 50,
+ // accessorKey: 'channel_number',
+ // sortingFn: (a, b, columnId) => {
+ // return (
+ // parseInt(a.original.channel_number) -
+ // parseInt(b.original.channel_number)
+ // );
+ // },
+ // mantineTableHeadCellProps: {
+ // align: 'right',
+ // // // style: {
+ // // // backgroundColor: '#3F3F46',
+ // // // // minWidth: '20px',
+ // // // // justifyContent: 'center',
+ // // // // paddingLeft: 15,
+ // // // paddingRight: 0,
+ // // // },
+ // },
+ // mantineTableBodyCellProps: {
+ // align: 'right',
+ // // // style: {
+ // // // minWidth: '20px',
+ // // // // justifyContent: 'center',
+ // // // paddingLeft: 0,
+ // // // paddingRight: 0,
+ // // // },
+ // },
+ // },
+ // {
+ // id: 'name',
+ // header: 'Name',
+ // accessorKey: 'name',
+ // Header: ({ column }) => (
+ // {
+ // e.stopPropagation();
+ // handleFilterChange(column.id, e.target.value);
+ // }}
+ // size="xs"
+ // variant="unstyled"
+ // className="table-input-header"
+ // onClick={(e) => e.stopPropagation()}
+ // />
+ // ),
+ // Cell: ({ cell }) => (
+ //
+ // {cell.getValue()}
+ //
+ // ),
+ // },
+ // {
+ // header: 'Group',
+ // accessorKey: 'channel_group.name',
+ // accessorFn: (row) => row.channel_group?.name || '',
+ // Cell: ({ cell }) => (
+ //
+ // {cell.getValue()}
+ //
+ // ),
+ // Header: ({ column }) => (
+ // e.stopPropagation()}>
+ // {
+ // handleFilterChange(column.id, value);
+ // }}
+ // data={channelGroupOptions}
+ // variant="unstyled"
+ // className="table-input-header custom-multiselect"
+ // />
+ //
+ // ),
+ // },
+ // {
+ // header: '',
+ // accessorKey: 'logo',
+ // enableSorting: false,
+ // size: 75,
+ // mantineTableBodyCellProps: {
+ // align: 'center',
+ // style: {
+ // maxWidth: '75px',
+ // },
+ // },
+ // Cell: ({ cell }) => (
+ //
+ //
+ //
+ // ),
+ // },
+ // ],
+ // [
+ // channelGroupOptions,
+ // filterValues,
+ // selectedProfile,
+ // selectedProfileChannels,
+ // rowSelection,
+ // channelsPageSelection,
+ // channelsEnabledHeaderSwitch,
+ // ]
+ // );
+>>>>>>> Stashed changes
// (Optional) bulk delete, but your endpoint is @TODO
const deleteChannels = async () => {
@@ -971,8 +1311,42 @@ const ChannelsTable = React.memo(({ }) => {
// }, [rowSelection])
useEffect(() => {
+<<<<<<< Updated upstream
fetchData();
}, [fetchData]);
+=======
+ const selectedRows = table
+ .getSelectedRowModel()
+ .rows.map((row) => row.original);
+ setChannelsPageSelection(selectedRows);
+
+ if (selectedProfileId != '0') {
+ setChannelsEnabledHeaderSwitch(
+ selectedRows.filter((row) => selectedProfileChannelIds.has(row.id))
+ .length == selectedRows.length
+ );
+ }
+ }, [rowSelection]);
+
+ const filteredData = Object.values(channels).filter((row) =>
+ columns.every(({ accessorKey }) => {
+ if (!accessorKey) {
+ return true;
+ }
+
+ const filterValue = filterValues[accessorKey];
+ const rowValue = getDescendantProp(row, accessorKey);
+
+ if (Array.isArray(filterValue) && filterValue.length != 0) {
+ return filterValue.includes(rowValue);
+ } else if (filterValue) {
+ return rowValue?.toLowerCase().includes(filterValues[accessorKey]);
+ }
+
+ return true;
+ })
+ );
+>>>>>>> Stashed changes
const deleteProfile = async (id) => {
await API.deleteChannelProfile(id);
@@ -999,6 +1373,7 @@ const ChannelsTable = React.memo(({ }) => {
);
};
+<<<<<<< Updated upstream
const editChannel = useCallback((row) => {
setChannel(row.original);
setChannelModalOpen(true);
@@ -1058,6 +1433,334 @@ const ChannelsTable = React.memo(({ }) => {
virtualizer.getTotalSize() - notUndefined(items[items.length - 1]).end
]
: [0, 0];
+=======
+ const newColumns = useMemo(
+ () => [
+ {
+ id: 'select',
+ size: 30,
+ header: ({ table }) => (
+
+ ),
+ cell: ({ row }) => (
+
+ ),
+ enableSorting: false,
+ enableColumnFilter: false,
+ },
+ {
+ id: 'enabled',
+ size: 45,
+ header: () => (
+
+
+
+ ),
+ cell: ({ row }) => (
+
+ ),
+ enableSorting: false,
+ },
+ {
+ accessorKey: 'channel_number',
+ size: 30,
+ header: () => #,
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ },
+ {
+ id: 'name',
+ accessorKey: 'name',
+ header: ({ column }) => (
+
+ ),
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ },
+ {
+ accessorFn: (row) => row.channel_group?.name || '',
+ id: 'channel_group',
+ header: () => (
+ d.channel_group?.name || ''))
+ )}
+ size="xs"
+ searchable
+ onClick={stopPropagation}
+ onChange={handleGroupChange}
+ />
+ ),
+ cell: ({ getValue }) => (
+
+ {getValue()}
+
+ ),
+ },
+ {
+ accessorKey: 'logo',
+ size: 75,
+ header: '',
+ cell: ({ getValue }) => {
+ const value = getValue();
+ const src = value?.cache_url || logo;
+ return (
+
+
+
+ );
+ },
+ enableSorting: false,
+ },
+ {
+ id: 'actions',
+ size: 75,
+ header: '',
+ cell: ({ row }) => (
+
+ ),
+ enableSorting: false,
+ },
+ ],
+ [selectedProfileId, selectedProfileChannelIds, data]
+ );
+
+ const table = useReactTable({
+ data,
+ columns: newColumns,
+ pageCount,
+ state: {
+ sorting,
+ filters,
+ pagination,
+ rowSelection,
+ },
+ manualPagination: true,
+ manualSorting: true,
+ manualFiltering: true,
+ enableRowSelection: true,
+ onPaginationChange: setPagination,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setFilters,
+ onRowSelectionChange: setRowSelection,
+ getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ debugTable: true,
+ });
+>>>>>>> Stashed changes
+
+ // const oldtable = useMantineReactTable({
+ // ...TableHelper.defaultProperties,
+ // columns,
+ // data,
+ // enablePagination: true,
+ // manualPagination: true,
+ // enableColumnActions: false,
+ // enableRowSelection: true,
+ // renderTopToolbar: false,
+ // onRowSelectionChange: onRowSelectionChange,
+ // onSortingChange: setSorting,
+ // state: {
+ // isLoading: isLoading || channelsLoading,
+ // sorting,
+ // rowSelection,
+ // },
+ // enableBottomToolbar: true,
+ // renderBottomToolbar: ({ table }) => (
+ //
+ // Page Size
+ //
+ //
+ // {paginationString}
+ //
+ // ),
+ // initialState: {
+ // density: 'compact',
+ // sorting: [
+ // {
+ // id: 'channel_number',
+ // desc: false,
+ // },
+ // ],
+ // },
+ // enableRowActions: true,
+ // enableExpandAll: false,
+ // displayColumnDefOptions: {
+ // 'mrt-row-select': {
+ // size: 10,
+ // maxSize: 10,
+ // mantineTableHeadCellProps: {
+ // align: 'right',
+ // style: {
+ // paddding: 0,
+ // // paddingLeft: 7,
+ // width: '20px',
+ // minWidth: '20px',
+ // backgroundColor: '#3F3F46',
+ // },
+ // },
+ // mantineTableBodyCellProps: {
+ // align: 'right',
+ // style: {
+ // paddingLeft: 0,
+ // width: '20px',
+ // minWidth: '20px',
+ // },
+ // },
+ // },
+ // 'mrt-row-expand': {
+ // size: 20,
+ // maxSize: 20,
+ // header: '',
+ // mantineTableHeadCellProps: {
+ // style: {
+ // padding: 0,
+ // paddingLeft: 2,
+ // width: '20px',
+ // minWidth: '20px',
+ // maxWidth: '20px',
+ // backgroundColor: '#3F3F46',
+ // },
+ // },
+ // mantineTableBodyCellProps: {
+ // style: {
+ // padding: 0,
+ // paddingLeft: 2,
+ // width: '20px',
+ // minWidth: '20px',
+ // maxWidth: '20px',
+ // },
+ // },
+ // },
+ // 'mrt-row-actions': {
+ // size: 85,
+ // maxWidth: 85,
+ // mantineTableHeadCellProps: {
+ // align: 'center',
+ // style: {
+ // minWidth: '85px',
+ // maxWidth: '85px',
+ // // paddingRight: 40,
+ // fontWeight: 'normal',
+ // color: 'rgb(207,207,207)',
+ // backgroundColor: '#3F3F46',
+ // },
+ // },
+ // mantineTableBodyCellProps: {
+ // style: {
+ // minWidth: '85px',
+ // maxWidth: '85px',
+ // paddingLeft: 0,
+ // // paddingRight: 10,
+ // },
+ // },
+ // },
+ // },
+ // mantineExpandButtonProps: ({ row, table }) => ({
+ // onClick: () => {
+ // setRowSelection({ [row.index]: true });
+ // table.setExpanded({ [row.id]: !row.getIsExpanded() });
+ // },
+ // size: 'xs',
+ // style: {
+ // transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)',
+ // transition: 'transform 0.2s',
+ // },
+ // }),
+ // renderDetailPanel: ({ row }) => (
+ //
+ // ),
+ // renderRowActions: ({ row }) => (
+ //
+ // ),
+ // mantineTableContainerProps: {
+ // style: {
+ // height: 'calc(100vh - 150px)',
+ // overflowY: 'auto',
+ // // margin: 5,
+ // },
+ // },
+ // });
return (
@@ -1200,6 +1903,30 @@ const ChannelsTable = React.memo(({ }) => {
{/* Paper container: contains top toolbar and table (or ghost state) */}
+<<<<<<< Updated upstream
+=======
+
+ {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */}
+
+
+
{/* Table or ghost empty state inside Paper */}
{initialDataCount === 0 && (
+=======
+ {initialDataCount > 0 && (
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+
+ ))}
+
+ ))}
+
+
+
+
+
+ Page Size
+
+
+ {paginationString}
+
+
+
+>>>>>>> Stashed changes
)}
diff --git a/frontend/src/store/channels.jsx b/frontend/src/store/channels.jsx
index f0987f44..fe00f655 100644
--- a/frontend/src/store/channels.jsx
+++ b/frontend/src/store/channels.jsx
@@ -10,7 +10,7 @@ const useChannelsStore = create((set, get) => ({
channelGroups: {},
profiles: {},
selectedProfileId: '0',
- selectedProfileChannels: [],
+ selectedProfileChannelIds: new Set(),
channelsPageSelection: [],
stats: {},
activeChannels: {},
@@ -97,10 +97,10 @@ const useChannelsStore = create((set, get) => ({
},
profiles,
selectedProfile: profiles[state.selectedProfileId],
- selectedProfileChannels:
+ selectedProfileChannelIds:
state.selectedProfileId == '0'
? []
- : profiles[state.selectedProfileId].channels,
+ : new Set(profiles[state.selectedProfileId].channels),
};
});
},
@@ -149,7 +149,9 @@ const useChannelsStore = create((set, get) => ({
},
profiles,
selectedProfile: profiles[state.selectedProfileId],
- selectedProfileChannels: profiles[state.selectedProfileId].channels,
+ selectedProfileChannelIds: new Set(
+ profiles[state.selectedProfileId].channels
+ ),
};
}),
@@ -256,7 +258,7 @@ const useChannelsStore = create((set, get) => ({
if (profileIds.includes(state.selectedProfileId)) {
additionalUpdates = {
selectedProfileId: '0',
- selectedProfileChannels: [],
+ selectedProfileChannelIds: new Set(),
selectedProfile: {},
};
}
@@ -276,27 +278,30 @@ const useChannelsStore = create((set, get) => ({
const profile = state.profiles[profileId];
if (!profile) return state; // Profile doesn't exist, no update needed
+ const profileChannels = new Set(state.profiles[profileId].channels);
+
+ if (enabled) {
+ channelIds.forEach((id) => {
+ profileChannels.add(id);
+ });
+ } else {
+ channelIds.forEach((id) => {
+ profileChannels.delete(id);
+ });
+ }
+
// Efficiently update only the specific channel
return {
profiles: {
...state.profiles,
[profileId]: {
...profile,
- channels: profile.channels.map((channel) =>
- channelIds.includes(channel.id)
- ? { ...channel, enabled } // Update enabled flag
- : channel
- ),
+ channels: profileChannels,
},
},
- selectedProfileChannels: state.selectedProfileChannels.map(
- (channel) => ({
- id: channel.id,
- enabled: channelIds.includes(channel.id)
- ? enabled
- : channel.enabled,
- })
- ),
+ ...(state.selectedProfileId == profileId && {
+ selectedProfileChannelIds: profileChannels,
+ }),
};
}),
@@ -306,7 +311,8 @@ const useChannelsStore = create((set, get) => ({
setSelectedProfileId: (id) =>
set((state) => ({
selectedProfileId: id,
- selectedProfileChannels: id == '0' ? [] : state.profiles[id].channels,
+ selectedProfileChannelIds:
+ id == '0' ? new Set() : new Set(state.profiles[id].channels),
})),
setChannelStats: (stats) => {