yangdx commited on
Commit
74d6dff
·
1 Parent(s): 14d6097

feat: add sortable columns to document manager

Browse files

- 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

lightrag_webui/src/features/DocumentManager.tsx CHANGED
@@ -21,7 +21,7 @@ import { errorMessage } from '@/lib/utils'
21
  import { toast } from 'sonner'
22
  import { useBackendState } from '@/stores/state'
23
 
24
- import { RefreshCwIcon, ActivityIcon } from 'lucide-react'
25
  import { DocStatusResponse } from '@/api/lightrag'
26
  import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
27
 
@@ -99,6 +99,10 @@ const pulseStyle = `
99
  }
100
  `;
101
 
 
 
 
 
102
  export default function DocumentManager() {
103
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
104
  const { t } = useTranslation()
@@ -108,6 +112,52 @@ export default function DocumentManager() {
108
  const currentTab = useSettingsStore.use.currentTab()
109
  const showFileName = useSettingsStore.use.showFileName()
110
  const setShowFileName = useSettingsStore.use.setShowFileName()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
  // Store previous status counts
113
  const prevStatusCounts = useRef({
@@ -268,6 +318,11 @@ export default function DocumentManager() {
268
  return () => clearInterval(interval)
269
  }, [health, fetchDocuments, t, currentTab])
270
 
 
 
 
 
 
271
  return (
272
  <Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
273
  <CardHeader className="py-2 px-6">
@@ -344,18 +399,57 @@ export default function DocumentManager() {
344
  <Table className="w-full">
345
  <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
346
  <TableRow className="border-b bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/75 shadow-[inset_0_-1px_0_rgba(0,0,0,0.1)]">
347
- <TableHead>{t('documentPanel.documentManager.columns.id')}</TableHead>
 
 
 
 
 
 
 
 
 
 
 
 
348
  <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
349
  <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
350
  <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
351
  <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
352
- <TableHead>{t('documentPanel.documentManager.columns.created')}</TableHead>
353
- <TableHead>{t('documentPanel.documentManager.columns.updated')}</TableHead>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  </TableRow>
355
  </TableHeader>
356
  <TableBody className="text-sm overflow-auto">
357
- {Object.entries(docs.statuses).map(([status, documents]) =>
358
- documents.map((doc) => (
 
 
 
359
  <TableRow key={doc.id}>
360
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
361
  {showFileName ? (
@@ -415,8 +509,8 @@ export default function DocumentManager() {
415
  {new Date(doc.updated_at).toLocaleString()}
416
  </TableCell>
417
  </TableRow>
418
- ))
419
- )}
420
  </TableBody>
421
  </Table>
422
  </div>
 
21
  import { toast } from 'sonner'
22
  import { useBackendState } from '@/stores/state'
23
 
24
+ import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
25
  import { DocStatusResponse } from '@/api/lightrag'
26
  import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
27
 
 
99
  }
100
  `;
101
 
102
+ // Type definitions for sort field and direction
103
+ type SortField = 'created_at' | 'updated_at' | 'id';
104
+ type SortDirection = 'asc' | 'desc';
105
+
106
  export default function DocumentManager() {
107
  const [showPipelineStatus, setShowPipelineStatus] = useState(false)
108
  const { t } = useTranslation()
 
112
  const currentTab = useSettingsStore.use.currentTab()
113
  const showFileName = useSettingsStore.use.showFileName()
114
  const setShowFileName = useSettingsStore.use.setShowFileName()
115
+
116
+ // Sort state
117
+ const [sortField, setSortField] = useState<SortField>('updated_at')
118
+ const [sortDirection, setSortDirection] = useState<SortDirection>('desc')
119
+
120
+ // Handle sort column click
121
+ const handleSort = (field: SortField) => {
122
+ if (sortField === field) {
123
+ // Toggle sort direction if clicking the same field
124
+ setSortDirection(prev => prev === 'asc' ? 'desc' : 'asc')
125
+ } else {
126
+ // Set new sort field with default desc direction
127
+ setSortField(field)
128
+ setSortDirection('desc')
129
+ }
130
+ }
131
+
132
+ // Sort documents based on current sort field and direction
133
+ const sortDocuments = (documents: DocStatusResponse[]) => {
134
+ return [...documents].sort((a, b) => {
135
+ let valueA, valueB;
136
+
137
+ // Special handling for ID field based on showFileName setting
138
+ if (sortField === 'id' && showFileName) {
139
+ valueA = getDisplayFileName(a);
140
+ valueB = getDisplayFileName(b);
141
+ } else if (sortField === 'id') {
142
+ valueA = a.id;
143
+ valueB = b.id;
144
+ } else {
145
+ // Date fields
146
+ valueA = new Date(a[sortField]).getTime();
147
+ valueB = new Date(b[sortField]).getTime();
148
+ }
149
+
150
+ // Apply sort direction
151
+ const sortMultiplier = sortDirection === 'asc' ? 1 : -1;
152
+
153
+ // Compare values
154
+ if (typeof valueA === 'string' && typeof valueB === 'string') {
155
+ return sortMultiplier * valueA.localeCompare(valueB);
156
+ } else {
157
+ return sortMultiplier * (valueA > valueB ? 1 : valueA < valueB ? -1 : 0);
158
+ }
159
+ });
160
+ }
161
 
162
  // Store previous status counts
163
  const prevStatusCounts = useRef({
 
318
  return () => clearInterval(interval)
319
  }, [health, fetchDocuments, t, currentTab])
320
 
321
+ // Add dependency on sort state to re-render when sort changes
322
+ useEffect(() => {
323
+ // This effect ensures the component re-renders when sort state changes
324
+ }, [sortField, sortDirection]);
325
+
326
  return (
327
  <Card className="!rounded-none !overflow-hidden flex flex-col h-full min-h-0">
328
  <CardHeader className="py-2 px-6">
 
399
  <Table className="w-full">
400
  <TableHeader className="sticky top-0 bg-background z-10 shadow-sm">
401
  <TableRow className="border-b bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/75 shadow-[inset_0_-1px_0_rgba(0,0,0,0.1)]">
402
+ <TableHead
403
+ onClick={() => handleSort('id')}
404
+ className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none"
405
+ >
406
+ <div className="flex items-center">
407
+ {t('documentPanel.documentManager.columns.id')}
408
+ {sortField === 'id' && (
409
+ <span className="ml-1">
410
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
411
+ </span>
412
+ )}
413
+ </div>
414
+ </TableHead>
415
  <TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
416
  <TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
417
  <TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
418
  <TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
419
+ <TableHead
420
+ onClick={() => handleSort('created_at')}
421
+ className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none"
422
+ >
423
+ <div className="flex items-center">
424
+ {t('documentPanel.documentManager.columns.created')}
425
+ {sortField === 'created_at' && (
426
+ <span className="ml-1">
427
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
428
+ </span>
429
+ )}
430
+ </div>
431
+ </TableHead>
432
+ <TableHead
433
+ onClick={() => handleSort('updated_at')}
434
+ className="cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 select-none"
435
+ >
436
+ <div className="flex items-center">
437
+ {t('documentPanel.documentManager.columns.updated')}
438
+ {sortField === 'updated_at' && (
439
+ <span className="ml-1">
440
+ {sortDirection === 'asc' ? <ArrowUpIcon size={14} /> : <ArrowDownIcon size={14} />}
441
+ </span>
442
+ )}
443
+ </div>
444
+ </TableHead>
445
  </TableRow>
446
  </TableHeader>
447
  <TableBody className="text-sm overflow-auto">
448
+ {Object.entries(docs.statuses).flatMap(([status, documents]) => {
449
+ // Apply sorting to documents
450
+ const sortedDocuments = sortDocuments(documents);
451
+
452
+ return sortedDocuments.map(doc => (
453
  <TableRow key={doc.id}>
454
  <TableCell className="truncate font-mono overflow-visible max-w-[250px]">
455
  {showFileName ? (
 
509
  {new Date(doc.updated_at).toLocaleString()}
510
  </TableCell>
511
  </TableRow>
512
+ ));
513
+ })}
514
  </TableBody>
515
  </Table>
516
  </div>