import { useState, useEffect, useMemo } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { Search, FileText, CheckSquare, Square, FolderOpen } from "lucide-react"; import { api } from "@/shared/config/database"; import { toast } from "sonner"; interface FileSelectionDialogProps { open: boolean; onOpenChange: (open: boolean) => void; projectId: string; branch?: string; excludePatterns?: string[]; onConfirm: (selectedFiles: string[]) => void; } interface FileNode { path: string; size: number; } export default function FileSelectionDialog({ open, onOpenChange, projectId, branch, excludePatterns, onConfirm }: FileSelectionDialogProps) { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [selectedFiles, setSelectedFiles] = useState>(new Set()); const [searchTerm, setSearchTerm] = useState(""); useEffect(() => { if (open && projectId) { loadFiles(); } else { // Reset state when closed setFiles([]); setSelectedFiles(new Set()); setSearchTerm(""); } }, [open, projectId, branch, excludePatterns]); const loadFiles = async () => { try { setLoading(true); // 传入排除模式,让后端过滤文件 const data = await api.getProjectFiles(projectId, branch, excludePatterns); setFiles(data); setSelectedFiles(new Set(data.map(f => f.path))); } catch (error) { console.error("Failed to load files:", error); toast.error("加载文件列表失败"); } finally { setLoading(false); } }; const filteredFiles = useMemo(() => { if (!searchTerm) return files; return files.filter(f => f.path.toLowerCase().includes(searchTerm.toLowerCase())); }, [files, searchTerm]); const handleToggleFile = (path: string) => { const newSelected = new Set(selectedFiles); if (newSelected.has(path)) { newSelected.delete(path); } else { newSelected.add(path); } setSelectedFiles(newSelected); }; const handleSelectAll = () => { const newSelected = new Set(selectedFiles); filteredFiles.forEach(f => newSelected.add(f.path)); setSelectedFiles(newSelected); }; const handleDeselectAll = () => { const newSelected = new Set(selectedFiles); filteredFiles.forEach(f => newSelected.delete(f.path)); setSelectedFiles(newSelected); }; const handleConfirm = () => { if (selectedFiles.size === 0) { toast.error("请至少选择一个文件"); return; } onConfirm(Array.from(selectedFiles)); onOpenChange(false); }; const formatSize = (bytes: number) => { if (bytes === 0) return ""; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; }; return (
选择要审计的文件
{excludePatterns && excludePatterns.length > 0 && ( 已排除 {excludePatterns.length} 种模式 )}
setSearchTerm(e.target.value)} className="pl-10 retro-input h-10" />
共 {files.length} 个文件 已选择 {selectedFiles.size} 个
{loading ? (
) : filteredFiles.length > 0 ? (
{filteredFiles.map((file) => (
handleToggleFile(file.path)} > handleToggleFile(file.path)} className="rounded-none border-2 border-black data-[state=checked]:bg-primary data-[state=checked]:text-white" />

{file.path}

{file.size > 0 && ( {formatSize(file.size)} )}
))}
) : (

没有找到文件

)}
); }