import { AppLayout } from '@/components/AppLayout' import { AppPagination } from '@/components/AppPagination' import { CreateExtractionDialog } from '@/components/extractions/CreateExtractionDialog' import { ExtractionsHeader } from '@/components/extractions/ExtractionsHeader' import { ExtractionsEmpty, ExtractionsError, ExtractionsLoading, } from '@/components/extractions/ExtractionsLoadingStates' import { ExtractionsTable } from '@/components/extractions/ExtractionsTable' import { type ExtractionInfo, type ExtractionSortField, type ExtractionSortOrder, type ExtractionStatus, extractionsService, } from '@/lib/api/services/extractions' import { EXTRACTION_EVENTS, extractionEvents } from '@/lib/events' import { useCallback, useEffect, useState } from 'react' import { toast } from 'sonner' export function ExtractionsPage() { const [extractions, setExtractions] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Search, sorting, and filtering state const [searchQuery, setSearchQuery] = useState('') const [sortBy, setSortBy] = useState('created_at') const [sortOrder, setSortOrder] = useState('desc') const [statusFilter, setStatusFilter] = useState('all') // Pagination state const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [totalCount, setTotalCount] = useState(0) const [pageSize, setPageSize] = useState(10) // Create extraction dialog state const [showCreateDialog, setShowCreateDialog] = useState(false) const [createLoading, setCreateLoading] = useState(false) const [url, setUrl] = useState('') // Debounce search query const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(searchQuery) useEffect(() => { const handler = setTimeout(() => { setDebouncedSearchQuery(searchQuery) }, 300) return () => clearTimeout(handler) }, [searchQuery]) const fetchExtractions = useCallback(async () => { try { setLoading(true) setError(null) const response = await extractionsService.getAllExtractions({ search: debouncedSearchQuery.trim() || undefined, sort_by: sortBy, sort_order: sortOrder, status_filter: statusFilter !== 'all' ? statusFilter : undefined, page: currentPage, limit: pageSize, }) setExtractions(response.extractions) setTotalPages(response.total_pages) setTotalCount(response.total) } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fetch extractions' setError(errorMessage) toast.error(errorMessage) } finally { setLoading(false) } }, [debouncedSearchQuery, sortBy, sortOrder, statusFilter, currentPage, pageSize]) useEffect(() => { fetchExtractions() }, [fetchExtractions]) // Reset to page 1 when filters change useEffect(() => { if (currentPage !== 1) { setCurrentPage(1) } }, [debouncedSearchQuery, sortBy, sortOrder, statusFilter, pageSize]) // Listen for extraction events to refresh the list useEffect(() => { const handleExtractionStatusUpdate = () => { fetchExtractions() } const handleExtractionCompleted = () => { fetchExtractions() } const handleExtractionFailed = () => { fetchExtractions() } // Subscribe to extraction events extractionEvents.on(EXTRACTION_EVENTS.EXTRACTION_STATUS_UPDATED, handleExtractionStatusUpdate) extractionEvents.on(EXTRACTION_EVENTS.EXTRACTION_COMPLETED, handleExtractionCompleted) extractionEvents.on(EXTRACTION_EVENTS.EXTRACTION_FAILED, handleExtractionFailed) return () => { // Cleanup event listeners extractionEvents.off(EXTRACTION_EVENTS.EXTRACTION_STATUS_UPDATED, handleExtractionStatusUpdate) extractionEvents.off(EXTRACTION_EVENTS.EXTRACTION_COMPLETED, handleExtractionCompleted) extractionEvents.off(EXTRACTION_EVENTS.EXTRACTION_FAILED, handleExtractionFailed) } }, [fetchExtractions]) const handlePageChange = (page: number) => { setCurrentPage(page) } const handlePageSizeChange = (size: number) => { setPageSize(size) setCurrentPage(1) // Reset to first page when changing page size } const handleCreateExtraction = async () => { if (!url.trim()) { toast.error('Please enter a URL') return } try { setCreateLoading(true) const response = await extractionsService.createExtraction(url.trim()) toast.success(response.message) // Reset form and close dialog setUrl('') setShowCreateDialog(false) // Emit event for new extraction created extractionEvents.emit(EXTRACTION_EVENTS.EXTRACTION_CREATED, response.extraction) // Refresh the extractions list fetchExtractions() } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to create extraction' toast.error(errorMessage) } finally { setCreateLoading(false) } } const handleCancelCreate = () => { setUrl('') setShowCreateDialog(false) } const handleExtractionDeleted = (extractionId: number) => { // Remove the deleted extraction from the current list setExtractions(prev => prev.filter(extraction => extraction.id !== extractionId)) // Update total count setTotalCount(prev => prev - 1) // If current page is now empty and not the first page, go to previous page const remainingOnCurrentPage = extractions.length - 1 if (remainingOnCurrentPage === 0 && currentPage > 1) { setCurrentPage(currentPage - 1) } // Refresh the full list to ensure accuracy fetchExtractions() } const renderContent = () => { if (loading) { return } if (error) { return } if (extractions.length === 0) { return } return (
) } return (
setShowCreateDialog(true)} loading={loading} error={error} extractionCount={totalCount} /> {renderContent()}
) }