157 lines
4.5 KiB
TypeScript
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>
|
|
)
|
|
}
|