veza/apps/web/src/components/views/FileManagerView.tsx

450 lines
16 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { SearchInput } from '../ui/input';
import { FileNode } from '../../types';
import {
LayoutGrid,
List,
Filter,
MoreVertical,
Download,
Trash2,
Move,
Folder,
Music,
Image as ImageIcon,
File,
CheckSquare,
Square,
Wand2,
Stamp,
Eye,
ChevronRight,
} from 'lucide-react';
import { useToast } from '../../context/ToastContext';
import { AutoMetadataDetectionModal } from '../library/AutoMetadataDetectionModal';
import { WatermarkSettingsModal } from '../library/WatermarkSettingsModal';
import { FileDetailsView } from './FileDetailsView';
// Mock Files
const MOCK_FILES: FileNode[] = [
{
id: '1',
name: 'Vocals_Main.wav',
type: 'audio',
size: '24 MB',
modified: '2h ago',
status: 'ready',
},
{
id: '2',
name: 'Project_Alpha_v3',
type: 'folder',
size: '-',
modified: '1d ago',
status: 'ready',
},
{
id: '3',
name: 'Bass_Drop_F#m.wav',
type: 'audio',
size: '12 MB',
modified: '3d ago',
status: 'processing',
},
{
id: '4',
name: 'Album_Cover_Art.png',
type: 'image',
size: '4 MB',
modified: '1w ago',
status: 'ready',
},
{
id: '5',
name: 'Reference_Track_01.mp3',
type: 'audio',
size: '8 MB',
modified: '2w ago',
status: 'ready',
},
{
id: '6',
name: 'Legal_Contracts.pdf',
type: 'document',
size: '2 MB',
modified: '3w ago',
status: 'archived',
},
{
id: '7',
name: 'Drums_Stem_02.wav',
type: 'audio',
size: '45 MB',
modified: '1h ago',
status: 'ready',
},
];
export const FileManagerView: React.FC = () => {
const { addToast } = useToast();
const [viewMode, setViewMode] = useState<'list' | 'grid'>('list');
const [searchQuery, setSearchQuery] = useState('');
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
const [currentFolder, setCurrentFolder] = useState('Root');
// Navigation State
const [selectedFileId, setSelectedFileId] = useState<string | null>(null);
// Modals
const [showMetadataModal, setShowMetadataModal] = useState(false);
const [showWatermarkModal, setShowWatermarkModal] = useState(false);
// --- Handlers ---
const handleFileClick = (file: FileNode) => {
if (file.type === 'folder') {
setCurrentFolder(file.name);
addToast(`Navigated to ${file.name}`, 'info');
} else {
setSelectedFileId(file.id);
}
};
const toggleSelection = (id: string) => {
setSelectedFiles((prev) =>
prev.includes(id) ? prev.filter((fid) => fid !== id) : [...prev, id],
);
};
const selectAll = () => {
if (selectedFiles.length === MOCK_FILES.length) setSelectedFiles([]);
else setSelectedFiles(MOCK_FILES.map((f) => f.id));
};
const handleBulkAction = (action: string) => {
addToast(`${action} ${selectedFiles.length} files`, 'success');
setSelectedFiles([]);
};
const filteredFiles = MOCK_FILES.filter((f) =>
f.name.toLowerCase().includes(searchQuery.toLowerCase()),
);
// --- Render Detail View if selected ---
if (selectedFileId) {
return (
<FileDetailsView
fileId={selectedFileId}
onBack={() => setSelectedFileId(null)}
/>
);
}
// --- Main Render ---
return (
<div className="space-y-8 animate-fadeIn pb-20">
{/* Header & Controls */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<h2 className="text-2xl font-display font-bold text-white mb-1">
FILE MANAGER
</h2>
<div className="flex items-center gap-2 text-sm text-kodo-content-dim">
<span
className="hover:text-white cursor-pointer"
onClick={() => setCurrentFolder('Root')}
>
Root
</span>
{currentFolder !== 'Root' && (
<>
<ChevronRight className="w-4 h-4" />
<span className="text-white font-bold">{currentFolder}</span>
</>
)}
</div>
</div>
<div className="flex gap-2 w-full md:w-auto">
<Button
variant="ghost"
onClick={() => setShowMetadataModal(true)}
title="AI Auto-Tag"
aria-label="AI Auto-Tag"
>
<Wand2 className="w-4 h-4" />
</Button>
<Button
variant="ghost"
onClick={() => setShowWatermarkModal(true)}
title="Watermark Settings"
aria-label="Paramètres de filigrane"
>
<Stamp className="w-4 h-4" />
</Button>
<div className="bg-kodo-ink p-1 rounded-lg border border-kodo-steel flex">
<button
onClick={() => setViewMode('list')}
className={`p-2 rounded ${viewMode === 'list' ? 'bg-kodo-slate text-white' : 'text-kodo-content-dim hover:text-white'}`}
aria-label="Vue liste"
aria-pressed={viewMode === 'list'}
>
<List className="w-4 h-4" />
</button>
<button
onClick={() => setViewMode('grid')}
className={`p-2 rounded ${viewMode === 'grid' ? 'bg-kodo-slate text-white' : 'text-kodo-content-dim hover:text-white'}`}
aria-label="Vue grille"
aria-pressed={viewMode === 'grid'}
>
<LayoutGrid className="w-4 h-4" />
</button>
</div>
</div>
</div>
{/* Search & Filter Bar */}
<div className="bg-kodo-ink/50 p-4 rounded-xl border border-kodo-steel/50 flex flex-col md:flex-row gap-4 items-center justify-between">
<div className="w-full md:w-96">
<SearchInput
placeholder="Search files..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
{selectedFiles.length > 0 ? (
<div className="flex items-center gap-4 bg-kodo-steel/10 px-4 py-2 rounded-lg border border-kodo-steel/30 animate-fadeIn w-full md:w-auto">
<span className="text-sm font-bold text-kodo-steel">
{selectedFiles.length} Selected
</span>
<div className="h-4 w-px bg-kodo-cyan/30 mx-2"></div>
<button
onClick={() => handleBulkAction('Downloaded')}
className="text-kodo-text-main hover:text-white"
title="Download"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={() => handleBulkAction('Moved')}
className="text-kodo-text-main hover:text-white"
title="Move"
>
<Move className="w-4 h-4" />
</button>
<button
onClick={() => handleBulkAction('Deleted')}
className="text-kodo-red hover:text-kodo-red/80"
title="Delete"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
) : (
<div className="flex gap-2">
<Button
variant="ghost"
size="sm"
icon={<Filter className="w-3 h-3" />}
>
Type
</Button>
<Button
variant="ghost"
size="sm"
icon={<Filter className="w-3 h-3" />}
>
Date
</Button>
</div>
)}
</div>
{/* Content Area */}
<div className="min-h-[500px]">
{viewMode === 'list' ? (
<Card variant="default" className="p-0 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="border-b border-kodo-steel bg-kodo-ink/50 text-xs font-mono text-kodo-content-dim uppercase tracking-wider">
<th className="p-4 w-10">
<div
onClick={selectAll}
className="cursor-pointer hover:text-white"
>
{selectedFiles.length === MOCK_FILES.length ? (
aesthetic-improvements: reduce decorative cyan in player, library, and views (80/20 rule, batch 12) - Player: VisualizerSettingsModal decorative icon (1 instance) - Library: QueueView decorative artist text, AutoMetadataDetectionModal decorative icon and loading spinner border and fileName text and detected key text, SaveQueueAsPlaylistModal decorative icon, EditPlaylistModal decorative icon, PlaylistsView loading spinner, CreatePlaylistModal decorative icon (7 instances) - Views: StudioView decorative icon, FileDetailsView decorative icon, GearView decorative icons and order number text, ProfileView loading spinner and social icons, AnalyticsView loading spinner and decorative chart legend dot and chart bars and device icon and revenue text, DiscoverView loading spinner and decorative icon and weekly mix text, FileManagerView decorative music icons (14 instances) - Total: ~22 files, ~22 instances replaced - Preserved: Active/selected states (LyricsPanel autoScroll active state, VisualizerSettingsModal selected mode, PlayerControls shuffle/repeatMode/showVisualizer active states, MiniPlayer isQueueOpen active state, AddToPlaylistModal selected playlist, PlaylistDetailView dragged item, StudioView active tab, SearchBar focused state, CheckoutView checkbox accents - focus/interaction, SearchPageView radio button accent - focus/interaction, FileManagerView selected files checkmarks - active states, ProfileView social links - functional links, LiveView links - functional links), primary actions, design system variants - Action 11.3.1.3 in progress (twelfth batch: player, library, and views components)
2026-01-16 10:30:07 +00:00
<CheckSquare className="w-4 h-4 text-kodo-steel" />
) : (
<Square className="w-4 h-4" />
)}
</div>
</th>
<th className="p-4">Name</th>
<th className="p-4">Type</th>
<th className="p-4">Size</th>
<th className="p-4">Modified</th>
<th className="p-4">Status</th>
<th className="p-4 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-kodo-steel/30">
{filteredFiles.map((file) => (
<tr
key={file.id}
className={`group hover:bg-white/5 transition-colors ${selectedFiles.includes(file.id) ? 'bg-kodo-cyan/5' : ''}`}
>
<td className="p-4">
<div
onClick={() => toggleSelection(file.id)}
className="cursor-pointer text-kodo-content-dim hover:text-white"
>
{selectedFiles.includes(file.id) ? (
aesthetic-improvements: reduce decorative cyan in player, library, and views (80/20 rule, batch 12) - Player: VisualizerSettingsModal decorative icon (1 instance) - Library: QueueView decorative artist text, AutoMetadataDetectionModal decorative icon and loading spinner border and fileName text and detected key text, SaveQueueAsPlaylistModal decorative icon, EditPlaylistModal decorative icon, PlaylistsView loading spinner, CreatePlaylistModal decorative icon (7 instances) - Views: StudioView decorative icon, FileDetailsView decorative icon, GearView decorative icons and order number text, ProfileView loading spinner and social icons, AnalyticsView loading spinner and decorative chart legend dot and chart bars and device icon and revenue text, DiscoverView loading spinner and decorative icon and weekly mix text, FileManagerView decorative music icons (14 instances) - Total: ~22 files, ~22 instances replaced - Preserved: Active/selected states (LyricsPanel autoScroll active state, VisualizerSettingsModal selected mode, PlayerControls shuffle/repeatMode/showVisualizer active states, MiniPlayer isQueueOpen active state, AddToPlaylistModal selected playlist, PlaylistDetailView dragged item, StudioView active tab, SearchBar focused state, CheckoutView checkbox accents - focus/interaction, SearchPageView radio button accent - focus/interaction, FileManagerView selected files checkmarks - active states, ProfileView social links - functional links, LiveView links - functional links), primary actions, design system variants - Action 11.3.1.3 in progress (twelfth batch: player, library, and views components)
2026-01-16 10:30:07 +00:00
<CheckSquare className="w-4 h-4 text-kodo-steel" />
) : (
<Square className="w-4 h-4" />
)}
</div>
</td>
<td className="p-4">
<div
className="flex items-center gap-4 cursor-pointer"
onClick={() => handleFileClick(file)}
>
{file.type === 'folder' && (
<Folder className="w-5 h-5 text-kodo-gold" />
)}
{file.type === 'audio' && (
aesthetic-improvements: reduce decorative cyan in player, library, and views (80/20 rule, batch 12) - Player: VisualizerSettingsModal decorative icon (1 instance) - Library: QueueView decorative artist text, AutoMetadataDetectionModal decorative icon and loading spinner border and fileName text and detected key text, SaveQueueAsPlaylistModal decorative icon, EditPlaylistModal decorative icon, PlaylistsView loading spinner, CreatePlaylistModal decorative icon (7 instances) - Views: StudioView decorative icon, FileDetailsView decorative icon, GearView decorative icons and order number text, ProfileView loading spinner and social icons, AnalyticsView loading spinner and decorative chart legend dot and chart bars and device icon and revenue text, DiscoverView loading spinner and decorative icon and weekly mix text, FileManagerView decorative music icons (14 instances) - Total: ~22 files, ~22 instances replaced - Preserved: Active/selected states (LyricsPanel autoScroll active state, VisualizerSettingsModal selected mode, PlayerControls shuffle/repeatMode/showVisualizer active states, MiniPlayer isQueueOpen active state, AddToPlaylistModal selected playlist, PlaylistDetailView dragged item, StudioView active tab, SearchBar focused state, CheckoutView checkbox accents - focus/interaction, SearchPageView radio button accent - focus/interaction, FileManagerView selected files checkmarks - active states, ProfileView social links - functional links, LiveView links - functional links), primary actions, design system variants - Action 11.3.1.3 in progress (twelfth batch: player, library, and views components)
2026-01-16 10:30:07 +00:00
<Music className="w-5 h-5 text-kodo-steel" />
)}
{file.type === 'image' && (
<ImageIcon className="w-5 h-5 text-kodo-magenta" />
)}
{['document', 'archive', 'project'].includes(
file.type,
) && <File className="w-5 h-5 text-kodo-content-dim" />}
<span className="font-medium text-kodo-text-main group-hover:text-white transition-colors">
{file.name}
</span>
</div>
</td>
<td className="p-4">
<span className="px-2 py-1 bg-kodo-ink rounded text-[10px] uppercase font-bold text-kodo-content-dim">
{file.type}
</span>
</td>
<td className="p-4 text-sm text-kodo-content-dim font-mono">
{file.size}
</td>
<td className="p-4 text-sm text-kodo-content-dim">
{file.modified}
</td>
<td className="p-4">
<span
className={`flex items-center gap-1.5 text-xs font-bold capitalize ${file.status === 'ready' ? 'text-kodo-lime' : file.status === 'processing' ? 'text-kodo-gold' : 'text-kodo-content-dim'}`}
>
<div
className={`w-1.5 h-1.5 rounded-full ${file.status === 'ready' ? 'bg-kodo-lime' : file.status === 'processing' ? 'bg-kodo-gold animate-pulse' : 'bg-kodo-steel'}`}
></div>
{file.status}
</span>
</td>
<td className="p-4 text-right">
<div className="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
size="icon"
className="p-1.5"
onClick={() => handleFileClick(file)}
>
<Eye className="w-4 h-4" />
</Button>
<Button variant="ghost" size="icon" className="p-1.5">
<MoreVertical className="w-4 h-4" />
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
) : (
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-4">
{filteredFiles.map((file) => (
<Card
key={file.id}
variant="default"
className={`p-4 flex flex-col items-center text-center gap-4 cursor-pointer hover:border-kodo-steel/50 transition-all group relative ${selectedFiles.includes(file.id) ? 'border-kodo-cyan bg-kodo-cyan/5' : ''}`}
onClick={() => handleFileClick(file)}
>
<div
className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => {
e.stopPropagation();
toggleSelection(file.id);
}}
>
{selectedFiles.includes(file.id) ? (
<CheckSquare className="w-4 h-4 text-kodo-cyan" />
) : (
<Square className="w-4 h-4 text-kodo-content-dim hover:text-white" />
)}
</div>
<div className="w-16 h-16 rounded-2xl bg-kodo-ink flex items-center justify-center shadow-lg transition-colors duration-200">
{file.type === 'folder' && (
<Folder className="w-8 h-8 text-kodo-gold" />
)}
{file.type === 'audio' && (
aesthetic-improvements: reduce decorative cyan in player, library, and views (80/20 rule, batch 12) - Player: VisualizerSettingsModal decorative icon (1 instance) - Library: QueueView decorative artist text, AutoMetadataDetectionModal decorative icon and loading spinner border and fileName text and detected key text, SaveQueueAsPlaylistModal decorative icon, EditPlaylistModal decorative icon, PlaylistsView loading spinner, CreatePlaylistModal decorative icon (7 instances) - Views: StudioView decorative icon, FileDetailsView decorative icon, GearView decorative icons and order number text, ProfileView loading spinner and social icons, AnalyticsView loading spinner and decorative chart legend dot and chart bars and device icon and revenue text, DiscoverView loading spinner and decorative icon and weekly mix text, FileManagerView decorative music icons (14 instances) - Total: ~22 files, ~22 instances replaced - Preserved: Active/selected states (LyricsPanel autoScroll active state, VisualizerSettingsModal selected mode, PlayerControls shuffle/repeatMode/showVisualizer active states, MiniPlayer isQueueOpen active state, AddToPlaylistModal selected playlist, PlaylistDetailView dragged item, StudioView active tab, SearchBar focused state, CheckoutView checkbox accents - focus/interaction, SearchPageView radio button accent - focus/interaction, FileManagerView selected files checkmarks - active states, ProfileView social links - functional links, LiveView links - functional links), primary actions, design system variants - Action 11.3.1.3 in progress (twelfth batch: player, library, and views components)
2026-01-16 10:30:07 +00:00
<Music className="w-8 h-8 text-kodo-steel" />
)}
{file.type === 'image' && (
<ImageIcon className="w-8 h-8 text-kodo-magenta" />
)}
{['document', 'archive', 'project'].includes(file.type) && (
<File className="w-8 h-8 text-kodo-content-dim" />
)}
</div>
<div className="w-full">
<h4 className="font-bold text-white text-sm truncate w-full">
{file.name}
</h4>
<p className="text-xs text-kodo-content-dim mt-1">{file.size}</p>
</div>
</Card>
))}
</div>
)}
</div>
{/* Modals */}
{showMetadataModal && (
<AutoMetadataDetectionModal
fileName={
selectedFileId
? MOCK_FILES.find((f) => f.id === selectedFileId)?.name ||
'Selected File'
: 'Scan Library'
}
onClose={() => setShowMetadataModal(false)}
onApply={(data) => {
addToast(`Applied: ${data.genre} - ${data.bpm}BPM`, 'success');
setShowMetadataModal(false);
}}
/>
)}
{showWatermarkModal && (
<WatermarkSettingsModal
onClose={() => setShowWatermarkModal(false)}
onSave={() => addToast('Watermark settings updated', 'success')}
/>
)}
</div>
);
};