From 1af0803c624f96cd2e0d436d5d0072afe04bcccd Mon Sep 17 00:00:00 2001 From: yangdx Date: Sun, 17 Aug 2025 10:38:12 +0800 Subject: [PATCH] fix(ui): fix selection state management in paginated views - Replace DeselectDocumentsDialog with smart selection button - Auto-reset selection on page/filter changes - Remove deletion restrictions and update i18n --- .../documents/DeleteDocumentsDialog.tsx | 11 +-- .../documents/DeselectDocumentsDialog.tsx | 74 ----------------- .../src/features/DocumentManager.tsx | 81 ++++++++++++++++--- lightrag_webui/src/locales/ar.json | 12 +-- lightrag_webui/src/locales/en.json | 12 +-- lightrag_webui/src/locales/fr.json | 12 +-- lightrag_webui/src/locales/zh.json | 12 +-- lightrag_webui/src/locales/zh_TW.json | 12 +-- 8 files changed, 93 insertions(+), 133 deletions(-) delete mode 100644 lightrag_webui/src/components/documents/DeselectDocumentsDialog.tsx diff --git a/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx b/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx index acbf011e..65305ccb 100644 --- a/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx +++ b/lightrag_webui/src/components/documents/DeleteDocumentsDialog.tsx @@ -35,11 +35,10 @@ const Label = ({ interface DeleteDocumentsDialogProps { selectedDocIds: string[] - totalCompletedCount: number onDocumentsDeleted?: () => Promise } -export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCount, onDocumentsDeleted }: DeleteDocumentsDialogProps) { +export default function DeleteDocumentsDialog({ selectedDocIds, onDocumentsDeleted }: DeleteDocumentsDialogProps) { const { t } = useTranslation() const [open, setOpen] = useState(false) const [confirmText, setConfirmText] = useState('') @@ -59,12 +58,6 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo const handleDelete = useCallback(async () => { if (!isConfirmEnabled || selectedDocIds.length === 0) return - // Check if user is trying to delete all completed documents - if (selectedDocIds.length === totalCompletedCount && totalCompletedCount > 0) { - toast.error(t('documentPanel.deleteDocuments.cannotDeleteAll')) - return - } - setIsDeleting(true) try { const result = await deleteDocuments(selectedDocIds, deleteFile) @@ -101,7 +94,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo } finally { setIsDeleting(false) } - }, [isConfirmEnabled, selectedDocIds, totalCompletedCount, deleteFile, setOpen, t, onDocumentsDeleted]) + }, [isConfirmEnabled, selectedDocIds, deleteFile, setOpen, t, onDocumentsDeleted]) return ( diff --git a/lightrag_webui/src/components/documents/DeselectDocumentsDialog.tsx b/lightrag_webui/src/components/documents/DeselectDocumentsDialog.tsx deleted file mode 100644 index ecb8af3e..00000000 --- a/lightrag_webui/src/components/documents/DeselectDocumentsDialog.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useState, useCallback, useEffect } from 'react' -import Button from '@/components/ui/Button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, - DialogFooter -} from '@/components/ui/Dialog' - -import { XIcon, AlertCircleIcon } from 'lucide-react' -import { useTranslation } from 'react-i18next' - -interface DeselectDocumentsDialogProps { - selectedCount: number - onDeselect: () => void -} - -export default function DeselectDocumentsDialog({ selectedCount, onDeselect }: DeselectDocumentsDialogProps) { - const { t } = useTranslation() - const [open, setOpen] = useState(false) - - // Reset state when dialog closes - useEffect(() => { - if (!open) { - // No state to reset for this simple dialog - } - }, [open]) - - const handleDeselect = useCallback(() => { - onDeselect() - setOpen(false) - }, [onDeselect, setOpen]) - - return ( - - - - - e.preventDefault()}> - - - - {t('documentPanel.deselectDocuments.title')} - - - {t('documentPanel.deselectDocuments.description', { count: selectedCount })} - - - - - - - - - - ) -} diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx index 99e65775..01f08df5 100644 --- a/lightrag_webui/src/features/DocumentManager.tsx +++ b/lightrag_webui/src/features/DocumentManager.tsx @@ -17,7 +17,6 @@ import Checkbox from '@/components/ui/Checkbox' import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog' import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog' import DeleteDocumentsDialog from '@/components/documents/DeleteDocumentsDialog' -import DeselectDocumentsDialog from '@/components/documents/DeselectDocumentsDialog' import PaginationControls from '@/components/ui/PaginationControls' import { @@ -33,7 +32,7 @@ import { errorMessage } from '@/lib/utils' import { toast } from 'sonner' import { useBackendState } from '@/stores/state' -import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, RotateCcwIcon } from 'lucide-react' +import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, RotateCcwIcon, CheckSquareIcon, XIcon } from 'lucide-react' import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog' type StatusFilter = DocStatus | 'all'; @@ -327,6 +326,52 @@ export default function DocumentManager() { return allDocuments; }, [docs, sortField, sortDirection, statusFilter, sortDocuments]); + // Calculate current page selection state (after filteredAndSortedDocs is defined) + const currentPageDocIds = useMemo(() => { + return filteredAndSortedDocs?.map(doc => doc.id) || [] + }, [filteredAndSortedDocs]) + + const selectedCurrentPageCount = useMemo(() => { + return currentPageDocIds.filter(id => selectedDocIds.includes(id)).length + }, [currentPageDocIds, selectedDocIds]) + + const isCurrentPageFullySelected = useMemo(() => { + return currentPageDocIds.length > 0 && selectedCurrentPageCount === currentPageDocIds.length + }, [currentPageDocIds, selectedCurrentPageCount]) + + const hasCurrentPageSelection = useMemo(() => { + return selectedCurrentPageCount > 0 + }, [selectedCurrentPageCount]) + + // Handle select current page + const handleSelectCurrentPage = useCallback(() => { + setSelectedDocIds(currentPageDocIds) + }, [currentPageDocIds]) + + + // Get selection button properties + const getSelectionButtonProps = useCallback(() => { + if (!hasCurrentPageSelection) { + return { + text: t('documentPanel.selectDocuments.selectCurrentPage', { count: currentPageDocIds.length }), + action: handleSelectCurrentPage, + icon: CheckSquareIcon + } + } else if (isCurrentPageFullySelected) { + return { + text: t('documentPanel.selectDocuments.deselectAll', { count: currentPageDocIds.length }), + action: handleDeselectAll, + icon: XIcon + } + } else { + return { + text: t('documentPanel.selectDocuments.selectCurrentPage', { count: currentPageDocIds.length }), + action: handleSelectCurrentPage, + icon: CheckSquareIcon + } + } + }, [hasCurrentPageSelection, isCurrentPageFullySelected, currentPageDocIds.length, handleSelectCurrentPage, handleDeselectAll, t]) + // Calculate document counts for each status const documentCounts = useMemo(() => { if (!docs) return { all: 0 } as Record; @@ -766,6 +811,11 @@ export default function DocumentManager() { } }, [showFileName, sortField]); + // Reset selection state when page, status filter, or sort changes + useEffect(() => { + setSelectedDocIds([]) + }, [pagination.page, statusFilter, sortField, sortDirection]); + // Central effect to handle all data fetching useEffect(() => { if (currentTab === 'documents') { @@ -830,18 +880,29 @@ export default function DocumentManager() { {isSelectionMode && ( )} - {isSelectionMode ? ( - - ) : ( + {isSelectionMode && hasCurrentPageSelection ? ( + (() => { + const buttonProps = getSelectionButtonProps(); + const IconComponent = buttonProps.icon; + return ( + + ); + })() + ) : !isSelectionMode ? ( - )} + ) : null}