Files
DataClaw/frontend/src/components/ProjectSwitcher.tsx
T

119 lines
4.2 KiB
TypeScript
Raw Normal View History

2026-03-21 21:26:57 +08:00
import { useEffect, useState } from 'react';
2026-03-16 16:12:35 +08:00
import { ChevronDown, Plus, Folder } from 'lucide-react';
2026-03-21 21:26:57 +08:00
import { useProjectStore } from '@/store/projectStore';
2026-03-16 16:12:35 +08:00
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
DropdownMenuGroup,
} from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
export function ProjectSwitcher() {
const { projects, currentProject, fetchProjects, setCurrentProject, addProject } = useProjectStore();
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [newProjectName, setNewProjectName] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
fetchProjects();
}, [fetchProjects]);
const handleCreateProject = async () => {
if (!newProjectName.trim()) return;
setIsSubmitting(true);
try {
await addProject(newProjectName);
setNewProjectName('');
setIsCreateDialogOpen(false);
} catch (error) {
console.error('Failed to create project:', error);
} finally {
setIsSubmitting(false);
}
};
return (
<div className="flex items-center gap-2 px-4 py-2 bg-background h-12">
<DropdownMenu>
<DropdownMenuTrigger className="flex h-8 items-center gap-1 rounded-md px-2 font-semibold hover:bg-accent hover:text-accent-foreground outline-none transition-colors">
<Folder className="h-4 w-4 mr-1 text-blue-500" />
{currentProject?.name || 'Select Project'}
<ChevronDown className="h-4 w-4 opacity-50" />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuGroup>
<DropdownMenuLabel className="flex items-center justify-between">
PROJECTS
<Button
variant="ghost"
size="icon"
className="h-5 w-5"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIsCreateDialogOpen(true);
}}
>
<Plus className="h-3 w-3" />
</Button>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</DropdownMenuGroup>
<div className="max-h-64 overflow-y-auto">
{projects.map((project) => (
<DropdownMenuItem
key={project.id}
onClick={() => {
setCurrentProject(project);
}}
className={currentProject?.id === project.id ? 'bg-accent' : ''}
>
<Folder className="h-4 w-4 mr-2 text-zinc-400" />
{project.name}
</DropdownMenuItem>
))}
{projects.length === 0 && (
<div className="px-2 py-4 text-center text-xs text-muted-foreground">
No projects found
</div>
)}
</div>
</DropdownMenuContent>
</DropdownMenu>
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Project</DialogTitle>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="name">Project Name</Label>
<Input
id="name"
value={newProjectName}
onChange={(e) => setNewProjectName(e.target.value)}
placeholder="Enter project name"
autoFocus
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>Cancel</Button>
<Button onClick={handleCreateProject} disabled={isSubmitting || !newProjectName.trim()}>
{isSubmitting ? 'Creating...' : 'Create Project'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}