diff --git a/src/pages/assets/bulk-duplicate-finder.tsx b/src/pages/assets/bulk-duplicate-finder.tsx index 8842b7d..5cb981b 100644 --- a/src/pages/assets/bulk-duplicate-finder.tsx +++ b/src/pages/assets/bulk-duplicate-finder.tsx @@ -7,6 +7,8 @@ import VirtualizedDuplicateList from '@/components/assets/duplicate-assets/Virtu import Loader from '@/components/ui/loader' import { Button } from '@/components/ui/button' import { RefreshCw, Search, Shield, Trash2 } from 'lucide-react' +import FloatingBar from '@/components/shared/FloatingBar' +import { AlertDialog } from '@/components/ui/alert-dialog' import { toast } from '@/components/ui/use-toast' import { humanizeBytes } from '@/helpers/string.helper' @@ -72,6 +74,27 @@ export default function BulkDuplicatePage() { return duplicates.flatMap(record => record.assets.map(asset => asset.id)); }, [duplicates]); + // Calculate selected assets info for discard mode + const selectedAssetsInfo = useMemo(() => { + if (selectionMode !== 'discard' || selectedAssets.size === 0) { + return { count: 0, totalSize: 0 }; + } + + let count = 0; + let totalSize = 0; + + duplicates.forEach(record => { + record.assets.forEach(asset => { + if (selectedAssets.has(asset.id)) { + count++; + totalSize += asset.exifInfo.fileSizeInByte; + } + }); + }); + + return { count, totalSize }; + }, [duplicates, selectedAssets, selectionMode]); + const handleAssetSelect = useCallback((assetId: string, isShiftClick?: boolean) => { setSelectedAssets(prev => { const newSelection = new Set(prev); @@ -175,6 +198,45 @@ export default function BulkDuplicatePage() { } }, [duplicates]); + const handleDeleteAllSelected = async () => { + if (selectedAssets.size === 0 || selectionMode !== 'discard') return; + + setIsDeleting(true); + try { + const selectedAssetIds = Array.from(selectedAssets); + + // Delete all selected assets + await deleteAssets(selectedAssetIds); + + // Update duplicates: remove deleted assets and clean up empty records + setDuplicates(prev => { + const updatedDuplicates = prev.map(record => ({ + ...record, + assets: record.assets.filter(asset => !selectedAssets.has(asset.id)) + })).filter(record => record.assets.length > 0); + + return updatedDuplicates; + }); + + // Clear selection + setSelectedAssets(new Set()); + setLastSelectedIndex(-1); + + toast({ + title: "Success", + description: `Successfully deleted ${selectedAssetIds.length} selected assets${selectedAssetsInfo.totalSize > 0 ? ` and saved ${humanizeBytes(selectedAssetsInfo.totalSize)} of storage` : ''}.`, + }); + } catch (error: any) { + toast({ + title: "Error", + description: error.message || "Failed to delete selected assets. Please try again.", + variant: "destructive", + }); + } finally { + setIsDeleting(false); + } + }; + const handleKeepSelected = async (record: IDuplicateAssetRecord, selectedIds: string[], unselectedIds: string[]) => { setIsDeleting(true); try { @@ -363,6 +425,38 @@ export default function BulkDuplicatePage() { )} + + {selectionMode === 'discard' && selectedAssets.size > 0 && ( + +
+

+ {selectedAssetsInfo.count} Selected + {selectedAssetsInfo.totalSize > 0 && ( + + ({humanizeBytes(selectedAssetsInfo.totalSize)}) + + )} +

+
+ 0 ? ` and save ${humanizeBytes(selectedAssetsInfo.totalSize)} of storage` : ''}. This action cannot be undone.`} + onConfirm={handleDeleteAllSelected} + disabled={selectedAssets.size === 0} + > + + +
+
+
+ )} ) }