Files
fast-next-template/frontend/src/components/projects/IssueSummary.tsx
Felipe Cardoso 35af7daf90 fix(frontend): align project types with backend enums
- Fix ProjectStatus: use 'active' instead of 'in_progress'
- Fix AgentStatus: remove 'active'/'pending'/'error', add 'waiting'
- Fix SprintStatus: add 'in_review'
- Rename IssueSummary to IssueCountSummary
- Update all components to use correct enum values

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:48:02 +01:00

195 lines
5.3 KiB
TypeScript

/**
* Issue Summary Component
*
* Sidebar component showing issue counts by status.
*/
'use client';
import {
GitBranch,
CircleDot,
PlayCircle,
Clock,
AlertCircle,
CheckCircle2,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Skeleton } from '@/components/ui/skeleton';
import type { IssueCountSummary } from './types';
// ============================================================================
// Types
// ============================================================================
interface IssueSummaryProps {
/** Issue summary data */
summary: IssueCountSummary | null;
/** Whether data is loading */
isLoading?: boolean;
/** Callback when "View All Issues" is clicked */
onViewAllIssues?: () => void;
/** Additional CSS classes */
className?: string;
}
interface StatusRowProps {
icon: React.ElementType;
iconColor: string;
label: string;
count: number;
}
// ============================================================================
// Subcomponents
// ============================================================================
function StatusRow({ icon: Icon, iconColor, label, count }: StatusRowProps) {
return (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Icon className={cn('h-4 w-4', iconColor)} aria-hidden="true" />
<span className="text-sm">{label}</span>
</div>
<span className="font-medium">{count}</span>
</div>
);
}
function IssueSummarySkeleton() {
return (
<Card>
<CardHeader className="pb-3">
<Skeleton className="h-5 w-32" />
</CardHeader>
<CardContent>
<div className="space-y-3">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-20" />
</div>
<Skeleton className="h-4 w-8" />
</div>
))}
<Skeleton className="h-px w-full" />
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Skeleton className="h-4 w-4" />
<Skeleton className="h-4 w-20" />
</div>
<Skeleton className="h-4 w-8" />
</div>
<div className="pt-2">
<Skeleton className="h-9 w-full" />
</div>
</div>
</CardContent>
</Card>
);
}
// ============================================================================
// Main Component
// ============================================================================
export function IssueSummary({
summary,
isLoading = false,
onViewAllIssues,
className,
}: IssueSummaryProps) {
if (isLoading) {
return <IssueSummarySkeleton />;
}
if (!summary) {
return (
<Card className={className} data-testid="issue-summary">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg">
<GitBranch className="h-5 w-5" aria-hidden="true" />
Issue Summary
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-col items-center justify-center py-4 text-center text-muted-foreground">
<CircleDot className="mb-2 h-6 w-6" aria-hidden="true" />
<p className="text-sm">No issues found</p>
</div>
</CardContent>
</Card>
);
}
return (
<Card className={className} data-testid="issue-summary">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg">
<GitBranch className="h-5 w-5" aria-hidden="true" />
Issue Summary
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3" role="list" aria-label="Issue counts by status">
<StatusRow
icon={CircleDot}
iconColor="text-blue-500"
label="Open"
count={summary.open}
/>
<StatusRow
icon={PlayCircle}
iconColor="text-yellow-500"
label="In Progress"
count={summary.in_progress}
/>
<StatusRow
icon={Clock}
iconColor="text-purple-500"
label="In Review"
count={summary.in_review}
/>
<StatusRow
icon={AlertCircle}
iconColor="text-red-500"
label="Blocked"
count={summary.blocked}
/>
<Separator />
<StatusRow
icon={CheckCircle2}
iconColor="text-green-500"
label="Closed"
count={summary.closed}
/>
{onViewAllIssues && (
<div className="pt-2">
<Button
variant="outline"
className="w-full"
size="sm"
onClick={onViewAllIssues}
>
View All Issues ({summary.total})
</Button>
</div>
)}
</div>
</CardContent>
</Card>
);
}