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,
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.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 }: { sprint: Sprint }) {
const daysRemaining = getSprintDaysRemaining(sprint);
const isOverdue = daysRemaining < 0;
return (
Active Sprint
{sprint.name}
{isOverdue ? `${Math.abs(daysRemaining)} days overdue` : `${daysRemaining} days left`}
{sprint.goal && (
{sprint.goal}
)}
{formatSprintDateRange(sprint)}
);
}
// Main Page Component
export default async function ProjectsOverviewPage() {
// Fetch all data in parallel
let projectStats: ProjectStats[] = [];
let activeSprint: Sprint | null = null;
let sprintCounts = { planning: 0, active: 0, completed: 0, total: 0 };
let errorMessage: string | null = null;
try {
[projectStats, activeSprint, sprintCounts] = await Promise.all([
fetchProjectsWithStats(),
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 */}
{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.
);
}