'use client'; import * as React from 'react'; import { Search, Send, Download, X, List, LayoutGrid, CheckSquare, XSquare } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Logo } from '@/components/logo'; import { FolderTree } from '@/components/folder-tree'; import { FileList } from '@/components/file-list'; import { findNodeByPath, getAllFiles, getNodesByIds } from '@/lib/utils'; import type { File as FileType, Folder, FileSystemNode } from '@/app/api/files/route'; import { Card, CardContent } from './ui/card'; import { MobileSheet } from './mobile-sheet'; import { Button } from './ui/button'; import Link from 'next/link'; import { ThemeToggle } from './theme-toggle'; import { AnimatePresence, motion } from 'framer-motion'; import { useToast } from '@/hooks/use-toast'; import JSZip from 'jszip'; import { GridView } from './grid-view'; import { ToggleGroup, ToggleGroupItem } from './ui/toggle-group'; import { useLocalStorage } from '@/hooks/use-local-storage'; import { cn } from '@/lib/utils'; interface FileBrowserProps { initialData: Folder; isPublicShare?: boolean; } export function FileBrowser({ initialData, isPublicShare = false }: FileBrowserProps) { const [fileSystemData] = React.useState(initialData); const [currentPath, setCurrentPath] = React.useState('/'); const [searchTerm, setSearchTerm] = React.useState(''); const [selectedIds, setSelectedIds] = React.useState([]); const [view, setView] = useLocalStorage<'list' | 'grid'>('file-browser-view', 'grid'); const [isSelectionMode, setIsSelectionMode] = React.useState(false); const { toast, dismiss } = useToast(); React.useEffect(() => { if (!isSelectionMode) { setSelectedIds([]); } }, [isSelectionMode]); const handleSearchChange = (event: React.ChangeEvent) => { setSearchTerm(event.target.value); setSelectedIds([]); // Clear selection on new search }; const currentFolder = React.useMemo(() => { if (isPublicShare) return fileSystemData; return findNodeByPath(fileSystemData, currentPath); }, [fileSystemData, currentPath, isPublicShare]); const { filesToShow, foldersToShow, allVisibleItems } = React.useMemo(() => { if (searchTerm) { const allFiles = getAllFiles(fileSystemData); const filteredFiles = allFiles.filter((file) => file.name.toLowerCase().includes(searchTerm.toLowerCase()) ); return { filesToShow: filteredFiles, foldersToShow: [], allVisibleItems: filteredFiles, }; } if (currentFolder && currentFolder.type === 'folder') { const files = currentFolder.children.filter( (child): child is FileType => child.type === 'file' ); const folders = currentFolder.children.filter( (child): child is Folder => child.type === 'folder' ); return { filesToShow: files, foldersToShow: folders, allVisibleItems: [...folders, ...files] }; } return { filesToShow: [], foldersToShow: [], allVisibleItems: [] }; }, [fileSystemData, currentPath, searchTerm, currentFolder]); const breadcrumbs = currentPath.split('/').filter(Boolean); const handleSelectAll = (isChecked: boolean) => { if (isChecked) { setSelectedIds(allVisibleItems.map(item => item.id)); } else { setSelectedIds([]); } }; const handleSelectItem = (id: string, isChecked: boolean) => { setSelectedIds(prev => isChecked ? [...prev, id] : prev.filter(selectedId => selectedId !== id) ); }; const handleBatchDownload = async () => { const { saveAs } = await import('file-saver'); const { id: toastId } = toast({ title: 'Preparing Download', description: 'Zipping your files... Please wait.', }); try { const selectedNodes = getNodesByIds(fileSystemData, selectedIds); const filesToZip: FileType[] = []; selectedNodes.forEach(node => { if (node.type === 'file') { filesToZip.push(node); } else if (node.type === 'folder') { filesToZip.push(...getAllFiles(node)); } }); if (filesToZip.length === 0) { toast({ variant: 'destructive', title: 'No Files Selected', description: 'Please select files to download.', }); dismiss(toastId); return; } const zip = new JSZip(); await Promise.all( filesToZip.map(async (file) => { const response = await fetch(file.path); const blob = await response.blob(); // Use the relative path within the zip file const zipPath = file.path.startsWith('/') ? file.path.substring(1) : file.path; zip.file(zipPath, blob); }) ); const zipBlob = await zip.generateAsync({ type: 'blob' }); saveAs(zipBlob, 'medico-docs.zip'); toast({ title: 'Download Ready', description: 'Your ZIP file has been downloaded.', }); } catch (error) { console.error('Batch download failed:', error); toast({ variant: 'destructive', title: 'Download Failed', description: 'There was an error creating the ZIP file.', }); } finally { dismiss(toastId); setSelectedIds([]); setIsSelectionMode(false); } }; const numSelected = selectedIds.length; return (
{isPublicShare ? : }
{!isPublicShare &&
}
{isSelectionMode ? ( ) : ( )} {!isPublicShare && ( )}
{isPublicShare || breadcrumbs.length === 0 ? ( {currentFolder?.name || 'Files'} ) : ( setCurrentPath('/')}>Files {breadcrumbs.map((crumb, index) => { const path = '/' + breadcrumbs.slice(0, index + 1).join('/'); return ( / setCurrentPath(path)}>{crumb} ) })} )}
{ if (value) setView(value as 'list' | 'grid')}} size="sm">

{searchTerm ? `Search Results` : currentFolder?.name}

{view === 'list' ? ( ) : ( )}
{numSelected > 0 && (
{numSelected} item{numSelected > 1 ? 's' : ''} selected
)}
); }