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
This commit is contained in:
parent
3e4214cef3
commit
1af0803c62
8 changed files with 93 additions and 133 deletions
|
|
@ -35,11 +35,10 @@ const Label = ({
|
||||||
|
|
||||||
interface DeleteDocumentsDialogProps {
|
interface DeleteDocumentsDialogProps {
|
||||||
selectedDocIds: string[]
|
selectedDocIds: string[]
|
||||||
totalCompletedCount: number
|
|
||||||
onDocumentsDeleted?: () => Promise<void>
|
onDocumentsDeleted?: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCount, onDocumentsDeleted }: DeleteDocumentsDialogProps) {
|
export default function DeleteDocumentsDialog({ selectedDocIds, onDocumentsDeleted }: DeleteDocumentsDialogProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const [confirmText, setConfirmText] = useState('')
|
const [confirmText, setConfirmText] = useState('')
|
||||||
|
|
@ -59,12 +58,6 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
|
||||||
const handleDelete = useCallback(async () => {
|
const handleDelete = useCallback(async () => {
|
||||||
if (!isConfirmEnabled || selectedDocIds.length === 0) return
|
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)
|
setIsDeleting(true)
|
||||||
try {
|
try {
|
||||||
const result = await deleteDocuments(selectedDocIds, deleteFile)
|
const result = await deleteDocuments(selectedDocIds, deleteFile)
|
||||||
|
|
@ -101,7 +94,7 @@ export default function DeleteDocumentsDialog({ selectedDocIds, totalCompletedCo
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false)
|
setIsDeleting(false)
|
||||||
}
|
}
|
||||||
}, [isConfirmEnabled, selectedDocIds, totalCompletedCount, deleteFile, setOpen, t, onDocumentsDeleted])
|
}, [isConfirmEnabled, selectedDocIds, deleteFile, setOpen, t, onDocumentsDeleted])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
side="bottom"
|
|
||||||
tooltip={t('documentPanel.deselectDocuments.tooltip')}
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<XIcon/> {t('documentPanel.deselectDocuments.button')}
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="sm:max-w-md" onCloseAutoFocus={(e) => e.preventDefault()}>
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle className="flex items-center gap-2">
|
|
||||||
<AlertCircleIcon className="h-5 w-5" />
|
|
||||||
{t('documentPanel.deselectDocuments.title')}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogDescription className="pt-2">
|
|
||||||
{t('documentPanel.deselectDocuments.description', { count: selectedCount })}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
|
||||||
{t('common.cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
onClick={handleDeselect}
|
|
||||||
>
|
|
||||||
{t('documentPanel.deselectDocuments.confirmButton')}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,6 @@ import Checkbox from '@/components/ui/Checkbox'
|
||||||
import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
||||||
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
||||||
import DeleteDocumentsDialog from '@/components/documents/DeleteDocumentsDialog'
|
import DeleteDocumentsDialog from '@/components/documents/DeleteDocumentsDialog'
|
||||||
import DeselectDocumentsDialog from '@/components/documents/DeselectDocumentsDialog'
|
|
||||||
import PaginationControls from '@/components/ui/PaginationControls'
|
import PaginationControls from '@/components/ui/PaginationControls'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -33,7 +32,7 @@ import { errorMessage } from '@/lib/utils'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { useBackendState } from '@/stores/state'
|
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'
|
import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
|
||||||
|
|
||||||
type StatusFilter = DocStatus | 'all';
|
type StatusFilter = DocStatus | 'all';
|
||||||
|
|
@ -327,6 +326,52 @@ export default function DocumentManager() {
|
||||||
return allDocuments;
|
return allDocuments;
|
||||||
}, [docs, sortField, sortDirection, statusFilter, sortDocuments]);
|
}, [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
|
// Calculate document counts for each status
|
||||||
const documentCounts = useMemo(() => {
|
const documentCounts = useMemo(() => {
|
||||||
if (!docs) return { all: 0 } as Record<string, number>;
|
if (!docs) return { all: 0 } as Record<string, number>;
|
||||||
|
|
@ -766,6 +811,11 @@ export default function DocumentManager() {
|
||||||
}
|
}
|
||||||
}, [showFileName, sortField]);
|
}, [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
|
// Central effect to handle all data fetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentTab === 'documents') {
|
if (currentTab === 'documents') {
|
||||||
|
|
@ -830,18 +880,29 @@ export default function DocumentManager() {
|
||||||
{isSelectionMode && (
|
{isSelectionMode && (
|
||||||
<DeleteDocumentsDialog
|
<DeleteDocumentsDialog
|
||||||
selectedDocIds={selectedDocIds}
|
selectedDocIds={selectedDocIds}
|
||||||
totalCompletedCount={documentCounts.processed || 0}
|
|
||||||
onDocumentsDeleted={handleDocumentsDeleted}
|
onDocumentsDeleted={handleDocumentsDeleted}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isSelectionMode ? (
|
{isSelectionMode && hasCurrentPageSelection ? (
|
||||||
<DeselectDocumentsDialog
|
(() => {
|
||||||
selectedCount={selectedDocIds.length}
|
const buttonProps = getSelectionButtonProps();
|
||||||
onDeselect={handleDeselectAll}
|
const IconComponent = buttonProps.icon;
|
||||||
/>
|
return (
|
||||||
) : (
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={buttonProps.action}
|
||||||
|
side="bottom"
|
||||||
|
tooltip={buttonProps.text}
|
||||||
|
>
|
||||||
|
<IconComponent className="h-4 w-4" />
|
||||||
|
{buttonProps.text}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : !isSelectionMode ? (
|
||||||
<ClearDocumentsDialog onDocumentsCleared={handleDocumentsCleared} />
|
<ClearDocumentsDialog onDocumentsCleared={handleDocumentsCleared} />
|
||||||
)}
|
) : null}
|
||||||
<UploadDocumentsDialog onDocumentsUploaded={fetchDocuments} />
|
<UploadDocumentsDialog onDocumentsUploaded={fetchDocuments} />
|
||||||
<PipelineStatusDialog
|
<PipelineStatusDialog
|
||||||
open={showPipelineStatus}
|
open={showPipelineStatus}
|
||||||
|
|
|
||||||
|
|
@ -74,15 +74,11 @@
|
||||||
"failed": "فشل حذف المستندات:\n{{message}}",
|
"failed": "فشل حذف المستندات:\n{{message}}",
|
||||||
"error": "فشل حذف المستندات:\n{{error}}",
|
"error": "فشل حذف المستندات:\n{{error}}",
|
||||||
"busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا",
|
"busy": "خط المعالجة مشغول، يرجى المحاولة مرة أخرى لاحقًا",
|
||||||
"notAllowed": "لا توجد صلاحية لتنفيذ هذه العملية",
|
"notAllowed": "لا توجد صلاحية لتنفيذ هذه العملية"
|
||||||
"cannotDeleteAll": "لا يمكن حذف جميع المستندات. إذا كنت بحاجة لحذف جميع المستندات، يرجى استخدام ميزة مسح المستندات."
|
|
||||||
},
|
},
|
||||||
"deselectDocuments": {
|
"selectDocuments": {
|
||||||
"button": "إلغاء التحديد",
|
"selectCurrentPage": "تحديد الصفحة الحالية ({{count}})",
|
||||||
"tooltip": "إلغاء تحديد جميع المستندات المحددة",
|
"deselectAll": "إلغاء تحديد الكل ({{count}})"
|
||||||
"title": "إلغاء تحديد المستندات",
|
|
||||||
"description": "سيؤدي هذا إلى مسح جميع المستندات المحددة ({{count}} محدد)",
|
|
||||||
"confirmButton": "إلغاء تحديد الكل"
|
|
||||||
},
|
},
|
||||||
"uploadDocuments": {
|
"uploadDocuments": {
|
||||||
"button": "رفع",
|
"button": "رفع",
|
||||||
|
|
|
||||||
|
|
@ -74,15 +74,11 @@
|
||||||
"failed": "Delete Documents Failed:\n{{message}}",
|
"failed": "Delete Documents Failed:\n{{message}}",
|
||||||
"error": "Delete Documents Failed:\n{{error}}",
|
"error": "Delete Documents Failed:\n{{error}}",
|
||||||
"busy": "Pipeline is busy, please try again later",
|
"busy": "Pipeline is busy, please try again later",
|
||||||
"notAllowed": "No permission to perform this operation",
|
"notAllowed": "No permission to perform this operation"
|
||||||
"cannotDeleteAll": "Cannot delete all documents. If you need to delete all documents, please use the Clear Documents feature."
|
|
||||||
},
|
},
|
||||||
"deselectDocuments": {
|
"selectDocuments": {
|
||||||
"button": "Deselect",
|
"selectCurrentPage": "Select Current Page ({{count}})",
|
||||||
"tooltip": "Deselect all selected documents",
|
"deselectAll": "Deselect All ({{count}})"
|
||||||
"title": "Deselect Documents",
|
|
||||||
"description": "This will clear all selected documents ({{count}} selected)",
|
|
||||||
"confirmButton": "Deselect All"
|
|
||||||
},
|
},
|
||||||
"uploadDocuments": {
|
"uploadDocuments": {
|
||||||
"button": "Upload",
|
"button": "Upload",
|
||||||
|
|
|
||||||
|
|
@ -74,15 +74,11 @@
|
||||||
"failed": "Échec de la suppression des documents :\n{{message}}",
|
"failed": "Échec de la suppression des documents :\n{{message}}",
|
||||||
"error": "Échec de la suppression des documents :\n{{error}}",
|
"error": "Échec de la suppression des documents :\n{{error}}",
|
||||||
"busy": "Le pipeline est occupé, veuillez réessayer plus tard",
|
"busy": "Le pipeline est occupé, veuillez réessayer plus tard",
|
||||||
"notAllowed": "Aucune autorisation pour effectuer cette opération",
|
"notAllowed": "Aucune autorisation pour effectuer cette opération"
|
||||||
"cannotDeleteAll": "Impossible de supprimer tous les documents. Si vous devez supprimer tous les documents, veuillez utiliser la fonction Effacer les documents."
|
|
||||||
},
|
},
|
||||||
"deselectDocuments": {
|
"selectDocuments": {
|
||||||
"button": "Désélectionner",
|
"selectCurrentPage": "Sélectionner la page actuelle ({{count}})",
|
||||||
"tooltip": "Désélectionner tous les documents sélectionnés",
|
"deselectAll": "Tout désélectionner ({{count}})"
|
||||||
"title": "Désélectionner les documents",
|
|
||||||
"description": "Cette action effacera tous les documents sélectionnés ({{count}} sélectionnés)",
|
|
||||||
"confirmButton": "Tout désélectionner"
|
|
||||||
},
|
},
|
||||||
"uploadDocuments": {
|
"uploadDocuments": {
|
||||||
"button": "Télécharger",
|
"button": "Télécharger",
|
||||||
|
|
|
||||||
|
|
@ -74,15 +74,11 @@
|
||||||
"failed": "删除文档失败:\n{{message}}",
|
"failed": "删除文档失败:\n{{message}}",
|
||||||
"error": "删除文档失败:\n{{error}}",
|
"error": "删除文档失败:\n{{error}}",
|
||||||
"busy": "流水线被占用,请稍后再试",
|
"busy": "流水线被占用,请稍后再试",
|
||||||
"notAllowed": "没有操作权限",
|
"notAllowed": "没有操作权限"
|
||||||
"cannotDeleteAll": "无法删除所有文档。如确实需要删除所有文档请使用清空文档功能。"
|
|
||||||
},
|
},
|
||||||
"deselectDocuments": {
|
"selectDocuments": {
|
||||||
"button": "取消选择",
|
"selectCurrentPage": "全选当前页 ({{count}})",
|
||||||
"tooltip": "取消选择所有文档",
|
"deselectAll": "取消全选 ({{count}})"
|
||||||
"title": "取消选择文档",
|
|
||||||
"description": "此操作将清除所有选中的文档(已选择 {{count}} 个)",
|
|
||||||
"confirmButton": "取消全部选择"
|
|
||||||
},
|
},
|
||||||
"uploadDocuments": {
|
"uploadDocuments": {
|
||||||
"button": "上传",
|
"button": "上传",
|
||||||
|
|
|
||||||
|
|
@ -74,15 +74,11 @@
|
||||||
"failed": "刪除文件失敗:\n{{message}}",
|
"failed": "刪除文件失敗:\n{{message}}",
|
||||||
"error": "刪除文件失敗:\n{{error}}",
|
"error": "刪除文件失敗:\n{{error}}",
|
||||||
"busy": "pipeline 被佔用,請稍後再試",
|
"busy": "pipeline 被佔用,請稍後再試",
|
||||||
"notAllowed": "沒有操作權限",
|
"notAllowed": "沒有操作權限"
|
||||||
"cannotDeleteAll": "無法刪除所有文件。如確實需要刪除所有文件請使用清空文件功能。"
|
|
||||||
},
|
},
|
||||||
"deselectDocuments": {
|
"selectDocuments": {
|
||||||
"button": "取消選取",
|
"selectCurrentPage": "全選當前頁 ({{count}})",
|
||||||
"tooltip": "取消選取所有文件",
|
"deselectAll": "取消全選 ({{count}})"
|
||||||
"title": "取消選取文件",
|
|
||||||
"description": "此操作將清除所有選取的文件(已選取 {{count}} 個)",
|
|
||||||
"confirmButton": "取消全部選取"
|
|
||||||
},
|
},
|
||||||
"uploadDocuments": {
|
"uploadDocuments": {
|
||||||
"button": "上傳",
|
"button": "上傳",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue