Files
fabrikanabytok/apps/fabrikanabytok/components/admin/file-manager/file-list.tsx
2025-11-28 20:47:59 +01:00

237 lines
7.9 KiB
TypeScript

"use client"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Checkbox } from "@/components/ui/checkbox"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu"
import {
FileIcon,
Download,
Trash2,
MoreVertical,
FileText,
Image as ImageIcon,
Box,
FileJson,
Settings,
Mail,
Globe,
Archive,
ExternalLink,
} from "lucide-react"
import type { FileItem } from "@/lib/types/file-manager.types"
import { formatBytes } from "@/lib/utils"
import { formatDistanceToNow } from "date-fns"
import { hu } from "date-fns/locale"
import { cn } from "@/lib/utils"
import { deleteFile } from "@/lib/actions/file-manager.actions"
import { toast } from "sonner"
import { useRouter } from "next/navigation"
interface FileListProps {
files: FileItem[]
selectedFiles: string[]
onSelectFiles: (fileIds: string[]) => void
onFileClick: (fileId: string) => void
}
export function FileList({ files, selectedFiles, onSelectFiles, onFileClick }: FileListProps) {
const router = useRouter()
const handleSelectAll = (checked: boolean) => {
if (checked) {
onSelectFiles(files.map((f) => f.id))
} else {
onSelectFiles([])
}
}
const handleSelectFile = (fileId: string, checked: boolean) => {
if (checked) {
onSelectFiles([...selectedFiles, fileId])
} else {
onSelectFiles(selectedFiles.filter((id) => id !== fileId))
}
}
const handleDelete = async (fileId: string) => {
if (!confirm("Are you sure you want to delete this file?")) return
try {
await deleteFile(fileId)
toast.success("File deleted successfully")
router.refresh()
} catch (error: any) {
toast.error(error.message || "Failed to delete file")
}
}
const getFileIcon = (type: FileItem["type"]) => {
const icons = {
model: Box,
image: ImageIcon,
document: FileText,
data: FileJson,
config: Settings,
template: Mail,
translation: Globe,
archive: Archive,
other: FileIcon,
}
return icons[type] || FileIcon
}
if (files.length === 0) {
return (
<div className="text-center py-16 text-muted-foreground">
<FileIcon className="w-16 h-16 mx-auto mb-4 opacity-50" />
<p>No files found</p>
</div>
)
}
return (
<div className="border rounded-lg bg-card">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedFiles.length === files.length && files.length > 0}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead>Name</TableHead>
<TableHead>Type</TableHead>
<TableHead>Category</TableHead>
<TableHead>Size</TableHead>
<TableHead>Uploaded</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-12"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{files.map((file) => {
const Icon = getFileIcon(file.type)
return (
<TableRow
key={file.id}
className={cn(
"cursor-pointer hover:bg-muted/50",
selectedFiles.includes(file.id) && "bg-brand-50"
)}
onClick={() => onFileClick(file.id)}
>
<TableCell onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedFiles.includes(file.id)}
onCheckedChange={(checked) => handleSelectFile(file.id, checked as boolean)}
/>
</TableCell>
<TableCell>
<div className="flex items-center gap-3">
<Icon className="w-5 h-5 text-muted-foreground flex-shrink-0" />
<div className="min-w-0 flex-1">
<div className="font-medium truncate" title={file.name}>
{file.name}
</div>
{file.modelMetadata && (
<div className="text-xs text-muted-foreground">
{file.modelMetadata.triangles.toLocaleString()} triangles
</div>
)}
</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="text-xs">
{file.extension.replace(".", "").toUpperCase()}
</Badge>
</TableCell>
<TableCell>
<Badge variant="secondary" className="text-xs">
{file.category}
</Badge>
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{formatBytes(file.size)}
</TableCell>
<TableCell className="text-sm text-muted-foreground">
{formatDistanceToNow(new Date(file.uploadedAt), { addSuffix: true, locale: hu })}
</TableCell>
<TableCell>
{file.linkedTo && file.linkedTo.length > 0 ? (
<Badge variant="default" className="text-xs">
In Use ({file.linkedTo.length})
</Badge>
) : (
<Badge variant="outline" className="text-xs">
Unused
</Badge>
)}
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreVertical className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => window.open(file.storageUrl, "_blank")}>
<ExternalLink className="w-4 h-4 mr-2" />
Open in New Tab
</DropdownMenuItem>
<DropdownMenuItem onClick={() => {
const a = document.createElement("a")
a.href = file.storageUrl
a.download = file.name
a.click()
}}>
<Download className="w-4 h-4 mr-2" />
Download
</DropdownMenuItem>
<DropdownMenuItem onClick={() => {
navigator.clipboard.writeText(file.storageUrl)
toast.success("URL copied!")
}}>
<Globe className="w-4 h-4 mr-2" />
Copy URL
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => handleDelete(file.id)}
className="text-destructive"
disabled={file.linkedTo && file.linkedTo.length > 0}
>
<Trash2 className="w-4 h-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
)
}