|
1 | 1 | 'use client' |
2 | 2 |
|
3 | | -import { useEffect, useState } from 'react' |
4 | | -import { Circle, CircleOff, FileText, Plus, Search, Trash2, X } from 'lucide-react' |
| 3 | +import { useCallback, useEffect, useState } from 'react' |
| 4 | +import { |
| 5 | + ChevronLeft, |
| 6 | + ChevronRight, |
| 7 | + Circle, |
| 8 | + CircleOff, |
| 9 | + FileText, |
| 10 | + Plus, |
| 11 | + Search, |
| 12 | + Trash2, |
| 13 | + X, |
| 14 | +} from 'lucide-react' |
5 | 15 | import { Button } from '@/components/ui/button' |
6 | 16 | import { Checkbox } from '@/components/ui/checkbox' |
7 | 17 | import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' |
@@ -59,18 +69,53 @@ export function Document({ |
59 | 69 | const [isLoadingDocument, setIsLoadingDocument] = useState(true) |
60 | 70 | const [error, setError] = useState<string | null>(null) |
61 | 71 |
|
62 | | - // Use the new chunks hook |
| 72 | + // Use the updated chunks hook with pagination |
63 | 73 | const { |
64 | 74 | chunks, |
65 | 75 | isLoading: isLoadingChunks, |
66 | 76 | error: chunksError, |
| 77 | + currentPage, |
| 78 | + totalPages, |
| 79 | + hasNextPage, |
| 80 | + hasPrevPage, |
| 81 | + goToPage, |
| 82 | + nextPage, |
| 83 | + prevPage, |
67 | 84 | refreshChunks, |
68 | 85 | updateChunk, |
69 | 86 | } = useDocumentChunks(knowledgeBaseId, documentId) |
70 | 87 |
|
71 | 88 | // Combine errors |
72 | 89 | const combinedError = error || chunksError |
73 | 90 |
|
| 91 | + // Handle pagination navigation |
| 92 | + const handlePrevPage = useCallback(() => { |
| 93 | + if (hasPrevPage && !isLoadingChunks) { |
| 94 | + prevPage()?.catch((err) => { |
| 95 | + logger.error('Previous page failed:', err) |
| 96 | + }) |
| 97 | + } |
| 98 | + }, [hasPrevPage, isLoadingChunks, prevPage]) |
| 99 | + |
| 100 | + const handleNextPage = useCallback(() => { |
| 101 | + if (hasNextPage && !isLoadingChunks) { |
| 102 | + nextPage()?.catch((err) => { |
| 103 | + logger.error('Next page failed:', err) |
| 104 | + }) |
| 105 | + } |
| 106 | + }, [hasNextPage, isLoadingChunks, nextPage]) |
| 107 | + |
| 108 | + const handleGoToPage = useCallback( |
| 109 | + (page: number) => { |
| 110 | + if (page !== currentPage && !isLoadingChunks) { |
| 111 | + goToPage(page)?.catch((err) => { |
| 112 | + logger.error('Go to page failed:', err) |
| 113 | + }) |
| 114 | + } |
| 115 | + }, |
| 116 | + [currentPage, isLoadingChunks, goToPage] |
| 117 | + ) |
| 118 | + |
74 | 119 | // Try to get document from store cache first, then fetch if needed |
75 | 120 | useEffect(() => { |
76 | 121 | const fetchDocument = async () => { |
@@ -308,7 +353,7 @@ export function Document({ |
308 | 353 | onClick={() => setIsCreateChunkModalOpen(true)} |
309 | 354 | disabled={document?.processingStatus === 'failed'} |
310 | 355 | size='sm' |
311 | | - className='flex items-center gap-1 bg-[#701FFC] font-[480] text-primary-foreground shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:cursor-not-allowed disabled:opacity-50' |
| 356 | + className='flex items-center gap-1 bg-[#701FFC] font-[480] text-white shadow-[0_0_0_0_#701FFC] transition-all duration-200 hover:bg-[#6518E6] hover:shadow-[0_0_0_4px_rgba(127,47,255,0.15)] disabled:cursor-not-allowed disabled:opacity-50' |
312 | 357 | > |
313 | 358 | <Plus className='h-3.5 w-3.5' /> |
314 | 359 | <span>Create Chunk</span> |
@@ -566,6 +611,64 @@ export function Document({ |
566 | 611 | </tbody> |
567 | 612 | </table> |
568 | 613 | </div> |
| 614 | + |
| 615 | + {/* Pagination Controls */} |
| 616 | + {document?.processingStatus === 'completed' && totalPages > 1 && ( |
| 617 | + <div className='flex items-center justify-center border-t bg-background px-6 py-4'> |
| 618 | + <div className='flex items-center gap-1'> |
| 619 | + <Button |
| 620 | + variant='ghost' |
| 621 | + size='sm' |
| 622 | + onClick={handlePrevPage} |
| 623 | + disabled={!hasPrevPage || isLoadingChunks} |
| 624 | + className='h-8 w-8 p-0' |
| 625 | + > |
| 626 | + <ChevronLeft className='h-4 w-4' /> |
| 627 | + </Button> |
| 628 | + |
| 629 | + {/* Page numbers - show a few around current page */} |
| 630 | + <div className='mx-4 flex items-center gap-6'> |
| 631 | + {Array.from({ length: Math.min(totalPages, 5) }, (_, i) => { |
| 632 | + let page: number |
| 633 | + if (totalPages <= 5) { |
| 634 | + page = i + 1 |
| 635 | + } else if (currentPage <= 3) { |
| 636 | + page = i + 1 |
| 637 | + } else if (currentPage >= totalPages - 2) { |
| 638 | + page = totalPages - 4 + i |
| 639 | + } else { |
| 640 | + page = currentPage - 2 + i |
| 641 | + } |
| 642 | + |
| 643 | + if (page < 1 || page > totalPages) return null |
| 644 | + |
| 645 | + return ( |
| 646 | + <button |
| 647 | + key={page} |
| 648 | + onClick={() => handleGoToPage(page)} |
| 649 | + disabled={isLoadingChunks} |
| 650 | + className={`font-medium text-sm transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50 ${ |
| 651 | + page === currentPage ? 'text-foreground' : 'text-muted-foreground' |
| 652 | + }`} |
| 653 | + > |
| 654 | + {page} |
| 655 | + </button> |
| 656 | + ) |
| 657 | + })} |
| 658 | + </div> |
| 659 | + |
| 660 | + <Button |
| 661 | + variant='ghost' |
| 662 | + size='sm' |
| 663 | + onClick={handleNextPage} |
| 664 | + disabled={!hasNextPage || isLoadingChunks} |
| 665 | + className='h-8 w-8 p-0' |
| 666 | + > |
| 667 | + <ChevronRight className='h-4 w-4' /> |
| 668 | + </Button> |
| 669 | + </div> |
| 670 | + </div> |
| 671 | + )} |
569 | 672 | </div> |
570 | 673 | </div> |
571 | 674 | </div> |
|
0 commit comments