feat: add extraction deletion functionality with confirmation dialog and update extraction list on deletion
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { TableCell, TableRow } from '@/components/ui/table'
|
||||
import type { ExtractionInfo } from '@/lib/api/services/extractions'
|
||||
import { extractionsService, type ExtractionInfo } from '@/lib/api/services/extractions'
|
||||
import { formatDateDistanceToNow } from '@/utils/format-date'
|
||||
import {
|
||||
AlertCircle,
|
||||
@@ -10,14 +18,44 @@ import {
|
||||
Clock,
|
||||
ExternalLink,
|
||||
Loader2,
|
||||
Trash2,
|
||||
User
|
||||
} from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface ExtractionsRowProps {
|
||||
extraction: ExtractionInfo
|
||||
onExtractionDeleted?: (extractionId: number) => void
|
||||
}
|
||||
|
||||
export function ExtractionsRow({ extraction }: ExtractionsRowProps) {
|
||||
export function ExtractionsRow({ extraction, onExtractionDeleted }: ExtractionsRowProps) {
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
|
||||
const [deleteLoading, setDeleteLoading] = useState(false)
|
||||
|
||||
const handleDeleteExtraction = async () => {
|
||||
if (!extraction.id) return
|
||||
|
||||
try {
|
||||
setDeleteLoading(true)
|
||||
const response = await extractionsService.deleteExtraction(extraction.id)
|
||||
toast.success(response.message)
|
||||
|
||||
// Close dialog
|
||||
setShowDeleteDialog(false)
|
||||
|
||||
// Notify parent component
|
||||
if (onExtractionDeleted) {
|
||||
onExtractionDeleted(extraction.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : 'Failed to delete extraction'
|
||||
toast.error(errorMessage)
|
||||
} finally {
|
||||
setDeleteLoading(false)
|
||||
}
|
||||
}
|
||||
const getStatusBadge = (status: ExtractionInfo['status']) => {
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
@@ -75,6 +113,7 @@ export function ExtractionsRow({ extraction }: ExtractionsRowProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow className="hover:bg-muted/50">
|
||||
<TableCell>
|
||||
<div className="min-w-0">
|
||||
@@ -128,8 +167,64 @@ export function ExtractionsRow({ extraction }: ExtractionsRowProps) {
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowDeleteDialog(true)}
|
||||
title="Delete extraction"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Extraction</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this extraction? This action cannot be undone.
|
||||
{extraction.title && (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
<strong>Title:</strong> {extraction.title}
|
||||
<br />
|
||||
<strong>URL:</strong> {extraction.url}
|
||||
</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteExtraction}
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
{deleteLoading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -10,9 +10,10 @@ import type { ExtractionInfo } from '@/lib/api/services/extractions'
|
||||
|
||||
interface ExtractionsTableProps {
|
||||
extractions: ExtractionInfo[]
|
||||
onExtractionDeleted?: (extractionId: number) => void
|
||||
}
|
||||
|
||||
export function ExtractionsTable({ extractions }: ExtractionsTableProps) {
|
||||
export function ExtractionsTable({ extractions, onExtractionDeleted }: ExtractionsTableProps) {
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
@@ -31,6 +32,7 @@ export function ExtractionsTable({ extractions }: ExtractionsTableProps) {
|
||||
<ExtractionsRow
|
||||
key={extraction.id}
|
||||
extraction={extraction}
|
||||
onExtractionDeleted={onExtractionDeleted}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
@@ -41,6 +41,10 @@ export interface GetExtractionsParams {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface DeleteExtractionResponse {
|
||||
message: string
|
||||
}
|
||||
|
||||
export class ExtractionsService {
|
||||
/**
|
||||
* Create a new extraction job
|
||||
@@ -135,6 +139,16 @@ export class ExtractionsService {
|
||||
)
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an extraction
|
||||
*/
|
||||
async deleteExtraction(extractionId: number): Promise<DeleteExtractionResponse> {
|
||||
const response = await apiClient.delete<DeleteExtractionResponse>(
|
||||
`/api/v1/extractions/${extractionId}`
|
||||
)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export const extractionsService = new ExtractionsService()
|
||||
|
||||
@@ -159,6 +159,23 @@ export function ExtractionsPage() {
|
||||
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 <ExtractionsLoading />
|
||||
@@ -174,7 +191,10 @@ export function ExtractionsPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<ExtractionsTable extractions={extractions} />
|
||||
<ExtractionsTable
|
||||
extractions={extractions}
|
||||
onExtractionDeleted={handleExtractionDeleted}
|
||||
/>
|
||||
<AppPagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
|
||||
Reference in New Issue
Block a user