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

391 lines
14 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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Plus,
Upload,
Search,
FolderPlus,
MoreHorizontal,
Play,
Download,
Trash2,
Eye,
Grid3X3,
List,
} from 'lucide-react';
const mockAlbums = [
{
id: 1,
name: 'Tech Videos',
itemCount: 12,
thumbnail: 'https://images.pexels.com/photos/3861969/pexels-photo-3861969.jpeg?auto=compress&cs=tinysrgb&w=300',
createdAt: '2024-01-15',
},
{
id: 2,
name: 'Marketing Assets',
itemCount: 8,
thumbnail: 'https://images.pexels.com/photos/3184287/pexels-photo-3184287.jpeg?auto=compress&cs=tinysrgb&w=300',
createdAt: '2024-01-10',
},
{
id: 3,
name: 'Stock Footage',
itemCount: 24,
thumbnail: 'https://images.pexels.com/photos/3184465/pexels-photo-3184465.jpeg?auto=compress&cs=tinysrgb&w=300',
createdAt: '2024-01-05',
},
];
const mockMediaFiles = [
{
id: 1,
name: 'intro-video.mp4',
type: 'video',
size: '45.2 MB',
duration: '2:30',
thumbnail: 'https://images.pexels.com/photos/3861969/pexels-photo-3861969.jpeg?auto=compress&cs=tinysrgb&w=300',
albumId: 1,
uploadedAt: '2024-01-15',
},
{
id: 2,
name: 'product-demo.mp4',
type: 'video',
size: '78.9 MB',
duration: '4:15',
thumbnail: 'https://images.pexels.com/photos/3184287/pexels-photo-3184287.jpeg?auto=compress&cs=tinysrgb&w=300',
albumId: 1,
uploadedAt: '2024-01-14',
},
{
id: 3,
name: 'background-music.mp3',
type: 'audio',
size: '8.3 MB',
duration: '3:45',
thumbnail: null,
albumId: 2,
uploadedAt: '2024-01-12',
},
{
id: 4,
name: 'hero-image.jpg',
type: 'image',
size: '2.1 MB',
duration: null,
thumbnail: 'https://images.pexels.com/photos/3184465/pexels-photo-3184465.jpeg?auto=compress&cs=tinysrgb&w=300',
albumId: 2,
uploadedAt: '2024-01-10',
},
];
export function MediaLibraryPage() {
const [searchQuery, setSearchQuery] = useState('');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [selectedAlbum, setSelectedAlbum] = useState<number | null>(null);
const filteredFiles = mockMediaFiles.filter(file =>
file.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
(selectedAlbum ? file.albumId === selectedAlbum : true)
);
const getFileIcon = (type: string) => {
switch (type) {
case 'video':
return '🎥';
case 'audio':
return '🎵';
case 'image':
return '🖼️';
default:
return '📄';
}
};
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold">Media Library</h1>
<p className="text-muted-foreground">
Organize and manage your video, audio, and image assets
</p>
</div>
<div className="flex space-x-3">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">
<FolderPlus className="mr-2 h-4 w-4" />
New Album
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Album</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<Input placeholder="Album name" />
<div className="flex justify-end space-x-2">
<Button variant="outline">Cancel</Button>
<Button>Create Album</Button>
</div>
</div>
</DialogContent>
</Dialog>
<Button>
<Upload className="mr-2 h-4 w-4" />
Upload Files
</Button>
</div>
</div>
{/* Search and 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 files..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 w-64"
/>
</div>
<Button
variant={selectedAlbum ? 'secondary' : 'ghost'}
onClick={() => setSelectedAlbum(null)}
>
All Files
</Button>
{mockAlbums.map((album) => (
<Button
key={album.id}
variant={selectedAlbum === album.id ? 'secondary' : 'ghost'}
onClick={() => setSelectedAlbum(album.id)}
>
{album.name}
</Button>
))}
</div>
<div className="flex items-center space-x-2">
<Button
variant={viewMode === 'grid' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setViewMode('grid')}
>
<Grid3X3 className="h-4 w-4" />
</Button>
<Button
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
size="sm"
onClick={() => setViewMode('list')}
>
<List className="h-4 w-4" />
</Button>
</div>
</div>
</CardContent>
</Card>
<Tabs defaultValue="files" className="space-y-6">
<TabsList>
<TabsTrigger value="files">Files</TabsTrigger>
<TabsTrigger value="albums">Albums</TabsTrigger>
</TabsList>
<TabsContent value="files" className="space-y-6">
{viewMode === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{filteredFiles.map((file) => (
<Card key={file.id} className="group hover:shadow-lg transition-all duration-200">
<CardHeader className="p-0">
<div className="relative aspect-video bg-muted rounded-t-lg overflow-hidden">
{file.thumbnail ? (
<img
src={file.thumbnail}
alt={file.name}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full flex items-center justify-center">
<span className="text-4xl">{getFileIcon(file.type)}</span>
</div>
)}
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center">
<div className="flex space-x-2">
<Button size="sm" variant="secondary">
<Eye className="h-4 w-4" />
</Button>
{file.type === 'video' && (
<Button size="sm" variant="secondary">
<Play className="h-4 w-4" />
</Button>
)}
</div>
</div>
<Badge className="absolute top-2 left-2">
{file.type}
</Badge>
{file.duration && (
<Badge variant="secondary" className="absolute bottom-2 right-2">
{file.duration}
</Badge>
)}
</div>
</CardHeader>
<CardContent className="pt-4">
<div className="space-y-2">
<h3 className="font-medium truncate">{file.name}</h3>
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>{file.size}</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Download className="mr-2 h-4 w-4" />
Download
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</CardContent>
</Card>
))}
</div>
) : (
<Card>
<CardContent className="p-0">
<div className="space-y-1">
{filteredFiles.map((file) => (
<div
key={file.id}
className="flex items-center justify-between p-4 hover:bg-muted/50 transition-colors"
>
<div className="flex items-center space-x-3">
{file.thumbnail ? (
<img
src={file.thumbnail}
alt={file.name}
className="w-10 h-10 object-cover rounded"
/>
) : (
<div className="w-10 h-10 bg-muted rounded flex items-center justify-center">
<span className="text-lg">{getFileIcon(file.type)}</span>
</div>
)}
<div>
<p className="font-medium">{file.name}</p>
<p className="text-sm text-muted-foreground">
{file.size} {file.type}
{file.duration && `${file.duration}`}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<span className="text-sm text-muted-foreground">
{file.uploadedAt}
</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<Download className="mr-2 h-4 w-4" />
Download
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
))}
</div>
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="albums" className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{mockAlbums.map((album) => (
<Card key={album.id} className="hover:shadow-lg transition-all duration-200 cursor-pointer">
<CardHeader className="p-0">
<div className="aspect-video bg-muted rounded-t-lg overflow-hidden">
<img
src={album.thumbnail}
alt={album.name}
className="w-full h-full object-cover"
/>
</div>
</CardHeader>
<CardContent className="pt-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<h3 className="font-semibold">{album.name}</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>{album.itemCount} items</span>
<span>Created {new Date(album.createdAt).toLocaleDateString()}</span>
</div>
</div>
</CardContent>
</Card>
))}
</div>
</TabsContent>
</Tabs>
</div>
);
}