Files
pragma-stack/frontend/src/components/layout/ProjectSwitcher.tsx
Felipe Cardoso a4c91cb8c3 refactor(frontend): clean up code by consolidating multi-line JSX into single lines where feasible
- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits.
- Improved consistency in component imports by grouping and consolidating them.
- No functional changes, purely restructuring for clarity and maintainability.
2026-01-01 11:46:57 +01:00

186 lines
5.2 KiB
TypeScript

/**
* Project Switcher Component
* Dropdown selector for switching between projects
* Displays current project and allows quick project navigation
*/
'use client';
import { useState, useCallback } from 'react';
import { useRouter } from '@/lib/i18n/routing';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Button } from '@/components/ui/button';
import { FolderKanban, Plus, ChevronDown } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
interface Project {
id: string;
slug: string;
name: string;
/** Optional description */
description?: string;
}
interface ProjectSwitcherProps {
/** Currently selected project */
currentProject?: Project;
/** List of available projects */
projects: Project[];
/** Callback when project is changed */
onProjectChange?: (projectSlug: string) => void;
/** Additional className */
className?: string;
}
export function ProjectSwitcher({
currentProject,
projects,
onProjectChange,
className,
}: ProjectSwitcherProps) {
const router = useRouter();
const [open, setOpen] = useState(false);
const handleProjectChange = useCallback(
(projectSlug: string) => {
if (onProjectChange) {
onProjectChange(projectSlug);
} else {
// Default behavior: navigate to project dashboard
router.push(`/projects/${projectSlug}`);
}
setOpen(false);
},
[onProjectChange, router]
);
const handleCreateProject = useCallback(() => {
router.push('/projects/new');
setOpen(false);
}, [router]);
// If no projects, show create button
if (projects.length === 0) {
return (
<Button
variant="outline"
size="sm"
onClick={handleCreateProject}
className={cn('gap-2', className)}
data-testid="create-project-button"
>
<Plus className="h-4 w-4" aria-hidden="true" />
<span>Create Project</span>
</Button>
);
}
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className={cn('gap-2 min-w-[160px] justify-between', className)}
data-testid="project-switcher-trigger"
aria-label={
currentProject ? `Switch project, current: ${currentProject.name}` : 'Select project'
}
>
<div className="flex items-center gap-2">
<FolderKanban className="h-4 w-4" aria-hidden="true" />
<span className="truncate max-w-[120px]">
{currentProject?.name ?? 'Select Project'}
</span>
</div>
<ChevronDown className="h-4 w-4 opacity-50" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-[200px]" data-testid="project-switcher-menu">
<DropdownMenuLabel>Projects</DropdownMenuLabel>
<DropdownMenuSeparator />
{projects.map((project) => (
<DropdownMenuItem
key={project.id}
onSelect={() => handleProjectChange(project.slug)}
className="cursor-pointer"
data-testid={`project-option-${project.slug}`}
>
<FolderKanban className="mr-2 h-4 w-4" aria-hidden="true" />
<span className="truncate">{project.name}</span>
{currentProject?.id === project.id && (
<span className="ml-auto text-xs text-muted-foreground">Current</span>
)}
</DropdownMenuItem>
))}
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={handleCreateProject}
className="cursor-pointer"
data-testid="create-project-option"
>
<Plus className="mr-2 h-4 w-4" aria-hidden="true" />
<span>Create New Project</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
/**
* Alternative version using Select component for simpler use cases
*/
interface ProjectSelectProps {
/** Currently selected project slug */
value?: string;
/** List of available projects */
projects: Project[];
/** Callback when project is selected */
onValueChange: (projectSlug: string) => void;
/** Placeholder text */
placeholder?: string;
/** Additional className */
className?: string;
}
export function ProjectSelect({
value,
projects,
onValueChange,
placeholder = 'Select a project',
className,
}: ProjectSelectProps) {
return (
<Select value={value} onValueChange={onValueChange}>
<SelectTrigger className={cn('w-[200px]', className)} data-testid="project-select">
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{projects.map((project) => (
<SelectItem
key={project.id}
value={project.slug}
data-testid={`project-select-option-${project.slug}`}
>
{project.name}
</SelectItem>
))}
</SelectContent>
</Select>
);
}