/** * Sprint Progress Component * * Displays sprint overview with progress bar, issue stats, and burndown chart. */ 'use client'; import { TrendingUp, Calendar } from 'lucide-react'; import { format } from 'date-fns'; import { cn } from '@/lib/utils'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Skeleton } from '@/components/ui/skeleton'; import { ProgressBar } from './ProgressBar'; import { BurndownChart } from './BurndownChart'; import type { Sprint, BurndownDataPoint } from './types'; // ============================================================================ // Types // ============================================================================ interface SprintProgressProps { /** Current sprint data */ sprint: Sprint | null; /** Burndown chart data */ burndownData?: BurndownDataPoint[]; /** List of available sprints for selector */ availableSprints?: { id: string; name: string }[]; /** Currently selected sprint ID */ selectedSprintId?: string; /** Callback when sprint selection changes */ onSprintChange?: (sprintId: string) => void; /** Whether data is loading */ isLoading?: boolean; /** Additional CSS classes */ className?: string; } // ============================================================================ // Helper Functions // ============================================================================ function formatSprintDates(startDate?: string, endDate?: string): string { if (!startDate || !endDate) return 'Dates not set'; try { const start = format(new Date(startDate), 'MMM d'); const end = format(new Date(endDate), 'MMM d, yyyy'); return `${start} - ${end}`; } catch { return 'Invalid dates'; } } function calculateProgress(sprint: Sprint): number { if (sprint.total_issues === 0) return 0; return Math.round((sprint.completed_issues / sprint.total_issues) * 100); } // ============================================================================ // Subcomponents // ============================================================================ interface StatCardProps { value: number; label: string; colorClass: string; } function StatCard({ value, label, colorClass }: StatCardProps) { return (
{value}
{label}
); } function SprintProgressSkeleton() { return (
{/* Progress bar skeleton */}
{/* Stats grid skeleton */}
{[1, 2, 3, 4].map((i) => (
))}
{/* Burndown skeleton */}
); } // ============================================================================ // Main Component // ============================================================================ export function SprintProgress({ sprint, burndownData = [], availableSprints = [], selectedSprintId, onSprintChange, isLoading = false, className, }: SprintProgressProps) { if (isLoading) { return ; } if (!sprint) { return ( No active sprint
); } const progress = calculateProgress(sprint); const dateRange = formatSprintDates(sprint.start_date, sprint.end_date); return (
{sprint.name} ({dateRange})
{availableSprints.length > 1 && onSprintChange && ( )}
{/* Sprint Progress */} {/* Issue Stats Grid */}
{/* Burndown Chart */}

Burndown Chart

); }