diff --git a/frontend/src/App.js b/frontend/src/App.js index 334246ec..5c758891 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -98,14 +98,14 @@ const App = () => { toggleDrawer={toggleDrawer} /> - {/* Main content area, no AppBar, so no marginTop */} + {/* Main content area */} , path: '/channels' }, { label: 'M3U', icon: , path: '/m3u' }, { label: 'EPG', icon: , path: '/epg' }, - { - label: 'Stream Profiles', - icon: , - path: '/stream-profiles', - }, + { label: 'Stream Profiles', icon: , path: '/stream-profiles' }, { label: 'TV Guide', icon: , path: '/guide' }, { label: 'Settings', icon: , path: '/settings' }, ]; const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { const location = useLocation(); + const theme = useTheme(); return ( { width: open ? drawerWidth : miniDrawerWidth, overflowX: 'hidden', transition: 'width 0.3s', - backgroundColor: '#18181b', + backgroundColor: theme.palette.background.default, color: 'text.primary', display: 'flex', flexDirection: 'column', @@ -118,16 +116,18 @@ const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { borderRadius: 1, width: open ? '208px' : 'auto', transition: 'all 0.2s ease', - bgcolor: isActive ? 'rgba(21, 69, 62, 0.67)' : 'transparent', + bgcolor: isActive + ? theme.custom.sidebar.activeBackground + : 'transparent', border: isActive - ? '1px solid #14917e' + ? `1px solid ${theme.custom.sidebar.activeBorder}` : '1px solid transparent', color: 'text.primary', px: 1, py: 0.25, '&:hover': { - bgcolor: '#27272a', - border: '1px solid #3f3f46', + bgcolor: theme.custom.sidebar.hoverBackground, + border: `1px solid ${theme.custom.sidebar.hoverBorder}`, }, }} > @@ -148,9 +148,10 @@ const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { sx: { fontSize: '14px', fontWeight: 400, - color: isActive ? '##d4d4d8' : '##d4d4d8', - fontFamily: 'Inter, sans-serif', + fontFamily: theme.custom.sidebar.fontFamily, letterSpacing: '-0.3px', + // Keeping the text color as it is in your original + color: isActive ? '#d4d4d8' : '#d4d4d8', }, }} /> diff --git a/frontend/src/components/tables/ChannelsTable.js b/frontend/src/components/tables/ChannelsTable.js index e65c7514..235177ca 100644 --- a/frontend/src/components/tables/ChannelsTable.js +++ b/frontend/src/components/tables/ChannelsTable.js @@ -17,6 +17,7 @@ import { TextField, Autocomplete, InputAdornment, + Paper, } from '@mui/material'; import useChannelsStore from '../../store/channels'; import { @@ -28,6 +29,11 @@ import { ContentCopy, Tv as TvIcon, Clear as ClearIcon, + IndeterminateCheckBox, + CompareArrows, + Code, + AddBox, + Hd as HdIcon, } from '@mui/icons-material'; import API from '../../api'; import ChannelForm from '../forms/Channel'; @@ -37,6 +43,9 @@ import logo from '../../images/logo.png'; import useVideoStore from '../../store/useVideoStore'; import useSettingsStore from '../../store/settings'; import usePlaylistsStore from '../../store/playlists'; +import { Tv2, ScreenShare, Scroll } from 'lucide-react'; +import { styled, useTheme } from '@mui/material/styles'; +import ghostImage from '../../images/ghost.svg'; const ChannelStreams = ({ channel, isExpanded }) => { const channelStreams = useChannelsStore( @@ -45,12 +54,10 @@ const ChannelStreams = ({ channel, isExpanded }) => { const { playlists } = usePlaylistsStore(); const removeStream = async (stream) => { - let streamSet = new Set(channelStreams); - streamSet.delete(stream); - streamSet = Array.from(streamSet); + const newStreamList = channelStreams.filter((s) => s.id !== stream.id); await API.updateChannel({ ...channel, - stream_ids: streamSet.map((stream) => stream.id), + stream_ids: newStreamList.map((s) => s.id), }); }; @@ -109,11 +116,11 @@ const ChannelStreams = ({ channel, isExpanded }) => { renderRowActions: ({ row }) => ( <> removeStream(row.original)} > - {/* Small icon size */} + ), @@ -137,6 +144,63 @@ const ChannelStreams = ({ channel, isExpanded }) => { ); }; +/* ----------------------------------------------------------- + 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 ChannelsTable = ({}) => { const [channel, setChannel] = useState(null); const [channelModalOpen, setChannelModalOpen] = useState(false); @@ -150,6 +214,8 @@ const ChannelsTable = ({}) => { const [filterValues, setFilterValues] = useState({}); + const theme = useTheme(); + const { showVideo } = useVideoStore(); const { channels, @@ -193,7 +259,7 @@ const ChannelsTable = ({}) => { header: 'Name', accessorKey: 'channel_name', muiTableHeadCellProps: { - sx: { textAlign: 'center' }, // Center-align the header + sx: { textAlign: 'center' }, }, Header: ({ column }) => ( { fullWidth sx={ { - // '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size - // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size + // '& .MuiInputBase-root': { fontSize: '0.875rem' }, + // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // width: '200px', // Optional: Adjust width } } @@ -216,7 +282,7 @@ const ChannelsTable = ({}) => { endAdornment: ( handleFilterChange(column.id, '')} // Clear text on click + onClick={() => handleFilterChange(column.id, '')} edge="end" size="small" > @@ -254,8 +320,8 @@ const ChannelsTable = ({}) => { onClick={(e) => e.stopPropagation()} sx={{ pb: 0.8, - // '& .MuiInputBase-root': { fontSize: '0.875rem' }, // Text size - // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // Label size + // '& .MuiInputBase-root': { fontSize: '0.875rem' }, + // '& .MuiInputLabel-root': { fontSize: '0.75rem' }, // width: '200px', // Optional: Adjust width }} /> @@ -475,7 +541,7 @@ const ChannelsTable = ({}) => { sorting, rowSelection, }, - rowVirtualizerInstanceRef, // optional + rowVirtualizerInstanceRef, rowVirtualizerOptions: { overscan: 5 }, initialState: { density: 'compact', @@ -484,10 +550,10 @@ const ChannelsTable = ({}) => { enableExpandAll: false, displayColumnDefOptions: { 'mrt-row-select': { - size: 50, // Set custom width (default is ~40px) + size: 50, }, 'mrt-row-expand': { - size: 10, // Set custom width (default is ~40px) + size: 10, header: '', muiTableHeadCellProps: { sx: { width: 38, minWidth: 38, maxWidth: 38, height: '100%' }, @@ -497,13 +563,13 @@ const ChannelsTable = ({}) => { }, }, 'mrt-row-actions': { - size: 68, // Set custom width (default is ~40px) + size: 68, }, }, muiExpandButtonProps: ({ row, table }) => ({ onClick: () => { setRowSelection({ [row.index]: true }); - table.setExpanded({ [row.id]: !row.getIsExpanded() }); //only 1 detail panel open at a time + table.setExpanded({ [row.id]: !row.getIsExpanded() }); }, sx: { transform: row.getIsExpanded() ? 'rotate(180deg)' : 'rotate(-90deg)', @@ -629,42 +695,445 @@ const ChannelsTable = ({}) => { return ( - + {/* Header Row: outside the Paper */} + + + Channels + + + + Links: + + + + {/* HDHR Button */} + + + {/* M3U Button */} + + + {/* EPG Button */} + + + + + {/* Paper container: contains top toolbar and table (or ghost state) */} + + {/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */} + + + + + + + + + + + + + + + + + + {/* Table or ghost empty state inside Paper */} + + {filteredData.length === 0 ? ( + + + + + 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. + + + + + ) : ( + + + + )} + + - {/* Channel Form Modal */} - {/* Popover for the "copy" URLs */} -
+ -
+
- {/* Snackbar for feedback */} + + + + + + + + + + diff --git a/frontend/src/pages/Channels.js b/frontend/src/pages/Channels.js index b4c00028..73eccb9a 100644 --- a/frontend/src/pages/Channels.js +++ b/frontend/src/pages/Channels.js @@ -10,10 +10,10 @@ const ChannelsPage = () => { {