import { DashboardLayout } from "@/components/layout/sidebar"; import { PageHeader } from "@/components/layout/page-header"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; // Progress component not available, using inline div progress bars import { Separator } from "@/components/ui/separator"; import { FolderKanban, Target, CheckCircle2, Clock, ExternalLink, TrendingUp, AlertCircle, Users, Calendar, ArrowRight, Zap, Activity, AlertTriangle, BarChart3, Sparkles, Timer, } from "lucide-react"; import Link from "next/link"; import { fetchProjectsWithStats, fetchAllSprints, fetchActiveSprint, countSprintsByStatus, getProjectHealth, getSprintDaysRemaining, formatSprintDateRange, ProjectStats, Sprint, } from "@/lib/data/projects"; import { Task } from "@/lib/data/tasks"; import { getGanttProjectUrl, getGanttSprintUrl, getGanttTaskUrl, getGanttTasksUrl, siteUrls, } from "@/lib/config/sites"; // Force dynamic rendering to fetch fresh data on each request export const dynamic = "force-dynamic"; // Helper functions function formatRelativeTime(dateString: string): string { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); } function getPriorityColor(priority: string): string { switch (priority) { case "urgent": return "bg-red-500/15 text-red-400 border-red-500/30"; case "high": return "bg-orange-500/15 text-orange-400 border-orange-500/30"; case "medium": return "bg-yellow-500/15 text-yellow-400 border-yellow-500/30"; default: return "bg-blue-500/15 text-blue-400 border-blue-500/30"; } } function getStatusIcon(status: string) { switch (status) { case "done": return ; case "in-progress": return ; case "review": case "validate": return ; default: return
; } } // Stat Card Component function StatCard({ title, value, subtitle, icon: Icon, colorClass, trend, }: { title: string; value: string | number; subtitle?: string; icon: React.ElementType; colorClass: string; trend?: "up" | "down" | "neutral"; }) { return (

{title}

{value}

{subtitle &&

{subtitle}

}
); } // Task List Item Component function TaskListItem({ task }: { task: Task }) { return (
{getStatusIcon(task.status)}
{task.title}
{formatRelativeTime(task.updatedAt)} {task.assigneeName && ( {task.assigneeName} )}
{task.priority}
); } // Project Card Component function ProjectCard({ stats }: { stats: ProjectStats }) { const health = getProjectHealth(stats); const { project, progress, totalTasks, completedTasks, inProgressTasks, urgentTasks, highPriorityTasks, recentTasks } = stats; return (
{project.name}
{health.label}
{project.description && ( {project.description} )} {/* Progress Bar */}
Progress {progress}%

{completedTasks} of {totalTasks} tasks completed

{/* Task Stats */}
In Progress: {inProgressTasks}
High Priority: {highPriorityTasks}
{urgentTasks > 0 && (
{urgentTasks} urgent task{urgentTasks !== 1 ? 's' : ''} need{urgentTasks === 1 ? 's' : ''} attention
)}
{/* Recent Activity */}

Recent Activity

{recentTasks.length > 0 ? (
{recentTasks.slice(0, 3).map((task) => (
{getStatusIcon(task.status)} {task.title} {formatRelativeTime(task.updatedAt)}
))}
) : (

No recent activity

)}
{/* Action Link */} View in Gantt Board ); } // Active Sprint Card Component function ActiveSprintCard({ sprint, projectStats }: { sprint: Sprint; projectStats: ProjectStats[] }) { const daysRemaining = getSprintDaysRemaining(sprint); const sprintProject = projectStats.find(p => p.project.id === sprint.projectId); const sprintProgress = sprintProject?.progress || 0; const isOverdue = daysRemaining < 0; return (
Active Sprint

{sprint.name}

{isOverdue ? `${Math.abs(daysRemaining)} days overdue` : `${daysRemaining} days left`}
{sprint.goal && (

{sprint.goal}

)}
Sprint Progress {sprintProgress}%
{formatSprintDateRange(sprint)} {sprintProject && ( {sprintProject.project.name} )}
); } // Main Page Component export default async function ProjectsOverviewPage() { // Fetch all data in parallel let projectStats: ProjectStats[] = []; let sprints: Sprint[] = []; let activeSprint: Sprint | null = null; let sprintCounts = { planning: 0, active: 0, completed: 0, total: 0 }; let errorMessage: string | null = null; try { [projectStats, sprints, activeSprint, sprintCounts] = await Promise.all([ fetchProjectsWithStats(), fetchAllSprints(), fetchActiveSprint(), countSprintsByStatus(), ]); } catch (error) { console.error("Error fetching projects data:", error); errorMessage = error instanceof Error ? error.message : "Failed to load projects data"; } // Calculate overall stats const totalTasks = projectStats.reduce((sum, p) => sum + p.totalTasks, 0); const completedTasks = projectStats.reduce((sum, p) => sum + p.completedTasks, 0); const overallProgress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; const urgentTasks = projectStats.reduce((sum, p) => sum + p.urgentTasks, 0); const highPriorityTasks = projectStats.reduce((sum, p) => sum + p.highPriorityTasks, 0); // Sort projects by health status (critical first, then warning, then healthy) const sortedProjects = [...projectStats].sort((a, b) => { const healthA = getProjectHealth(a).status; const healthB = getProjectHealth(b).status; const order = { critical: 0, warning: 1, healthy: 2 }; return order[healthA] - order[healthB]; }); return (
{/* Header */} Manage all projects and track progress. Edit projects in{" "} gantt-board } > {/* Error Message */} {errorMessage && (

Error loading projects

{errorMessage}

Make sure you are logged into gantt-board and have access to the Supabase database.

)} {/* Stats Overview */}
{/* Active Sprint Section */} {activeSprint && (
Sprint Stats
Planning {sprintCounts.planning}
Active {sprintCounts.active}
Completed {sprintCounts.completed}
Total Sprints {sprintCounts.total}
)} {/* Projects Grid */}

Project Health

Healthy
At Risk
Critical
{projectStats.length === 0 ? (

No projects found

Create projects in the Gantt Board to see them here

) : (
{sortedProjects.map((stats) => ( ))}
)}
{/* Recent Activity Section */}
{/* Recent Tasks Across All Projects */} Recently Updated Tasks {(() => { const allRecentTasks = projectStats .flatMap(p => p.recentTasks.map(t => ({ ...t, projectName: p.project.name, projectColor: p.project.color }))) .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) .slice(0, 5); return allRecentTasks.length > 0 ? (
{allRecentTasks.map((task) => (
{getStatusIcon(task.status)}
{task.title}
{task.projectName} {formatRelativeTime(task.updatedAt)}
{task.priority}
))}
) : (

No recent activity

); })()} {/* Quick Actions */} Quick Actions
{activeSprint && ( )}

Project Management

Mission Control is read-only. Create and edit projects in gantt-board.

); }