From fa5d39d8a2d6bb64f24bcefd0726f2583f54387e Mon Sep 17 00:00:00 2001 From: yangdx Date: Thu, 27 Mar 2025 13:37:50 +0800 Subject: [PATCH] feat: add sortable columns to document manager - Add sorting capability for ID, created_at and updated_at columns - Implement ascending/descending sort with visual indicators - Handle special case for filename sorting in ID column - Add hover effects on sortable column headers --- .../src/features/DocumentManager.tsx | 110 ++++++++++++++++-- 1 file changed, 102 insertions(+), 8 deletions(-) diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx index 46ad2692..b2d1591b 100644 --- a/lightrag_webui/src/features/DocumentManager.tsx +++ b/lightrag_webui/src/features/DocumentManager.tsx @@ -21,7 +21,7 @@ import { errorMessage } from '@/lib/utils' import { toast } from 'sonner' import { useBackendState } from '@/stores/state' -import { RefreshCwIcon, ActivityIcon } from 'lucide-react' +import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react' import { DocStatusResponse } from '@/api/lightrag' import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog' @@ -99,6 +99,10 @@ const pulseStyle = ` } `; +// Type definitions for sort field and direction +type SortField = 'created_at' | 'updated_at' | 'id'; +type SortDirection = 'asc' | 'desc'; + export default function DocumentManager() { const [showPipelineStatus, setShowPipelineStatus] = useState(false) const { t } = useTranslation() @@ -108,6 +112,52 @@ export default function DocumentManager() { const currentTab = useSettingsStore.use.currentTab() const showFileName = useSettingsStore.use.showFileName() const setShowFileName = useSettingsStore.use.setShowFileName() + + // Sort state + const [sortField, setSortField] = useState('updated_at') + const [sortDirection, setSortDirection] = useState('desc') + + // Handle sort column click + const handleSort = (field: SortField) => { + if (sortField === field) { + // Toggle sort direction if clicking the same field + setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc') + } else { + // Set new sort field with default desc direction + setSortField(field) + setSortDirection('desc') + } + } + + // Sort documents based on current sort field and direction + const sortDocuments = (documents: DocStatusResponse[]) => { + return [...documents].sort((a, b) => { + let valueA, valueB; + + // Special handling for ID field based on showFileName setting + if (sortField === 'id' && showFileName) { + valueA = getDisplayFileName(a); + valueB = getDisplayFileName(b); + } else if (sortField === 'id') { + valueA = a.id; + valueB = b.id; + } else { + // Date fields + valueA = new Date(a[sortField]).getTime(); + valueB = new Date(b[sortField]).getTime(); + } + + // Apply sort direction + const sortMultiplier = sortDirection === 'asc' ? 1 : -1; + + // Compare values + if (typeof valueA === 'string' && typeof valueB === 'string') { + return sortMultiplier * valueA.localeCompare(valueB); + } else { + return sortMultiplier * (valueA > valueB ? 1 : valueA < valueB ? -1 : 0); + } + }); + } // Store previous status counts const prevStatusCounts = useRef({ @@ -268,6 +318,11 @@ export default function DocumentManager() { return () => clearInterval(interval) }, [health, fetchDocuments, t, currentTab]) + // Add dependency on sort state to re-render when sort changes + useEffect(() => { + // This effect ensures the component re-renders when sort state changes + }, [sortField, sortDirection]); + return ( @@ -344,18 +399,57 @@ export default function DocumentManager() { - {t('documentPanel.documentManager.columns.id')} + handleSort('id')} + className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none" + > +
+ {t('documentPanel.documentManager.columns.id')} + {sortField === 'id' && ( + + {sortDirection === 'asc' ? : } + + )} +
+
{t('documentPanel.documentManager.columns.summary')} {t('documentPanel.documentManager.columns.status')} {t('documentPanel.documentManager.columns.length')} {t('documentPanel.documentManager.columns.chunks')} - {t('documentPanel.documentManager.columns.created')} - {t('documentPanel.documentManager.columns.updated')} + handleSort('created_at')} + className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none" + > +
+ {t('documentPanel.documentManager.columns.created')} + {sortField === 'created_at' && ( + + {sortDirection === 'asc' ? : } + + )} +
+
+ handleSort('updated_at')} + className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none" + > +
+ {t('documentPanel.documentManager.columns.updated')} + {sortField === 'updated_at' && ( + + {sortDirection === 'asc' ? : } + + )} +
+
- {Object.entries(docs.statuses).map(([status, documents]) => - documents.map((doc) => ( + {Object.entries(docs.statuses).flatMap(([status, documents]) => { + // Apply sorting to documents + const sortedDocuments = sortDocuments(documents); + + return sortedDocuments.map(doc => ( {showFileName ? ( @@ -415,8 +509,8 @@ export default function DocumentManager() { {new Date(doc.updated_at).toLocaleString()} - )) - )} + )); + })}