diff --git a/frontend/src/components/tables/ChannelsTable.jsx b/frontend/src/components/tables/ChannelsTable.jsx
index 3d27ca61..f41ca72d 100644
--- a/frontend/src/components/tables/ChannelsTable.jsx
+++ b/frontend/src/components/tables/ChannelsTable.jsx
@@ -253,7 +253,7 @@ const ChannelsTable = ({ onReady }) => {
const tvgsLoaded = useEPGsStore((s) => s.tvgsLoaded);
// Get channel logos for logo selection
- const { logos: channelLogos, ensureLogosLoaded } = useChannelLogoSelection();
+ const { ensureLogosLoaded } = useChannelLogoSelection();
const theme = useMantineTheme();
const channelGroups = useChannelsStore((s) => s.channelGroups);
@@ -977,19 +977,11 @@ const ChannelsTable = ({ onReady }) => {
enableResizing: false,
header: '',
cell: (props) => (
- {
- // Ensure logos are loaded when user tries to edit
- ensureLogosLoaded();
- }}
- style={{ width: '100%', height: '100%' }}
- >
-
-
+
),
},
{
diff --git a/frontend/src/components/tables/ChannelsTable/EditableCell.jsx b/frontend/src/components/tables/ChannelsTable/EditableCell.jsx
index 065e4631..42d99e73 100644
--- a/frontend/src/components/tables/ChannelsTable/EditableCell.jsx
+++ b/frontend/src/components/tables/ChannelsTable/EditableCell.jsx
@@ -4,6 +4,7 @@ import React, {
useEffect,
useRef,
useMemo,
+ memo,
} from 'react';
import {
Box,
@@ -16,33 +17,88 @@ import {
} from '@mantine/core';
import API from '../../../api';
import useChannelsTableStore from '../../../store/channelsTable';
+import useLogosStore from '../../../store/logos';
+
+// Lightweight wrapper that only renders full editable cell when unlocked
+// This prevents 250+ heavy component instances when table is locked
+const EditableCellWrapper = memo(
+ ({ children, getValue, isUnlocked, renderLocked }) => {
+ if (!isUnlocked) {
+ // Render lightweight locked view
+ return renderLocked ? (
+ renderLocked(getValue())
+ ) : (
+
+ {getValue() ?? ''}
+
+ );
+ }
+ // Only render heavy component when unlocked
+ return children;
+ }
+);
// Editable text cell
export const EditableTextCell = ({ row, column, getValue }) => {
const isUnlocked = useChannelsTableStore((s) => s.isUnlocked);
+ const [isFocused, setIsFocused] = useState(false);
+
+ // When locked or not focused, show simple display
+ if (!isUnlocked || !isFocused) {
+ return (
+ isUnlocked && setIsFocused(true)}
+ style={{
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ cursor: isUnlocked ? 'text' : 'default',
+ padding: '0 4px',
+ }}
+ >
+ {getValue() ?? ''}
+
+ );
+ }
+
+ // Only mount heavy component when actually editing
+ return (
+ setIsFocused(false)}
+ />
+ );
+};
+
+// Inner component with all the editing logic - only rendered when focused
+const EditableTextCellInner = ({ row, column, getValue, onBlur }) => {
const initialValue = getValue() || '';
const [value, setValue] = useState(initialValue);
- const [isFocused, setIsFocused] = useState(false);
const previousValue = useRef(initialValue);
const isMounted = useRef(false);
const debounceTimer = useRef(null);
useEffect(() => {
const currentValue = getValue() || '';
- if (!isFocused && currentValue !== previousValue.current) {
+ if (currentValue !== previousValue.current) {
setValue(currentValue);
previousValue.current = currentValue;
}
- }, [getValue, isFocused]);
+ }, [getValue]);
const saveValue = useCallback(
async (newValue) => {
- // Don't save if not mounted, not unlocked, or value hasn't changed
- if (
- !isMounted.current ||
- !isUnlocked ||
- newValue === previousValue.current
- ) {
+ // Don't save if not mounted or value hasn't changed
+ if (!isMounted.current || newValue === previousValue.current) {
return;
}
@@ -62,7 +118,7 @@ export const EditableTextCell = ({ row, column, getValue }) => {
setValue(previousValue.current || '');
}
},
- [row.original.id, column.id, isUnlocked]
+ [row.original.id, column.id]
);
useEffect(() => {
@@ -77,7 +133,6 @@ export const EditableTextCell = ({ row, column, getValue }) => {
}, []);
const handleChange = (e) => {
- if (!isUnlocked) return;
const newValue = e.currentTarget.value;
setValue(newValue);
@@ -93,40 +148,10 @@ export const EditableTextCell = ({ row, column, getValue }) => {
};
const handleBlur = () => {
- setIsFocused(false);
- if (isUnlocked) {
- saveValue(value);
- }
+ saveValue(value);
+ onBlur();
};
- const handleClick = () => {
- if (isUnlocked) {
- setIsFocused(true);
- }
- };
-
- if (!isUnlocked || !isFocused) {
- return (
-
- {value}
-
- );
- }
-
return (
{
// Editable number cell
export const EditableNumberCell = ({ row, column, getValue }) => {
const isUnlocked = useChannelsTableStore((s) => s.isUnlocked);
+ const [isFocused, setIsFocused] = useState(false);
+
+ const value = getValue();
+ const formattedValue =
+ value !== null && value !== undefined
+ ? value === Math.floor(value)
+ ? Math.floor(value)
+ : value
+ : '';
+
+ // When locked or not focused, show simple display
+ if (!isUnlocked || !isFocused) {
+ return (
+ isUnlocked && setIsFocused(true)}
+ style={{
+ textAlign: 'right',
+ width: '100%',
+ cursor: isUnlocked ? 'text' : 'default',
+ padding: '0 4px',
+ }}
+ >
+ {formattedValue}
+
+ );
+ }
+
+ return (
+ setIsFocused(false)}
+ />
+ );
+};
+
+// Inner component with all the editing logic - only rendered when focused
+const EditableNumberCellInner = ({ row, column, getValue, onBlur }) => {
const initialValue = getValue();
const [value, setValue] = useState(initialValue);
- const [isFocused, setIsFocused] = useState(false);
const previousValue = useRef(initialValue);
const isMounted = useRef(false);
useEffect(() => {
const currentValue = getValue();
- if (!isFocused && currentValue !== previousValue.current) {
+ if (currentValue !== previousValue.current) {
setValue(currentValue);
previousValue.current = currentValue;
}
- }, [getValue, isFocused]);
+ }, [getValue]);
const saveValue = useCallback(
async (newValue) => {
- // Don't save if not mounted, not unlocked, or value hasn't changed
- if (
- !isMounted.current ||
- !isUnlocked ||
- newValue === previousValue.current
- ) {
+ // Don't save if not mounted or value hasn't changed
+ if (!isMounted.current || newValue === previousValue.current) {
return;
}
@@ -203,8 +262,7 @@ export const EditableNumberCell = ({ row, column, getValue }) => {
// If channel_number was changed, refetch to reorder the table
if (column.id === 'channel_number') {
await API.requeryChannels();
- // Exit edit mode after resorting to avoid confusion
- setIsFocused(false);
+ onBlur();
}
}
} catch (error) {
@@ -212,7 +270,7 @@ export const EditableNumberCell = ({ row, column, getValue }) => {
setValue(previousValue.current);
}
},
- [row.original.id, column.id, isUnlocked]
+ [row.original.id, column.id, onBlur]
);
useEffect(() => {
@@ -223,51 +281,14 @@ export const EditableNumberCell = ({ row, column, getValue }) => {
}, []);
const handleChange = (newValue) => {
- if (!isUnlocked) return;
setValue(newValue);
};
const handleBlur = () => {
- setIsFocused(false);
- if (isUnlocked) {
- saveValue(value);
- }
+ saveValue(value);
+ onBlur();
};
- const handleClick = () => {
- if (isUnlocked) {
- setIsFocused(true);
- }
- };
-
- const formattedValue =
- value !== null && value !== undefined
- ? value === Math.floor(value)
- ? Math.floor(value)
- : value
- : '';
-
- if (!isUnlocked || !isFocused) {
- return (
-
- {formattedValue}
-
- );
- }
-
return (
{
};
// Editable select cell for groups
-export const EditableGroupCell = ({ row, getValue, channelGroups }) => {
+export const EditableGroupCell = ({ row, channelGroups }) => {
const isUnlocked = useChannelsTableStore((s) => s.isUnlocked);
+ const [isFocused, setIsFocused] = useState(false);
const groupId = row.original.channel_group_id;
const groupName = channelGroups[groupId]?.name || '';
+
+ // Show simple display when locked OR when unlocked but not focused
+ if (!isUnlocked || !isFocused) {
+ return (
+ isUnlocked && setIsFocused(true)}
+ style={{
+ whiteSpace: 'nowrap',
+ overflow: 'hidden',
+ textOverflow: 'ellipsis',
+ padding: '0 4px',
+ cursor: isUnlocked ? 'pointer' : 'default',
+ }}
+ >
+ {groupName}
+
+ );
+ }
+
+ return (
+ setIsFocused(false)}
+ />
+ );
+};
+
+// Inner component with all the editing logic - only rendered when focused
+const EditableGroupCellInner = ({
+ row,
+ channelGroups,
+ groupName,
+ groupId,
+ onBlur,
+}) => {
const previousGroupId = useRef(groupId);
- const [isFocused, setIsFocused] = useState(false);
const [searchValue, setSearchValue] = useState('');
const saveValue = useCallback(
async (newGroupId) => {
- // Don't save if not unlocked or value hasn't changed
- if (
- !isUnlocked ||
- String(newGroupId) === String(previousGroupId.current)
- ) {
+ // Don't save if value hasn't changed
+ if (String(newGroupId) === String(previousGroupId.current)) {
return;
}
@@ -324,18 +380,12 @@ export const EditableGroupCell = ({ row, getValue, channelGroups }) => {
console.error('Failed to update channel group:', error);
}
},
- [row.original.id, isUnlocked]
+ [row.original.id]
);
- const handleClick = () => {
- if (isUnlocked) {
- setIsFocused(true);
- }
- };
-
const handleChange = (newGroupId) => {
saveValue(newGroupId);
- setIsFocused(false);
+ onBlur();
setSearchValue('');
};
@@ -344,33 +394,11 @@ export const EditableGroupCell = ({ row, getValue, channelGroups }) => {
label: group.name,
}));
- if (!isUnlocked || !isFocused) {
- return (
-
- {groupName}
-
- );
- }
-
return (