trello_fake/components/project-detail/project-stats.tsx

159 lines
5.5 KiB
TypeScript
Raw Permalink Normal View History

import {
TrendingUp,
DollarSign,
Calendar,
Clock,
CheckCircle2,
ListTodo,
AlertTriangle
} from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import { Project } from "@/types/project";
interface ProjectStatsProps {
project: Project;
taskStats: {
total: number;
completed: number;
inProgress: number;
highPriority: number;
overdue: number;
completionRate: number;
totalPlannedHours: number;
totalWorkedHours: number;
};
}
// Formatear moneda
function formatCurrency(amount: number): string {
return new Intl.NumberFormat("es-ES", {
style: "currency",
currency: "EUR",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}).format(amount);
}
// Formatear fecha
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString("es-ES", {
day: "numeric",
month: "short",
});
}
// Calcular días restantes
function getDaysRemaining(endDate: string): number {
const end = new Date(endDate);
const today = new Date();
const diff = end.getTime() - today.getTime();
return Math.ceil(diff / (1000 * 60 * 60 * 24));
}
export function ProjectStats({ project, taskStats }: ProjectStatsProps) {
const daysRemaining = getDaysRemaining(project.endDate);
const isOverdue = daysRemaining < 0;
const budgetUsedPercent = project.budget > 0 ? (project.spent / project.budget) * 100 : 0;
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{/* Progreso */}
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-blue-100 dark:bg-blue-900/30">
<TrendingUp className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-muted-foreground">Progreso</p>
<p className="text-2xl font-bold">{project.progress}%</p>
</div>
</div>
<Progress value={project.progress} className="mt-3 h-1.5" />
</CardContent>
</Card>
{/* Presupuesto */}
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-green-100 dark:bg-green-900/30">
<DollarSign className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-muted-foreground">Presupuesto</p>
<p className="text-2xl font-bold">{formatCurrency(project.budget)}</p>
</div>
</div>
<div className="mt-3 flex items-center justify-between text-xs">
<span className="text-muted-foreground">
Usado: {formatCurrency(project.spent)}
</span>
<span className={budgetUsedPercent > 90 ? "text-red-500" : "text-muted-foreground"}>
{budgetUsedPercent.toFixed(0)}%
</span>
</div>
</CardContent>
</Card>
{/* Fecha límite */}
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${isOverdue ? "bg-red-100 dark:bg-red-900/30" : "bg-purple-100 dark:bg-purple-900/30"}`}>
<Calendar className={`h-5 w-5 ${isOverdue ? "text-red-600 dark:text-red-400" : "text-purple-600 dark:text-purple-400"}`} />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-muted-foreground">Fecha límite</p>
<p className="text-2xl font-bold">{formatDate(project.endDate)}</p>
</div>
</div>
<p className={`mt-3 text-xs ${isOverdue ? "text-red-500" : "text-muted-foreground"}`}>
{isOverdue
? `${Math.abs(daysRemaining)} días de retraso`
: `${daysRemaining} días restantes`
}
</p>
</CardContent>
</Card>
{/* Tareas */}
<Card>
<CardContent className="p-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-orange-100 dark:bg-orange-900/30">
<ListTodo className="h-5 w-5 text-orange-600 dark:text-orange-400" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-muted-foreground">Tareas</p>
<p className="text-2xl font-bold">{taskStats.completed}/{taskStats.total}</p>
</div>
</div>
<div className="mt-3 flex items-center gap-3 text-xs">
{taskStats.highPriority > 0 && (
<span className="flex items-center gap-1 text-red-500">
<AlertTriangle className="h-3 w-3" />
{taskStats.highPriority} alta prioridad
</span>
)}
{taskStats.overdue > 0 && (
<span className="flex items-center gap-1 text-yellow-600 dark:text-yellow-500">
<Clock className="h-3 w-3" />
{taskStats.overdue} vencidas
</span>
)}
{taskStats.highPriority === 0 && taskStats.overdue === 0 && (
<span className="flex items-center gap-1 text-green-600 dark:text-green-500">
<CheckCircle2 className="h-3 w-3" />
Todo en orden
</span>
)}
</div>
</CardContent>
</Card>
</div>
);
}