"use client" import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import { format, isValid } from "date-fns" import { ArrowLeft, Calendar, CheckCircle2, Target, TrendingUp, Clock, Archive, ChevronRight, X } from "lucide-react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { parseSprintEnd, parseSprintStart } from "@/lib/utils" interface Sprint { id: string name: string goal: string | null startDate: string endDate: string status: "planning" | "active" | "completed" projectId: string createdAt: string } interface Task { id: string title: string status: string type: string priority: string sprintId: string | null } interface SprintStats { sprint: Sprint totalTasks: number completedTasks: number completionRate: number velocity: number // tasks per day durationDays: number } interface SprintDetail { sprint: Sprint tasks: Task[] completedTasks: Task[] rolledOverTasks: Task[] } function formatDateRange(startDate: string, endDate: string): string { const start = parseSprintStart(startDate) const end = parseSprintEnd(endDate) if (!isValid(start) || !isValid(end)) return "Invalid dates" return `${format(start, "MMM d")} - ${format(end, "MMM d, yyyy")}` } function formatDuration(days: number): string { if (days < 1) return "< 1 day" if (days === 1) return "1 day" return `${days} days` } export default function SprintArchivePage() { const router = useRouter() const [authReady, setAuthReady] = useState(false) const [isLoading, setIsLoading] = useState(true) const [sprintStats, setSprintStats] = useState([]) const [selectedSprint, setSelectedSprint] = useState(null) const [detailOpen, setDetailOpen] = useState(false) useEffect(() => { let isMounted = true const loadSession = async () => { try { const res = await fetch("/api/auth/session", { cache: "no-store" }) if (!res.ok) { if (isMounted) router.replace("/login") return } if (isMounted) setAuthReady(true) } catch { if (isMounted) router.replace("/login") } } loadSession() return () => { isMounted = false } }, [router]) useEffect(() => { if (!authReady) return const fetchData = async () => { setIsLoading(true) try { // Fetch completed sprints const sprintsRes = await fetch("/api/sprints?status=completed") const sprintsData = await sprintsRes.json() const completedSprints: Sprint[] = (sprintsData.sprints || []).filter( (s: Sprint) => s.status === "completed" ) // Fetch all tasks const tasksRes = await fetch("/api/tasks") const tasksData = await tasksRes.json() const allTasks: Task[] = tasksData.tasks || [] // Calculate stats for each sprint const stats: SprintStats[] = completedSprints.map((sprint) => { const sprintTasks = allTasks.filter((t) => t.sprintId === sprint.id) const completedTasks = sprintTasks.filter( (t) => t.status === "done" || t.status === "archived" ) const start = parseSprintStart(sprint.startDate) const end = parseSprintEnd(sprint.endDate) const durationMs = end.getTime() - start.getTime() const durationDays = Math.max(1, Math.ceil(durationMs / (1000 * 60 * 60 * 24))) return { sprint, totalTasks: sprintTasks.length, completedTasks: completedTasks.length, completionRate: sprintTasks.length > 0 ? Math.round((completedTasks.length / sprintTasks.length) * 100) : 0, velocity: parseFloat((completedTasks.length / durationDays).toFixed(2)), durationDays, } }) // Sort by end date (most recent first) stats.sort((a, b) => parseSprintEnd(b.sprint.endDate).getTime() - parseSprintEnd(a.sprint.endDate).getTime()) setSprintStats(stats) } catch (error) { console.error("Failed to fetch sprint data:", error) } finally { setIsLoading(false) } } fetchData() }, [authReady]) const handleSprintClick = async (sprintId: string) => { try { const [sprintRes, tasksRes] = await Promise.all([ fetch(`/api/sprints/${sprintId}`), fetch("/api/tasks"), ]) const sprintData = await sprintRes.json() const tasksData = await tasksRes.json() const sprint: Sprint = sprintData.sprint const allTasks: Task[] = tasksData.tasks || [] const sprintTasks = allTasks.filter((t) => t.sprintId === sprintId) const completedTasks = sprintTasks.filter( (t) => t.status === "done" || t.status === "archived" ) const rolledOverTasks = sprintTasks.filter( (t) => t.status !== "done" && t.status !== "archived" && t.status !== "canceled" ) setSelectedSprint({ sprint, tasks: sprintTasks, completedTasks, rolledOverTasks, }) setDetailOpen(true) } catch (error) { console.error("Failed to fetch sprint details:", error) } } if (!authReady) { return (

Checking session...

) } return (
{/* Header */}

Sprint Archive

View completed sprints and their metrics

{isLoading ? (
Loading sprint history...
) : sprintStats.length === 0 ? (

No Completed Sprints

When sprints are marked as completed, they will appear here with their metrics and statistics.

) : (
{sprintStats.map((stat) => ( handleSprintClick(stat.sprint.id)} >

{stat.sprint.name}

{stat.sprint.goal && (

{stat.sprint.goal}

)}
Completed
{/* Date Range */}
{formatDateRange(stat.sprint.startDate, stat.sprint.endDate)}
{/* Stats Grid */}
Total Tasks

{stat.totalTasks}

Completed

{stat.completedTasks}

{/* Completion Rate */}
Completion Rate {stat.completionRate}%
= 80 ? "bg-emerald-500" : stat.completionRate >= 50 ? "bg-blue-500" : "bg-amber-500" }`} style={{ width: `${stat.completionRate}%` }} />
{/* Velocity & Duration */}
Velocity: {stat.velocity} tasks/day
{formatDuration(stat.durationDays)}
{/* Click hint */}
View Details
))}
)}
{/* Sprint Detail Dialog */} {selectedSprint && ( <> Sprint Details

{selectedSprint.sprint.name}

{selectedSprint.sprint.goal && (

{selectedSprint.sprint.goal}

)}
Completed
{formatDateRange(selectedSprint.sprint.startDate, selectedSprint.sprint.endDate)}
{/* Summary Stats */}

{selectedSprint.tasks.length}

Total Tasks

{selectedSprint.completedTasks.length}

Completed

{selectedSprint.rolledOverTasks.length}

Rolled Over

{/* Completed Tasks */} {selectedSprint.completedTasks.length > 0 && (

Completed Tasks ({selectedSprint.completedTasks.length})

{selectedSprint.completedTasks.map((task) => (
{task.type} {task.title} {task.priority}
))}
)} {/* Rolled Over Tasks */} {selectedSprint.rolledOverTasks.length > 0 && (

Rolled Over Tasks ({selectedSprint.rolledOverTasks.length})

{selectedSprint.rolledOverTasks.map((task) => (
{task.type} {task.title} {task.status}
))}
)} {/* Canceled Tasks */} {selectedSprint.tasks.filter((t) => t.status === "canceled").length > 0 && (

Canceled Tasks ({selectedSprint.tasks.filter((t) => t.status === "canceled").length})

{selectedSprint.tasks .filter((t) => t.status === "canceled") .map((task) => (
{task.type} {task.title} canceled
))}
)}
)}
) }