video-flow-b/components/pages/history-page.tsx
2025-06-19 17:15:03 +08:00

331 lines
12 KiB
TypeScript

"use client";
import { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { Progress } from '@/components/ui/progress';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Search,
Filter,
MoreHorizontal,
Play,
Download,
Trash2,
RefreshCw,
Clock,
CheckCircle,
XCircle,
Eye,
} from 'lucide-react';
const mockTasks = [
{
id: 1,
title: 'Tech Product Demo',
status: 'completed',
progress: 100,
duration: '2:45',
createdAt: '2024-01-15T10:30:00Z',
completedAt: '2024-01-15T11:15:00Z',
chapters: 4,
actors: ['Sarah Chen', 'Dr. Marcus Webb'],
thumbnail: 'https://images.pexels.com/photos/3861969/pexels-photo-3861969.jpeg?auto=compress&cs=tinysrgb&w=300',
},
{
id: 2,
title: 'Marketing Campaign Video',
status: 'processing',
progress: 65,
duration: '1:30',
createdAt: '2024-01-14T15:20:00Z',
completedAt: null,
chapters: 3,
actors: ['Alex Rivera'],
thumbnail: 'https://images.pexels.com/photos/3184287/pexels-photo-3184287.jpeg?auto=compress&cs=tinysrgb&w=300',
},
{
id: 3,
title: 'Educational Content Series',
status: 'completed',
progress: 100,
duration: '5:20',
createdAt: '2024-01-12T09:00:00Z',
completedAt: '2024-01-12T10:30:00Z',
chapters: 6,
actors: ['Dr. Marcus Webb', 'Sarah Chen'],
thumbnail: 'https://images.pexels.com/photos/3184465/pexels-photo-3184465.jpeg?auto=compress&cs=tinysrgb&w=300',
},
{
id: 4,
title: 'Company Introduction',
status: 'failed',
progress: 0,
duration: '0:00',
createdAt: '2024-01-10T14:45:00Z',
completedAt: null,
chapters: 2,
actors: ['Sarah Chen'],
thumbnail: null,
},
{
id: 5,
title: 'Quarterly Report Presentation',
status: 'processing',
progress: 25,
duration: '3:15',
createdAt: '2024-01-09T11:00:00Z',
completedAt: null,
chapters: 5,
actors: ['Dr. Marcus Webb', 'Alex Rivera'],
thumbnail: 'https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg?auto=compress&cs=tinysrgb&w=300',
},
];
const statusConfig = {
completed: {
icon: CheckCircle,
color: 'text-green-600',
bgColor: 'bg-green-100 dark:bg-green-900/20',
label: 'Completed',
},
processing: {
icon: RefreshCw,
color: 'text-blue-600',
bgColor: 'bg-blue-100 dark:bg-blue-900/20',
label: 'Processing',
},
failed: {
icon: XCircle,
color: 'text-red-600',
bgColor: 'bg-red-100 dark:bg-red-900/20',
label: 'Failed',
},
};
export function HistoryPage() {
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState<string>('all');
const filteredTasks = mockTasks.filter(task => {
const matchesSearch = task.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
task.actors.some(actor => actor.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesStatus = statusFilter === 'all' || task.status === statusFilter;
return matchesSearch && matchesStatus;
});
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleString();
};
const getTimeSince = (dateString: string) => {
const now = new Date();
const date = new Date(dateString);
const diffInHours = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60));
if (diffInHours < 1) return 'Less than an hour ago';
if (diffInHours < 24) return `${diffInHours} hours ago`;
const diffInDays = Math.floor(diffInHours / 24);
return `${diffInDays} days ago`;
};
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Task History</h1>
<p className="text-muted-foreground">
Track the progress and manage your video generation tasks
</p>
</div>
</div>
{/* Filters */}
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
placeholder="Search tasks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline">
<Filter className="mr-2 h-4 w-4" />
Status: {statusFilter === 'all' ? 'All' : statusConfig[statusFilter as keyof typeof statusConfig]?.label}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => setStatusFilter('all')}>
All Status
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter('completed')}>
Completed
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter('processing')}>
Processing
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setStatusFilter('failed')}>
Failed
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<Badge variant="secondary">
{filteredTasks.length} tasks
</Badge>
</div>
</CardContent>
</Card>
{/* Tasks List */}
<div className="space-y-4">
{filteredTasks.map((task) => {
const StatusIcon = statusConfig[task.status as keyof typeof statusConfig].icon;
const statusProps = statusConfig[task.status as keyof typeof statusConfig];
return (
<Card key={task.id} className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<div className="flex items-center space-x-4">
{/* Thumbnail */}
<div className="relative w-24 h-16 bg-muted rounded-lg overflow-hidden flex-shrink-0">
{task.thumbnail ? (
<img
src={task.thumbnail}
alt={task.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<span className="text-2xl">🎬</span>
</div>
)}
{task.status === 'processing' && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
<RefreshCw className="h-4 w-4 text-white animate-spin" />
</div>
)}
</div>
{/* Task Info */}
<div className="flex-1 space-y-2">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-lg">{task.title}</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{task.status === 'completed' && (
<>
<DropdownMenuItem>
<Play className="mr-2 h-4 w-4" />
Preview
</DropdownMenuItem>
<DropdownMenuItem>
<Download className="mr-2 h-4 w-4" />
Download
</DropdownMenuItem>
</>
)}
{task.status === 'failed' && (
<DropdownMenuItem>
<RefreshCw className="mr-2 h-4 w-4" />
Retry
</DropdownMenuItem>
)}
<DropdownMenuItem>
<Eye className="mr-2 h-4 w-4" />
View Details
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center space-x-4 text-sm">
<Badge
className={`${statusProps.bgColor} ${statusProps.color} border-none`}
>
<StatusIcon className="mr-1 h-3 w-3" />
{statusProps.label}
</Badge>
<div className="flex items-center text-muted-foreground">
<Clock className="mr-1 h-3 w-3" />
{task.duration}
</div>
<span className="text-muted-foreground">
{task.chapters} chapters
</span>
<span className="text-muted-foreground">
{task.actors.join(', ')}
</span>
</div>
{task.status === 'processing' && (
<div className="space-y-1">
<div className="flex justify-between text-xs text-muted-foreground">
<span>Processing...</span>
<span>{task.progress}%</span>
</div>
<Progress value={task.progress} className="h-1" />
</div>
)}
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Started {getTimeSince(task.createdAt)}</span>
{task.completedAt && (
<span>Completed {formatDate(task.completedAt)}</span>
)}
</div>
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{filteredTasks.length === 0 && (
<Card className="text-center py-12">
<CardContent>
<div className="space-y-4">
<div className="mx-auto w-16 h-16 rounded-full bg-muted flex items-center justify-center">
<Clock className="h-8 w-8 text-muted-foreground" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-semibold">No tasks found</h3>
<p className="text-muted-foreground">
{searchQuery || statusFilter !== 'all'
? 'No tasks match your current filters. Try adjusting your search.'
: 'You haven\'t created any videos yet. Start by creating your first AI video!'
}
</p>
</div>
</div>
</CardContent>
</Card>
)}
</div>
);
}