import { useState, useCallback, useMemo } from "react"; import { Folder, Check, AlertCircle, Edit2, CheckCheck, Loader2 } from "lucide-react"; import type { DetectedProject } from "../api"; import { sortDetectedProjects } from "../utils/projectDetection"; export interface ProjectDetectionResultsProps { /** Detected projects from the scan */ detectedProjects: DetectedProject[]; /** Called when a project is selected/deselected */ onSelect: (project: DetectedProject, selected: boolean) => void; /** Called when the name of a detected project is edited */ onEditName: (index: number, newName: string) => void; /** Called when register selected button is clicked */ onRegisterSelected: (projects: DetectedProject[]) => void; /** Loading state during registration */ isRegistering?: boolean; } /** * ProjectDetectionResults - Auto-detect results UI * * Displays detected projects with: * - Checkboxes for selection * - Editable names * - Warnings for projects without fn database * - Register Selected / Register All buttons */ export function ProjectDetectionResults({ detectedProjects, onSelect, onEditName, onRegisterSelected, isRegistering = false, }: ProjectDetectionResultsProps) { const [selectedPaths, setSelectedPaths] = useState>(new Set()); const [editingIndex, setEditingIndex] = useState(null); const [editValue, setEditValue] = useState(""); // Sort projects: existing ones first const sortedProjects = useMemo(() => { return sortDetectedProjects(detectedProjects); }, [detectedProjects]); // Calculate selection state const selectedCount = selectedPaths.size; const allSelected = selectedCount === sortedProjects.length && sortedProjects.length > 0; // Toggle selection for a single project const toggleSelection = useCallback((project: DetectedProject) => { setSelectedPaths((prev) => { const next = new Set(prev); if (next.has(project.path)) { next.delete(project.path); onSelect(project, false); } else { next.add(project.path); onSelect(project, true); } return next; }); }, [onSelect]); // Select/deselect all const toggleAll = useCallback(() => { if (allSelected) { setSelectedPaths(new Set()); sortedProjects.forEach((p) => onSelect(p, false)); } else { const allPaths = new Set(sortedProjects.map((p) => p.path)); setSelectedPaths(allPaths); sortedProjects.forEach((p) => onSelect(p, true)); } }, [allSelected, sortedProjects, onSelect]); // Start editing a name const startEditing = useCallback((index: number, currentName: string) => { setEditingIndex(index); setEditValue(currentName); }, []); // Save edited name const saveEdit = useCallback(() => { if (editingIndex !== null) { onEditName(editingIndex, editValue.trim() || sortedProjects[editingIndex].suggestedName); setEditingIndex(null); setEditValue(""); } }, [editingIndex, editValue, onEditName, sortedProjects]); // Cancel editing const cancelEdit = useCallback(() => { setEditingIndex(null); setEditValue(""); }, []); // Handle register selected const handleRegisterSelected = useCallback(() => { const selected = sortedProjects.filter((p) => selectedPaths.has(p.path)); onRegisterSelected(selected); }, [sortedProjects, selectedPaths, onRegisterSelected]); // Handle register all const handleRegisterAll = useCallback(() => { onRegisterSelected(sortedProjects); }, [sortedProjects, onRegisterSelected]); if (sortedProjects.length === 0) { return (

No projects detected

Try a different base path or add a project manually
); } return (
{/* Header with select all */}
{selectedCount} selected
{/* Project list */}
{sortedProjects.map((project, index) => { const isSelected = selectedPaths.has(project.path); const isEditing = editingIndex === index; const hasKbDb = project.existing; return (
toggleSelection(project)} disabled={isRegistering} />
{hasKbDb && }
{isEditing ? (
setEditValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") saveEdit(); if (e.key === "Escape") cancelEdit(); }} autoFocus />
) : (
{project.suggestedName}
)}
{project.path}
{!hasKbDb && (
No fn database found - will be initialized
)}
); })}
{/* Actions */}
); }