feat: enhance document list navigation and sorting functionality (#23383)

This commit is contained in:
yyh
2025-08-05 10:19:47 +08:00
committed by GitHub
parent a724f35672
commit d8584dc03a
8 changed files with 966 additions and 28 deletions

View File

@@ -139,7 +139,12 @@ const DocumentDetail: FC<Props> = ({ datasetId, documentId }) => {
})
const backToPrev = () => {
router.push(`/datasets/${datasetId}/documents`)
// Preserve pagination and filter states when navigating back
const searchParams = new URLSearchParams(window.location.search)
const queryString = searchParams.toString()
const separator = queryString ? '?' : ''
const backPath = `/datasets/${datasetId}/documents${separator}${queryString}`
router.push(backPath)
}
const isDetailLoading = !documentDetail && !error

View File

@@ -18,7 +18,6 @@ import {
import { useContext } from 'use-context-selector'
import { useRouter } from 'next/navigation'
import { useTranslation } from 'react-i18next'
import dayjs from 'dayjs'
import { Globe01 } from '../../base/icons/src/vender/line/mapsAndTravel'
import ChunkingModeLabel from '../common/chunking-mode-label'
import FileTypeIcon from '../../base/file-uploader/file-type-icon'
@@ -99,7 +98,6 @@ export const StatusItem: FC<{
const { mutateAsync: enableDocument } = useDocumentEnable()
const { mutateAsync: disableDocument } = useDocumentDisable()
const { mutateAsync: deleteDocument } = useDocumentDelete()
const downloadDocument = useDocumentDownload()
const onOperate = async (operationName: OperationName) => {
let opApi = deleteDocument
@@ -313,9 +311,9 @@ export const OperationAction: FC<{
downloadDocument.mutateAsync({
datasetId,
documentId: detail.id,
}).then((response) => {
if (response.download_url)
window.location.href = response.download_url
}).then((response) => {
if (response.download_url)
window.location.href = response.download_url
}).catch((error) => {
console.error(error)
notify({ type: 'error', message: t('common.actionMsg.downloadFailed') })
@@ -478,7 +476,8 @@ const DocumentList: FC<IDocumentListProps> = ({
const isGeneralMode = chunkingMode !== ChunkingMode.parentChild
const isQAMode = chunkingMode === ChunkingMode.qa
const [localDocs, setLocalDocs] = useState<LocalDoc[]>(documents)
const [enableSort, setEnableSort] = useState(true)
const [sortField, setSortField] = useState<'name' | 'word_count' | 'hit_count' | 'created_at' | null>('created_at')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
const {
isShowEditModal,
showEditModal,
@@ -493,18 +492,74 @@ const DocumentList: FC<IDocumentListProps> = ({
})
useEffect(() => {
setLocalDocs(documents)
}, [documents])
const onClickSort = () => {
setEnableSort(!enableSort)
if (enableSort) {
const sortedDocs = [...localDocs].sort((a, b) => dayjs(a.created_at).isBefore(dayjs(b.created_at)) ? -1 : 1)
setLocalDocs(sortedDocs)
}
else {
if (!sortField) {
setLocalDocs(documents)
return
}
const sortedDocs = [...documents].sort((a, b) => {
let aValue: any
let bValue: any
switch (sortField) {
case 'name':
aValue = a.name?.toLowerCase() || ''
bValue = b.name?.toLowerCase() || ''
break
case 'word_count':
aValue = a.word_count || 0
bValue = b.word_count || 0
break
case 'hit_count':
aValue = a.hit_count || 0
bValue = b.hit_count || 0
break
case 'created_at':
aValue = a.created_at
bValue = b.created_at
break
default:
return 0
}
if (sortField === 'name') {
const result = aValue.localeCompare(bValue)
return sortOrder === 'asc' ? result : -result
}
else {
const result = aValue - bValue
return sortOrder === 'asc' ? result : -result
}
})
setLocalDocs(sortedDocs)
}, [documents, sortField, sortOrder])
const handleSort = (field: 'name' | 'word_count' | 'hit_count' | 'created_at') => {
if (sortField === field) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
}
else {
setSortField(field)
setSortOrder('desc')
}
}
const renderSortHeader = (field: 'name' | 'word_count' | 'hit_count' | 'created_at', label: string) => {
const isActive = sortField === field
const isDesc = isActive && sortOrder === 'desc'
return (
<div className='flex cursor-pointer items-center hover:text-text-secondary' onClick={() => handleSort(field)}>
{label}
<ArrowDownIcon
className={cn('ml-0.5 h-3 w-3 stroke-current stroke-2 transition-all',
isActive ? 'text-text-tertiary' : 'text-text-disabled',
isActive && !isDesc ? 'rotate-180' : '',
)}
/>
</div>
)
}
const [currDocument, setCurrDocument] = useState<LocalDoc | null>(null)
@@ -586,18 +641,17 @@ const DocumentList: FC<IDocumentListProps> = ({
</div>
</td>
<td>
<div className='flex'>
{t('datasetDocuments.list.table.header.fileName')}
</div>
{renderSortHeader('name', t('datasetDocuments.list.table.header.fileName'))}
</td>
<td className='w-[130px]'>{t('datasetDocuments.list.table.header.chunkingMode')}</td>
<td className='w-24'>{t('datasetDocuments.list.table.header.words')}</td>
<td className='w-44'>{t('datasetDocuments.list.table.header.hitCount')}</td>
<td className='w-24'>
{renderSortHeader('word_count', t('datasetDocuments.list.table.header.words'))}
</td>
<td className='w-44'>
<div className='flex items-center' onClick={onClickSort}>
{t('datasetDocuments.list.table.header.uploadTime')}
<ArrowDownIcon className={cn('ml-0.5 h-3 w-3 cursor-pointer stroke-current stroke-2', enableSort ? 'text-text-tertiary' : 'text-text-disabled')} />
</div>
{renderSortHeader('hit_count', t('datasetDocuments.list.table.header.hitCount'))}
</td>
<td className='w-44'>
{renderSortHeader('created_at', t('datasetDocuments.list.table.header.uploadTime'))}
</td>
<td className='w-40'>{t('datasetDocuments.list.table.header.status')}</td>
<td className='w-20'>{t('datasetDocuments.list.table.header.action')}</td>