diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx index 313d2b4b..d216d44a 100644 --- a/frontend/src/components/tables/ChannelsTable.jsx +++ b/frontend/src/components/tables/ChannelsTable.jsx @@ -306,6 +306,9 @@ const ChannelsTable = ({}) => { const [isBulkDelete, setIsBulkDelete] = useState(false); const [channelToDelete, setChannelToDelete] = useState(null); + // Column sizing state for resizable columns + const [columnSizing, setColumnSizing] = useLocalStorage('channels-table-column-sizing', {}); + // M3U and EPG URL configuration state const [m3uParams, setM3uParams] = useState({ cachedlogos: true, @@ -697,14 +700,17 @@ const ChannelsTable = ({}) => { { id: 'expand', size: 20, + enableResizing: false, }, { id: 'select', size: 30, + enableResizing: false, }, { id: 'enabled', size: 45, + enableResizing: false, cell: ({ row, table }) => { return ( { id: 'channel_number', accessorKey: 'channel_number', size: 40, + minSize: 30, + maxSize: 100, cell: ({ getValue }) => { const value = getValue(); // Format as integer if no decimal component @@ -739,6 +747,7 @@ const ChannelsTable = ({}) => { { id: 'name', accessorKey: 'name', + minSize: 100, cell: ({ getValue }) => ( { ); }, size: 120, + minSize: 80, + maxSize: 200, }, { id: 'channel_group', @@ -813,6 +824,8 @@ const ChannelsTable = ({}) => { ), size: 175, + minSize: 100, + maxSize: 300, }, { id: 'logo', @@ -821,6 +834,9 @@ const ChannelsTable = ({}) => { return row.logo_id; }, size: 75, + minSize: 50, + maxSize: 120, + enableResizing: false, header: '', cell: ({ getValue }) => { const logoId = getValue(); @@ -839,6 +855,7 @@ const ChannelsTable = ({}) => { { id: 'actions', size: tableSize == 'compact' ? 75 : 100, + enableResizing: false, header: '', cell: ({ row }) => ( { manualFiltering: true, enableRowSelection: true, onRowSelectionChange: onRowSelectionChange, + state: { + columnSizing, + }, + onColumnSizingChange: setColumnSizing, getExpandedRowHeight: (row) => { return 20 + 28 * row.original.streams.length; }, diff --git a/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx b/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx index 1e32e258..17ceecd7 100644 --- a/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx +++ b/frontend/src/components/tables/CustomTable/CustomTableHeader.jsx @@ -67,6 +67,7 @@ const CustomTableHeader = ({ ? header.getSize() : undefined, minWidth: 0, + position: 'relative', // ...(tableCellProps && tableCellProps({ cell: header })), }} > @@ -80,6 +81,40 @@ const CustomTableHeader = ({ > {renderHeaderCell(header)} + {header.column.getCanResize() && ( + { + e.target.style.opacity = '1'; + e.target.style.backgroundColor = '#6b7280'; + }} + onMouseLeave={(e) => { + if (!header.column.getIsResizing()) { + e.target.style.opacity = '0.5'; + e.target.style.backgroundColor = 'transparent'; + } + }} + /> + )} ); })} diff --git a/frontend/src/components/tables/CustomTable/index.jsx b/frontend/src/components/tables/CustomTable/index.jsx index 3be2dc2f..3a1ab69b 100644 --- a/frontend/src/components/tables/CustomTable/index.jsx +++ b/frontend/src/components/tables/CustomTable/index.jsx @@ -92,8 +92,11 @@ const useTable = ({ state: { data: options.data, selectedTableIds, + columnSizing: options.state?.columnSizing || {}, }, getCoreRowModel: options.getCoreRowModel ?? getCoreRowModel(), + enableColumnResizing: true, + columnResizeMode: 'onChange', }); const selectedTableIdsSet = useMemo( diff --git a/frontend/src/components/tables/table.css b/frontend/src/components/tables/table.css index 5392d1f8..15304103 100644 --- a/frontend/src/components/tables/table.css +++ b/frontend/src/components/tables/table.css @@ -180,3 +180,35 @@ html { -ms-user-select: text !important; cursor: text !important; } + +/* Column resize handle styles */ +.resizer { + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 5px; + cursor: col-resize; + user-select: none; + touch-action: none; + background-color: transparent; + opacity: 0; + transition: all 0.2s ease; +} + +.resizer:hover { + opacity: 1 !important; + background-color: #6b7280 !important; +} + +.resizer.isResizing { + opacity: 1 !important; + background-color: #3b82f6 !important; + width: 3px; +} + +/* Show resize handle on header hover */ +.th:hover .resizer { + opacity: 0.5; + background-color: #6b7280; +}