feat: add extraction deletion functionality with confirmation dialog and update extraction list on deletion
Some checks failed
Frontend CI / lint (push) Failing after 18s
Frontend CI / build (push) Has been skipped

This commit is contained in:
JSC
2025-08-25 21:40:47 +02:00
parent 4a973e5044
commit 6a40311a82
4 changed files with 135 additions and 4 deletions

View File

@@ -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>
</>
)
}

View File

@@ -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>

View File

@@ -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()

View File

@@ -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}