135 lines
4.1 KiB
TypeScript
135 lines
4.1 KiB
TypeScript
import { Badge } from '@/components/ui/badge'
|
|
import { Button } from '@/components/ui/button'
|
|
import { TableCell, TableRow } from '@/components/ui/table'
|
|
import type { ExtractionInfo } from '@/lib/api/services/extractions'
|
|
import { formatDateDistanceToNow } from '@/utils/format-date'
|
|
import {
|
|
AlertCircle,
|
|
Calendar,
|
|
CheckCircle,
|
|
Clock,
|
|
ExternalLink,
|
|
Loader2,
|
|
User
|
|
} from 'lucide-react'
|
|
|
|
interface ExtractionsRowProps {
|
|
extraction: ExtractionInfo
|
|
}
|
|
|
|
export function ExtractionsRow({ extraction }: ExtractionsRowProps) {
|
|
const getStatusBadge = (status: ExtractionInfo['status']) => {
|
|
switch (status) {
|
|
case 'pending':
|
|
return (
|
|
<Badge variant="secondary" className="gap-1">
|
|
<Clock className="h-3 w-3" />
|
|
Pending
|
|
</Badge>
|
|
)
|
|
case 'processing':
|
|
return (
|
|
<Badge variant="outline" className="gap-1">
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
Processing
|
|
</Badge>
|
|
)
|
|
case 'completed':
|
|
return (
|
|
<Badge variant="default" className="gap-1">
|
|
<CheckCircle className="h-3 w-3" />
|
|
Completed
|
|
</Badge>
|
|
)
|
|
case 'failed':
|
|
return (
|
|
<Badge variant="destructive" className="gap-1">
|
|
<AlertCircle className="h-3 w-3" />
|
|
Failed
|
|
</Badge>
|
|
)
|
|
}
|
|
}
|
|
|
|
const getServiceBadge = (service: string | undefined) => {
|
|
if (!service) return <span className="text-muted-foreground">-</span>
|
|
|
|
const serviceColors: Record<string, string> = {
|
|
youtube: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300',
|
|
soundcloud: 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300',
|
|
vimeo: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300',
|
|
tiktok: 'bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300',
|
|
twitter: 'bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-300',
|
|
instagram: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300',
|
|
}
|
|
|
|
const colorClass =
|
|
serviceColors[service.toLowerCase()] ||
|
|
'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300'
|
|
|
|
return (
|
|
<Badge variant="outline" className={colorClass}>
|
|
{service.toUpperCase()}
|
|
</Badge>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<TableRow className="hover:bg-muted/50">
|
|
<TableCell>
|
|
<div className="min-w-0">
|
|
<div className="font-medium truncate">
|
|
{extraction.title || 'Extracting...'}
|
|
</div>
|
|
<div className="text-sm text-muted-foreground truncate max-w-64">
|
|
{extraction.url}
|
|
</div>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-center">
|
|
{getServiceBadge(extraction.service)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-2">
|
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">
|
|
{extraction.user_name || 'Unknown'}
|
|
</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="space-y-1">
|
|
{getStatusBadge(extraction.status)}
|
|
{extraction.error && (
|
|
<div
|
|
className="text-xs text-destructive max-w-48 truncate"
|
|
title={extraction.error}
|
|
>
|
|
{extraction.error}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
<Calendar className="h-3 w-3" />
|
|
{formatDateDistanceToNow(extraction.created_at)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell className="text-center">
|
|
<div className="flex items-center justify-center gap-1">
|
|
<Button variant="ghost" size="sm" asChild>
|
|
<a
|
|
href={extraction.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
title="Open original URL"
|
|
>
|
|
<ExternalLink className="h-4 w-4" />
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
)
|
|
} |