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 = ({}) => { - + - - - + {/* 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. + + + + + +
+ - You can still create channels without streams if you’d like, - and map them later. - - - - + /> +
+
)}
- {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 && }