import React, { useState, useEffect } from 'react'; import { FaFolder, FaFolderOpen, FaFile, FaChevronRight, FaChevronDown, FaSpinner } from 'react-icons/fa'; import axios from 'axios'; interface TreeNode { name: string; path: string; type: 'file' | 'folder'; children?: TreeNode[]; } const FileTree: React.FC<{ projectPath: string; onFileSelect: (path: string) => void }> = ({ projectPath, onFileSelect }) => { const [tree, setTree] = useState([]); const [expanded, setExpanded] = useState>(new Set()); const [selected, setSelected] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { axios.get('/api/files', { params: { project_path: projectPath } }) .then(res => setTree(res.data)) .catch(() => setTree([])) .finally(() => setLoading(false)); }, [projectPath]); const toggle = (path: string) => { setExpanded(prev => { const next = new Set(prev); if (next.has(path)) next.delete(path); else next.add(path); return next; }); }; const handleClick = (node: TreeNode) => { if (node.type === 'folder') { toggle(node.path); } else { setSelected(node.path); onFileSelect(node.path); } }; const renderNode = (node: TreeNode, depth: number = 0) => { const isOpen = expanded.has(node.path); return (
handleClick(node)} style={{ paddingLeft: depth * 16 + 8, padding: '4px 8px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 6, background: selected === node.path ? 'var(--accent)' : 'transparent', color: selected === node.path ? 'white' : 'var(--text-primary)', fontSize: 13, }} > {node.type === 'folder' ? ( <> {isOpen ? : } {isOpen ? : } ) : ( <> )} {node.name}
{node.type === 'folder' && isOpen && node.children?.map(child => renderNode(child, depth + 1))}
); }; if (loading) return
Loading files...
; return
{tree.map(node => renderNode(node))}
; }; export default FileTree;