2026-02-05 18:04:00 +00:00
|
|
|
import React from 'react';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import {
|
|
|
|
|
CheckSquare,
|
|
|
|
|
Square,
|
|
|
|
|
Folder,
|
|
|
|
|
Music,
|
|
|
|
|
Image as ImageIcon,
|
|
|
|
|
File,
|
|
|
|
|
Share2,
|
|
|
|
|
MoreVertical,
|
|
|
|
|
Wand2,
|
|
|
|
|
} from 'lucide-react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
import type { CloudFileNode } from './types';
|
|
|
|
|
|
|
|
|
|
export interface FileTableRowProps {
|
|
|
|
|
file: CloudFileNode;
|
|
|
|
|
selected: boolean;
|
|
|
|
|
onToggleSelect: (id: string) => void;
|
|
|
|
|
onFileClick: (file: CloudFileNode) => void;
|
|
|
|
|
onAction?: (action: string, file: CloudFileNode) => void;
|
|
|
|
|
isLoading?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function FileTypeIcon({ type }: { type: CloudFileNode['type'] }) {
|
2026-02-07 14:20:44 +00:00
|
|
|
if (type === 'folder') return <Folder className="w-5 h-5 text-warning" />;
|
|
|
|
|
if (type === 'audio') return <Music className="w-5 h-5 text-muted-foreground" />;
|
|
|
|
|
if (type === 'image') return <ImageIcon className="w-5 h-5 text-primary" />;
|
2026-02-05 18:04:00 +00:00
|
|
|
if (['document', 'archive', 'project'].includes(type)) {
|
2026-02-07 14:20:44 +00:00
|
|
|
return <File className="w-5 h-5 text-muted-foreground" />;
|
2026-02-05 18:04:00 +00:00
|
|
|
}
|
2026-02-07 14:20:44 +00:00
|
|
|
return <File className="w-5 h-5 text-muted-foreground" />;
|
2026-02-05 18:04:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function FileTableRow({
|
|
|
|
|
file,
|
|
|
|
|
selected,
|
|
|
|
|
onToggleSelect,
|
|
|
|
|
onFileClick,
|
|
|
|
|
onAction,
|
|
|
|
|
isLoading = false,
|
|
|
|
|
}: FileTableRowProps) {
|
|
|
|
|
if (isLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<tr className="animate-pulse">
|
|
|
|
|
<td className="p-4 w-10">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-4 w-4 rounded bg-muted" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<div className="flex items-center gap-4">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-5 w-5 rounded bg-muted shrink-0" />
|
|
|
|
|
<div className="h-4 w-32 rounded bg-muted" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<div className="flex gap-1">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-4 w-12 rounded bg-muted" />
|
|
|
|
|
<div className="h-4 w-10 rounded bg-muted" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-4 w-12 rounded bg-muted font-mono text-xs" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-4 w-20 rounded bg-muted text-xs" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</td>
|
|
|
|
|
<td className="p-4 text-right">
|
2026-02-07 14:20:44 +00:00
|
|
|
<div className="h-8 w-8 rounded bg-muted ml-auto" />
|
2026-02-05 18:04:00 +00:00
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<tr
|
|
|
|
|
className={cn(
|
|
|
|
|
'group hover:bg-white/5 transition-colors',
|
2026-02-07 14:20:44 +00:00
|
|
|
selected && 'bg-primary/5'
|
2026-02-05 18:04:00 +00:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => onToggleSelect(file.id)}
|
2026-02-07 14:20:44 +00:00
|
|
|
className="cursor-pointer text-muted-foreground hover:text-foreground"
|
2026-02-05 18:04:00 +00:00
|
|
|
aria-label={selected ? 'Désélectionner' : 'Sélectionner'}
|
|
|
|
|
>
|
|
|
|
|
{selected ? (
|
2026-02-07 14:20:44 +00:00
|
|
|
<CheckSquare className="w-4 h-4 text-primary" />
|
2026-02-05 18:04:00 +00:00
|
|
|
) : (
|
|
|
|
|
<Square className="w-4 h-4" />
|
|
|
|
|
)}
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className="flex items-center gap-4 cursor-pointer text-left w-full"
|
|
|
|
|
onClick={() => onFileClick(file)}
|
|
|
|
|
>
|
|
|
|
|
<FileTypeIcon type={file.type} />
|
2026-02-07 14:20:44 +00:00
|
|
|
<span className="font-medium text-foreground group-hover:text-foreground transition-colors">
|
2026-02-05 18:04:00 +00:00
|
|
|
{file.name}
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<div className="flex gap-1 flex-wrap">
|
|
|
|
|
{file.tags?.map((t) => (
|
|
|
|
|
<span
|
|
|
|
|
key={t}
|
2026-02-07 14:20:44 +00:00
|
|
|
className="text-xs bg-muted px-1.5 py-0.5 rounded text-muted-foreground border border-border"
|
2026-02-05 18:04:00 +00:00
|
|
|
>
|
|
|
|
|
{t}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
2026-02-08 23:04:51 +00:00
|
|
|
<td className="p-4 text-muted-foreground font-mono text-xs">{file.size}</td>
|
|
|
|
|
<td className="p-4 text-muted-foreground text-xs">{file.modified}</td>
|
2026-02-05 18:04:00 +00:00
|
|
|
<td className="p-4 text-right">
|
|
|
|
|
<div className="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
|
|
|
{file.type === 'audio' && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
2026-02-07 14:20:44 +00:00
|
|
|
className="p-1.5 text-muted-foreground"
|
2026-02-05 18:04:00 +00:00
|
|
|
title="Process with AI"
|
|
|
|
|
onClick={() => onAction?.('ai', file)}
|
|
|
|
|
>
|
|
|
|
|
<Wand2 className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="p-1.5"
|
|
|
|
|
onClick={() => onAction?.('share', file)}
|
|
|
|
|
>
|
|
|
|
|
<Share2 className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
className="p-1.5"
|
|
|
|
|
onClick={() => onAction?.('more', file)}
|
|
|
|
|
>
|
|
|
|
|
<MoreVertical className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
}
|