diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx
index d0823107..dbfd7aa2 100644
--- a/frontend/src/components/tables/ChannelsTable.jsx
+++ b/frontend/src/components/tables/ChannelsTable.jsx
@@ -24,11 +24,11 @@ import {
ScreenShare,
Scroll,
SquareMinus,
- Pencil,
- ArrowUp,
- ArrowDown,
- ArrowUpDown,
- TvMinimalPlay,
+ CirclePlay,
+ SquarePen,
+ Binary,
+ ArrowDown01,
+ SquarePlus,
} from 'lucide-react';
import ghostImage from '../../images/ghost.svg';
import {
@@ -40,24 +40,14 @@ import {
Button,
Paper,
Flex,
- Center,
Text,
Tooltip,
Grid,
Group,
useMantineTheme,
- UnstyledButton,
+ Center,
Container,
- Space,
} from '@mantine/core';
-import {
- IconArrowDown,
- IconArrowUp,
- IconDeviceDesktopSearch,
- IconSelector,
- IconSortAscendingNumbers,
- IconSquarePlus,
-} from '@tabler/icons-react'; // Import custom icons
const ChannelStreams = ({ channel, isExpanded }) => {
const channelStreams = useChannelsStore(
@@ -142,76 +132,12 @@ const ChannelStreams = ({ channel, isExpanded }) => {
return <>>;
}
- return (
-
-
-
- );
+ return ;
};
-// /* -----------------------------------------------------------
-// 2) Custom-styled "chip" buttons for HDHR, M3U, EPG
-// ------------------------------------------------------------ */
-// const HDHRButton = styled(Button)(() => ({
-// border: '1px solid #a3d977',
-// color: '#a3d977',
-// backgroundColor: 'transparent',
-// textTransform: 'none',
-// fontSize: '0.85rem',
-// display: 'flex',
-// alignItems: 'center',
-// gap: '4px',
-// padding: '2px 8px',
-// minWidth: 'auto',
-// '&:hover': {
-// borderColor: '#c2e583',
-// color: '#c2e583',
-// backgroundColor: 'rgba(163,217,119,0.1)',
-// },
-// }));
-
-// const M3UButton = styled(Button)(() => ({
-// border: '1px solid #5f6dc6',
-// color: '#5f6dc6',
-// backgroundColor: 'transparent',
-// textTransform: 'none',
-// fontSize: '0.85rem',
-// display: 'flex',
-// alignItems: 'center',
-// gap: '4px',
-// padding: '2px 8px',
-// minWidth: 'auto',
-// '&:hover': {
-// borderColor: '#7f8de6',
-// color: '#7f8de6',
-// backgroundColor: 'rgba(95,109,198,0.1)',
-// },
-// }));
-
-// const EPGButton = styled(Button)(() => ({
-// border: '1px solid #707070',
-// color: '#a0a0a0',
-// backgroundColor: 'transparent',
-// textTransform: 'none',
-// fontSize: '0.85rem',
-// display: 'flex',
-// alignItems: 'center',
-// gap: '4px',
-// padding: '2px 8px',
-// minWidth: 'auto',
-// '&:hover': {
-// borderColor: '#a0a0a0',
-// color: '#c0c0c0',
-// backgroundColor: 'rgba(112,112,112,0.1)',
-// },
-// }));
+const m3uUrl = `${window.location.protocol}//${window.location.host}/output/m3u`;
+const epgUrl = `${window.location.protocol}//${window.location.host}/output/epg`;
+const hdhrUrl = `${window.location.protocol}//${window.location.host}/output/hdhr`;
const ChannelsTable = ({}) => {
const [channel, setChannel] = useState(null);
@@ -219,7 +145,6 @@ const ChannelsTable = ({}) => {
const [rowSelection, setRowSelection] = useState([]);
const [channelGroupOptions, setChannelGroupOptions] = useState([]);
- const [anchorEl, setAnchorEl] = useState(null);
const [textToCopy, setTextToCopy] = useState('');
const [filterValues, setFilterValues] = useState({});
@@ -252,7 +177,9 @@ const ChannelsTable = ({}) => {
}));
};
- const outputUrlRef = useRef(null);
+ const hdhrUrlRef = useRef(null);
+ const m3uUrlRef = useRef(null);
+ const epgUrlRef = useRef(null);
const {
environment: { env_mode },
@@ -269,14 +196,18 @@ const ChannelsTable = ({}) => {
{
header: 'Name',
accessorKey: 'channel_name',
- muiTableHeadCellProps: {
+ mantineTableHeadCellProps: {
sx: { textAlign: 'center' },
},
Header: ({ column }) => (
handleFilterChange(column.id, e.target.value)}
+ onChange={(e) => {
+ e.stopPropagation();
+ handleFilterChange(column.id, e.target.value);
+ }}
size="xs"
/>
),
@@ -447,12 +378,16 @@ const ChannelsTable = ({}) => {
}
}, [sorting]);
- const handleCopy = async () => {
+ const handleCopy = async (textToCopy, ref) => {
try {
await navigator.clipboard.writeText(textToCopy);
- showAlert('Copied!');
+ notifications.show({
+ title: 'Copied!',
+ // style: { width: '200px', left: '200px' },
+ });
} catch (err) {
- const inputElement = outputUrlRef.current.querySelector('input'); // Get the actual input
+ const inputElement = ref.current; // Get the actual input
+ console.log(inputElement);
if (inputElement) {
inputElement.focus();
@@ -460,28 +395,28 @@ const ChannelsTable = ({}) => {
// For older browsers
document.execCommand('copy');
- showAlert('Copied!');
+ notifications.show({ title: 'Copied!' });
}
}
};
// Example copy URLs
- const copyM3UUrl = (event) => {
- setAnchorEl(event.currentTarget);
- setTextToCopy(
- `${window.location.protocol}//${window.location.host}/output/m3u`
+ const copyM3UUrl = () => {
+ handleCopy(
+ `${window.location.protocol}//${window.location.host}/output/m3u`,
+ m3uUrlRef
);
};
- const copyEPGUrl = (event) => {
- setAnchorEl(event.currentTarget);
- setTextToCopy(
- `${window.location.protocol}//${window.location.host}/output/epg`
+ const copyEPGUrl = () => {
+ handleCopy(
+ `${window.location.protocol}//${window.location.host}/output/epg`,
+ epgUrlRef
);
};
- const copyHDHRUrl = (event) => {
- setAnchorEl(event.currentTarget);
- setTextToCopy(
- `${window.location.protocol}//${window.location.host}/output/hdhr`
+ const copyHDHRUrl = () => {
+ handleCopy(
+ `${window.location.protocol}//${window.location.host}/output/hdhr`,
+ hdhrUrlRef
);
};
@@ -509,11 +444,6 @@ const ChannelsTable = ({}) => {
enableRowVirtualization: true,
enableRowSelection: true,
renderTopToolbar: false,
- icons: {
- IconSortAscending: IconArrowUp, // Upward arrow for ascending sort
- IconSortDescending: IconArrowDown, // Downward arrow for descending sort
- IconSort: IconSelector, // Default sort icon (unsorted state)
- },
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
state: {
@@ -535,23 +465,18 @@ const ChannelsTable = ({}) => {
'mrt-row-expand': {
size: 10,
header: '',
- muiTableHeadCellProps: {
- sx: { width: 38, minWidth: 38, maxWidth: 38, height: '100%' },
- },
- muiTableBodyCellProps: {
- sx: { width: 38, minWidth: 38, maxWidth: 38 },
- },
},
'mrt-row-actions': {
size: 74,
},
},
- muiExpandButtonProps: ({ row, table }) => ({
+ mantineExpandButtonProps: ({ row, table }) => ({
onClick: () => {
setRowSelection({ [row.index]: true });
table.setExpanded({ [row.id]: !row.getIsExpanded() });
},
- sx: {
+ size: 'xs',
+ style: {
transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)',
transition: 'transform 0.2s',
},
@@ -561,40 +486,42 @@ const ChannelsTable = ({}) => {
),
renderRowActions: ({ row }) => (
-
- {
- editChannel(row.original);
- }}
- >
-
-
-
+
+
+ {
+ editChannel(row.original);
+ }}
+ >
+
+
+
-
- deleteChannel(row.original.id)}
- >
-
-
-
+
+ deleteChannel(row.original.id)}
+ >
+
+
+
-
- handleWatchStream(row.original.channel_number)}
- >
-
-
-
+
+ handleWatchStream(row.original.channel_number)}
+ >
+
+
+
+
),
mantineTableContainerProps: {
@@ -603,9 +530,6 @@ const ChannelsTable = ({}) => {
overflowY: 'auto',
},
},
- muiSearchTextFieldProps: {
- variant: 'standard',
- },
});
return (
@@ -658,12 +582,10 @@ const ChannelsTable = ({}) => {
}
size="compact-sm"
- onClick={copyHDHRUrl}
p={5}
color="green"
variant="subtle"
style={{
- borderWidth: 1,
borderColor: theme.palette.custom.greenMain,
color: theme.palette.custom.greenMain,
}}
@@ -673,11 +595,12 @@ const ChannelsTable = ({}) => {
-
+
@@ -690,12 +613,9 @@ const ChannelsTable = ({}) => {
}
size="compact-sm"
- onClick={copyM3UUrl}
p={5}
- color="green"
variant="subtle"
style={{
- borderWidth: 1,
borderColor: theme.palette.custom.indigoMain,
color: theme.palette.custom.indigoMain,
}}
@@ -705,11 +625,12 @@ const ChannelsTable = ({}) => {
-
+
@@ -722,12 +643,10 @@ const ChannelsTable = ({}) => {
}
size="compact-sm"
- onClick={copyEPGUrl}
p={5}
- color="green"
variant="subtle"
+ color="gray.5"
style={{
- borderWidth: 1,
borderColor: theme.palette.custom.greyBorder,
color: theme.palette.custom.greyBorder,
}}
@@ -737,11 +656,12 @@ const ChannelsTable = ({}) => {
-
+
@@ -775,20 +695,18 @@ const ChannelsTable = ({}) => {
}}
>
-
- }
- variant="default"
- size="xs"
- onClick={deleteChannels}
- >
- Remove
-
-
+ }
+ variant="default"
+ size="xs"
+ onClick={deleteChannels}
+ >
+ Remove
+
}
+ leftSection={}
variant="default"
size="xs"
onClick={assignChannels}
@@ -800,7 +718,7 @@ const ChannelsTable = ({}) => {
}
+ leftSection={}
variant="default"
size="xs"
onClick={matchEpg}
@@ -810,115 +728,104 @@ const ChannelsTable = ({}) => {
-
- }
- variant="light"
- size="xs"
- onClick={() => editChannel()}
- p={5}
- color="green"
- style={{
- borderWidth: '1px',
- borderColor: 'green',
- color: 'white',
- }}
- >
- Add
-
-
+ }
+ variant="light"
+ size="xs"
+ onClick={() => editChannel()}
+ p={5}
+ color="green"
+ style={{
+ borderWidth: '1px',
+ borderColor: 'green',
+ color: 'white',
+ }}
+ >
+ Add
+
{/* Table or ghost empty state inside Paper */}
-
+
{filteredData.length === 0 && (
-
-
-
-
+
- It’s recommended to create channels after adding your M3U or
- streams.
-
-
+ It’s recommended to create channels after adding your M3U or
+ streams.
+
+
+ You can still create channels without streams if you’d like,
+ and map them later.
+
+ }
+ variant="light"
+ size="xs"
+ onClick={() => editChannel()}
+ color="gray"
+ style={{
+ marginTop: 20,
+ borderWidth: '1px',
+ borderColor: 'gray',
+ color: 'white',
+ }}
+ >
+ Create Channel
+
+
+
+
+
+
- You can still create channels without streams if you’d like,
- and map them later.
-
- }
- variant="light"
- size="xs"
- onClick={() => editChannel()}
- color="gray"
- style={{
- marginTop: 20,
- borderWidth: '1px',
- borderColor: 'gray',
- color: 'white',
- }}
- >
- Create Channel
-
-
-
+ />
+
+
)}
- {filteredData.length > 0 && (
-
-
-
- )}
+ {filteredData.length > 0 && }
{
const [groupOptions, setGroupOptions] = useState([]);
const [m3uOptions, setM3uOptions] = useState([]);
const [actionsOpenRow, setActionsOpenRow] = useState(null);
- const [dataFetched, setDataFetched] = useState(false);
+ const [initialDataCount, setInitialDataCount] = useState(null);
const [data, setData] = useState([]); // Holds fetched data
const [rowCount, setRowCount] = useState(0);
+ const [pageCount, setPageCount] = useState(0);
+ const [paginationString, setPaginationString] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [sorting, setSorting] = useState([]);
const [selectedStreamIds, setSelectedStreamIds] = useState([]);
@@ -63,7 +69,7 @@ const StreamsTable = ({}) => {
// const [allRowsSelected, setAllRowsSelected] = useState(false);
const [pagination, setPagination] = useState({
pageIndex: 0,
- pageSize: 25,
+ pageSize: 250,
});
const [filters, setFilters] = useState({
name: '',
@@ -112,7 +118,6 @@ const StreamsTable = ({}) => {
onClick={(e) => e.stopPropagation()}
onChange={handleFilterChange}
size="xs"
- margin="none"
/>
),
Cell: ({ cell }) => (
@@ -230,6 +235,21 @@ const StreamsTable = ({}) => {
const result = await API.queryStreams(params);
setData(result.results);
setRowCount(result.count);
+ setPageCount(Math.ceil(result.count / pagination.pageSize));
+
+ // Calculate the starting and ending item indexes
+ const startItem = pagination.pageIndex * pagination.pageSize + 1; // +1 to start from 1, not 0
+ const endItem = Math.min(
+ (pagination.pageIndex + 1) * pagination.pageSize,
+ result.count
+ );
+
+ if (initialDataCount === null) {
+ setInitialDataCount(result.count);
+ }
+
+ // Generate the string
+ setPaginationString(`${startItem} to ${endItem} of ${result.count}`);
const newSelection = {};
result.results.forEach((item, index) => {
@@ -250,9 +270,6 @@ const StreamsTable = ({}) => {
setGroupOptions(groups);
setIsLoading(false);
- if (dataFetched === false) {
- setDataFetched(true);
- }
}, [pagination, sorting, debouncedFilters]);
// Fallback: Individual creation (optional)
@@ -374,17 +391,21 @@ const StreamsTable = ({}) => {
setRowSelection(newSelection);
};
- const onPageSizeChange = (pageSize) => {
+ const onPageSizeChange = (e) => {
setPagination({
...pagination,
- pageSize,
+ pageSize: e.target.value,
});
};
const onPageIndexChange = (pageIndex) => {
+ if (!pageIndex || pageIndex > pageCount) {
+ return;
+ }
+
setPagination({
...pagination,
- pageIndex,
+ pageIndex: pageIndex - 1,
});
};
@@ -409,8 +430,33 @@ const StreamsTable = ({}) => {
rowVirtualizerInstanceRef,
rowVirtualizerOptions: { overscan: 5 }, //optionally customize the row virtualizer
enableBottomToolbar: true,
+ renderBottomToolbar: ({ table }) => (
+
+ Page Size
+
+
+ {paginationString}
+
+ ),
enableStickyHeader: true,
- onPaginationChange: onPaginationChange,
+ // onPaginationChange: onPaginationChange,
rowCount: rowCount,
enableRowSelection: true,
mantineSelectAllCheckboxProps: {
@@ -433,7 +479,7 @@ const StreamsTable = ({}) => {
state: {
isLoading: isLoading,
sorting,
- pagination,
+ // pagination,
rowSelection,
},
enableRowActions: true,
@@ -498,7 +544,7 @@ const StreamsTable = ({}) => {
),
mantineTableContainerProps: {
style: {
- height: 'calc(100vh - 180px)',
+ height: 'calc(100vh - 167px)',
overflowY: 'auto',
},
},
@@ -537,7 +583,7 @@ const StreamsTable = ({}) => {
return (
<>
{
- {!dataFetched && (
+ {initialDataCount === 0 && (
{
)}
- {dataFetched && }
+ {initialDataCount > 0 && }