237 lines
7.9 KiB
TypeScript
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>
|
|
)
|
|
}
|
|
|