import React, { useState, useMemo } from 'react'; import { FileItem, FileType } from '../types'; import { FileIcon } from './Icon'; import { formatBytes, formatDate } from '../utils/formatters'; import { ArrowUp, Server, Monitor, FolderOpen, RefreshCw, FolderPlus, Trash2, FilePenLine, Search } from 'lucide-react'; interface FilePaneProps { title: string; icon: 'local' | 'remote'; path: string; files: FileItem[]; isLoading: boolean; onNavigate: (path: string) => void; onNavigateUp: () => void; onSelectionChange: (ids: Set) => void; selectedIds: Set; connected?: boolean; refreshKey?: number; onCreateFolder?: () => void; onDelete?: () => void; onRename?: () => void; } const FilePane: React.FC = ({ title, icon, path, files, isLoading, onNavigate, onNavigateUp, onSelectionChange, selectedIds, connected = true, refreshKey, onCreateFolder, onDelete, onRename }) => { const [searchTerm, setSearchTerm] = useState(''); const [lastClickedId, setLastClickedId] = useState(null); const [pathInput, setPathInput] = useState(path); // Sync path input when prop changes OR refreshKey updates React.useEffect(() => { setPathInput(path); }, [path, refreshKey]); const handlePathKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { onNavigate(pathInput); } else if (e.key === 'Escape') { setPathInput(path); // Revert (e.target as HTMLInputElement).blur(); } }; // Filter files based on search term const displayFiles = useMemo(() => { if (!searchTerm) return files; return files.filter(f => f.name.toLowerCase().includes(searchTerm.toLowerCase())); }, [files, searchTerm]); const handleRowClick = (e: React.MouseEvent, file: FileItem) => { e.preventDefault(); // Prevent text selection let newSelected = new Set(selectedIds); if (e.ctrlKey || e.metaKey) { // Toggle selection if (newSelected.has(file.id)) { newSelected.delete(file.id); } else { newSelected.add(file.id); } setLastClickedId(file.id); } else if (e.shiftKey && lastClickedId) { // Range selection const lastIndex = displayFiles.findIndex(f => f.id === lastClickedId); const currentIndex = displayFiles.findIndex(f => f.id === file.id); if (lastIndex !== -1 && currentIndex !== -1) { const start = Math.min(lastIndex, currentIndex); const end = Math.max(lastIndex, currentIndex); newSelected = new Set(); for (let i = start; i <= end; i++) { newSelected.add(displayFiles[i].id); } } } else { // Single selection newSelected = new Set([file.id]); setLastClickedId(file.id); } onSelectionChange(newSelected); }; return (
{/* Header */}
{icon === 'local' ? : } {title}
경로: setPathInput(e.target.value)} onKeyDown={handlePathKeyDown} className="font-mono text-slate-700 w-full outline-none text-xs py-1" spellCheck={false} />
{/* Toolbar */}
{/* Search Input */}
setSearchTerm(e.target.value)} placeholder="검색..." className="w-32 focus:w-48 transition-all bg-slate-50 border border-slate-200 rounded-full py-1 pl-7 pr-3 text-xs text-slate-700 focus:outline-none focus:border-blue-500 focus:bg-white placeholder:text-slate-400" />
{/* File Grid */}
onSelectionChange(new Set())}> {!connected ? (

서버에 연결되지 않음

) : isLoading ? (

목록 조회 중...

) : ( e.stopPropagation()}> {/* Back Button Row */} {path !== '/' && !searchTerm && ( )} {displayFiles.map((file) => { const isSelected = selectedIds.has(file.id); return ( handleRowClick(e, file)} onDoubleClick={() => { if (file.type === FileType.FOLDER) { onNavigate(path === '/' ? `/${file.name}` : `${path}/${file.name}`); onSelectionChange(new Set()); // Clear selection on navigate setSearchTerm(''); // Clear search on navigate } }} className={`cursor-pointer border-b border-slate-50 group select-none ${isSelected ? 'bg-blue-100 text-blue-900 border-blue-200' : 'text-slate-700 hover:bg-slate-50' }`} > ); })} {displayFiles.length === 0 && ( )}
파일명 크기 종류 수정일
..
{file.name} {file.type === FileType.FILE ? formatBytes(file.size) : ''} {file.type === FileType.FOLDER ? '폴더' : file.name.split('.').pop()?.toUpperCase() || '파일'} {formatDate(file.date).split(',')[0]}
{searchTerm ? `"${searchTerm}" 검색 결과 없음` : '항목 없음'}
)}
{/* Footer Status */}
{files.length} 개 항목 {selectedIds.size > 0 && `(${selectedIds.size}개 선택됨)`} {connected ? '준비됨' : '오프라인'}
); }; export default FilePane;