Files
sbd2-frontend/src/pages/ExtractionsPage.tsx

157 lines
4.5 KiB
TypeScript

import { AppLayout } from '@/components/AppLayout'
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 { useEffect, useState } from 'react'
import { toast } from 'sonner'
export function ExtractionsPage() {
const [extractions, setExtractions] = useState<ExtractionInfo[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
// Search, sorting, and filtering state
const [searchQuery, setSearchQuery] = useState('')
const [sortBy, setSortBy] = useState<ExtractionSortField>('created_at')
const [sortOrder, setSortOrder] = useState<ExtractionSortOrder>('desc')
const [statusFilter, setStatusFilter] = useState<ExtractionStatus | 'all'>('all')
// 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 = async () => {
try {
setLoading(true)
setError(null)
const data = await extractionsService.getUserExtractions({
search: debouncedSearchQuery.trim() || undefined,
sort_by: sortBy,
sort_order: sortOrder,
status_filter: statusFilter !== 'all' ? statusFilter : undefined,
})
setExtractions(data)
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : 'Failed to fetch extractions'
setError(errorMessage)
toast.error(errorMessage)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchExtractions()
}, [debouncedSearchQuery, sortBy, sortOrder, statusFilter])
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)
// 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 renderContent = () => {
if (loading) {
return <ExtractionsLoading />
}
if (error) {
return <ExtractionsError error={error} onRetry={fetchExtractions} />
}
if (extractions.length === 0) {
return <ExtractionsEmpty searchQuery={searchQuery} statusFilter={statusFilter} />
}
return <ExtractionsTable extractions={extractions} />
}
return (
<AppLayout
breadcrumb={{
items: [{ label: 'Dashboard', href: '/' }, { label: 'Extractions' }],
}}
>
<div className="flex-1 rounded-xl bg-muted/50 p-4">
<ExtractionsHeader
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
sortBy={sortBy}
onSortByChange={setSortBy}
sortOrder={sortOrder}
onSortOrderChange={setSortOrder}
statusFilter={statusFilter}
onStatusFilterChange={setStatusFilter}
onRefresh={fetchExtractions}
onCreateClick={() => setShowCreateDialog(true)}
loading={loading}
error={error}
extractionCount={extractions.length}
/>
<CreateExtractionDialog
open={showCreateDialog}
onOpenChange={setShowCreateDialog}
loading={createLoading}
url={url}
onUrlChange={setUrl}
onSubmit={handleCreateExtraction}
onCancel={handleCancelCreate}
/>
{renderContent()}
</div>
</AppLayout>
)
}