From 2da8273de64fd19324ef4ea8769ab32141f75c3c Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Sat, 12 Jul 2025 17:41:35 -0500 Subject: [PATCH] Add confirmation for deleting and cleaning up groups. --- .../src/components/forms/GroupManager.jsx | 452 +++++++++++------- 1 file changed, 268 insertions(+), 184 deletions(-) diff --git a/frontend/src/components/forms/GroupManager.jsx b/frontend/src/components/forms/GroupManager.jsx index 3b63b738..abb44727 100644 --- a/frontend/src/components/forms/GroupManager.jsx +++ b/frontend/src/components/forms/GroupManager.jsx @@ -29,6 +29,8 @@ import { } from 'lucide-react'; import { notifications } from '@mantine/notifications'; import useChannelsStore from '../../store/channels'; +import useWarningsStore from '../../store/warnings'; +import ConfirmationDialog from '../ConfirmationDialog'; import API from '../../api'; // Move GroupItem outside to prevent recreation on every render @@ -136,6 +138,9 @@ const GroupManager = React.memo(({ isOpen, onClose }) => { const channelGroups = useChannelsStore((s) => s.channelGroups); const canEditChannelGroup = useChannelsStore((s) => s.canEditChannelGroup); const canDeleteChannelGroup = useChannelsStore((s) => s.canDeleteChannelGroup); + const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed); + const suppressWarning = useWarningsStore((s) => s.suppressWarning); + const [editingGroup, setEditingGroup] = useState(null); const [editName, setEditName] = useState(''); const [newGroupName, setNewGroupName] = useState(''); @@ -148,6 +153,11 @@ const GroupManager = React.memo(({ isOpen, onClose }) => { const [showM3UGroups, setShowM3UGroups] = useState(true); const [showUnusedGroups, setShowUnusedGroups] = useState(true); + // Confirmation dialog states + const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false); + const [groupToDelete, setGroupToDelete] = useState(null); + const [confirmCleanupOpen, setConfirmCleanupOpen] = useState(false); + // Memoize the channel groups array to prevent unnecessary re-renders const channelGroupsArray = useMemo(() => Object.values(channelGroups), @@ -348,6 +358,18 @@ const GroupManager = React.memo(({ isOpen, onClose }) => { return; } + // Store group for confirmation dialog + setGroupToDelete(group); + + // Skip warning if it's been suppressed + if (isWarningSuppressed('delete-group')) { + return executeDeleteGroup(group); + } + + setConfirmDeleteOpen(true); + }, [groupUsage, isWarningSuppressed]); + + const executeDeleteGroup = useCallback(async (group) => { try { await API.deleteChannelGroup(group.id); @@ -358,14 +380,50 @@ const GroupManager = React.memo(({ isOpen, onClose }) => { }); fetchGroupUsage(); // Refresh usage data + setConfirmDeleteOpen(false); } catch (error) { notifications.show({ title: 'Error', message: 'Failed to delete group', color: 'red', }); + setConfirmDeleteOpen(false); } - }, [groupUsage, fetchGroupUsage]); + }, [fetchGroupUsage]); + + const handleCleanup = useCallback(async () => { + // Skip warning if it's been suppressed + if (isWarningSuppressed('cleanup-groups')) { + return executeCleanup(); + } + + setConfirmCleanupOpen(true); + }, [isWarningSuppressed]); + + const executeCleanup = useCallback(async () => { + setIsCleaningUp(true); + try { + const result = await API.cleanupUnusedChannelGroups(); + + notifications.show({ + title: 'Cleanup Complete', + message: `Successfully deleted ${result.deleted_count} unused groups`, + color: 'green', + }); + + fetchGroupUsage(); // Refresh usage data + setConfirmCleanupOpen(false); + } catch (error) { + notifications.show({ + title: 'Cleanup Failed', + message: 'Failed to cleanup unused groups', + color: 'red', + }); + setConfirmCleanupOpen(false); + } finally { + setIsCleaningUp(false); + } + }, [fetchGroupUsage]); const handleNewGroupNameChange = useCallback((e) => { setNewGroupName(e.target.value); @@ -379,198 +437,224 @@ const GroupManager = React.memo(({ isOpen, onClose }) => { setSearchTerm(e.target.value); }, []); - const handleCleanup = useCallback(async () => { - setIsCleaningUp(true); - try { - const result = await API.cleanupUnusedChannelGroups(); - - notifications.show({ - title: 'Cleanup Complete', - message: `Successfully deleted ${result.deleted_count} unused groups`, - color: 'green', - }); - - fetchGroupUsage(); // Refresh usage data - } catch (error) { - notifications.show({ - title: 'Cleanup Failed', - message: 'Failed to cleanup unused groups', - color: 'red', - }); - } finally { - setIsCleaningUp(false); - } - }, [fetchGroupUsage]); - if (!isOpen) return null; return ( - - - } color="blue" variant="light"> - Manage channel groups. Groups associated with M3U accounts or containing channels cannot be deleted. - - - {/* Create new group section */} - - {isCreating ? ( - - e.key === 'Enter' && handleCreate()} - autoFocus - /> - - - - { - setIsCreating(false); - setNewGroupName(''); - }}> - - - - ) : ( - - )} - - {!isCreating && ( - - )} - - - - - {/* Filter Controls */} - - - - - Filter Groups - - setSearchTerm('')} - > - - - )} - /> - - - - Show: - - - - Channel Groups ({filterCounts.channels}) - - - - - - M3U Groups ({filterCounts.m3u}) - - - - Unused Groups ({filterCounts.unused}) - - - - - - - {/* Existing groups */} + <> + - - Groups ({filteredGroups.length}{(searchTerm || !showChannelGroups || !showM3UGroups || !showUnusedGroups) && ` of ${sortedGroups.length}`}) - + } color="blue" variant="light"> + Manage channel groups. Groups associated with M3U accounts or containing channels cannot be deleted. + - {loading ? ( - Loading group information... - ) : filteredGroups.length === 0 ? ( - - {searchTerm || !showChannelGroups || !showM3UGroups || !showUnusedGroups ? 'No groups found matching your filters' : 'No groups found'} - - ) : ( - - {filteredGroups.map((group) => ( - + {isCreating ? ( + + e.key === 'Enter' && handleCreate()} + autoFocus /> - ))} - - )} + + + + { + setIsCreating(false); + setNewGroupName(''); + }}> + + + + ) : ( + + )} + + {!isCreating && ( + + )} + + + + + {/* Filter Controls */} + + + + + Filter Groups + + setSearchTerm('')} + > + + + )} + /> + + + + Show: + + + + Channel Groups ({filterCounts.channels}) + + + + + + M3U Groups ({filterCounts.m3u}) + + + + Unused Groups ({filterCounts.unused}) + + + + + + + {/* Existing groups */} + + + Groups ({filteredGroups.length}{(searchTerm || !showChannelGroups || !showM3UGroups || !showUnusedGroups) && ` of ${sortedGroups.length}`}) + + + {loading ? ( + Loading group information... + ) : filteredGroups.length === 0 ? ( + + {searchTerm || !showChannelGroups || !showM3UGroups || !showUnusedGroups ? 'No groups found matching your filters' : 'No groups found'} + + ) : ( + + {filteredGroups.map((group) => ( + + ))} + + )} + + + + + + + + - + setConfirmDeleteOpen(false)} + onConfirm={() => executeDeleteGroup(groupToDelete)} + title="Confirm Group Deletion" + message={ + groupToDelete ? ( +
+ {`Are you sure you want to delete the following group? - - - - - +Name: ${groupToDelete.name} + +This action cannot be undone.`} +
+ ) : ( + 'Are you sure you want to delete this group? This action cannot be undone.' + ) + } + confirmLabel="Delete" + cancelLabel="Cancel" + actionKey="delete-group" + onSuppressChange={suppressWarning} + size="md" + /> + + setConfirmCleanupOpen(false)} + onConfirm={executeCleanup} + title="Confirm Group Cleanup" + message={ +
+ {`Are you sure you want to cleanup all unused groups? + +This will permanently delete all groups that are not associated with any channels or M3U accounts. + +This action cannot be undone.`} +
+ } + confirmLabel="Cleanup" + cancelLabel="Cancel" + actionKey="cleanup-groups" + onSuppressChange={suppressWarning} + size="md" + /> + ); }); + export default GroupManager;