Consumido
{spentPercentage}%
90 ? "bg-red-500" : spentPercentage > 70 ? "bg-yellow-500" : "bg-blue-500"
}`}
style={{ width: `${Math.min(spentPercentage, 100)}%` }}
/>
);
}
// Funciones helper para calcular estadísticas
function calculateStats(projects: Project[]) {
const total = projects.length;
if (total === 0) {
return {
total: 0,
avgProgress: 0,
totalBudget: 0,
totalSpent: 0,
avgBudget: 0,
budgetConsumed: 0,
minBudget: 0,
maxBudget: 0,
uniqueClients: 0,
oldestProject: null as string | null,
newestProject: null as string | null,
avgDuration: 0,
};
}
const totalBudget = projects.reduce((acc, p) => acc + p.budget, 0);
const totalSpent = projects.reduce((acc, p) => acc + p.spent, 0);
const avgProgress = Math.round(projects.reduce((acc, p) => acc + p.progress, 0) / total);
const avgBudget = Math.round(totalBudget / total);
const budgetConsumed = totalBudget > 0 ? Math.round((totalSpent / totalBudget) * 100) : 0;
const budgets = projects.map((p) => p.budget);
const minBudget = Math.min(...budgets);
const maxBudget = Math.max(...budgets);
const uniqueClients = new Set(projects.map((p) => p.client)).size;
const dates = projects.map((p) => new Date(p.startDate).getTime()).filter((d) => !isNaN(d));
const oldestProject = dates.length > 0 ? new Date(Math.min(...dates)).toLocaleDateString("es-ES") : null;
const newestProject = dates.length > 0 ? new Date(Math.max(...dates)).toLocaleDateString("es-ES") : null;
// Calcular duracion promedio en dias
const durations = projects
.map((p) => {
const start = new Date(p.startDate).getTime();
const end = new Date(p.endDate).getTime();
if (isNaN(start) || isNaN(end)) return null;
return Math.ceil((end - start) / (1000 * 60 * 60 * 24));
})
.filter((d): d is number => d !== null && d > 0);
const avgDuration = durations.length > 0 ? Math.round(durations.reduce((a, b) => a + b, 0) / durations.length) : 0;
return {
total,
avgProgress,
totalBudget,
totalSpent,
avgBudget,
budgetConsumed,
minBudget,
maxBudget,
uniqueClients,
oldestProject,
newestProject,
avgDuration,
};
}
// Panel de estadísticas para "Todos los proyectos"
interface AllProjectsStatsProps {
projects: Project[];
}
function AllProjectsStats({ projects }: AllProjectsStatsProps) {
const stats = calculateStats(projects);
const activeProjects = projects.filter((p) => p.status !== "2" && p.progress < 100);
const completedProjects = projects.filter((p) => p.status === "2" || p.progress >= 100);
return (
{/* Cards principales */}
}
highlight
/>
0 ? Math.round((activeProjects.length / stats.total) * 100) : 0}% del total`}
icon={}
/>
0 ? Math.round((completedProjects.length / stats.total) * 100) : 0}% del total`}
icon={}
/>
}
/>
{/* Segunda fila: Presupuesto */}
}
/>
}
/>
}
/>
}
/>
{/* Tercera fila: Fechas y distribucion */}
}
/>
}
/>
}
/>
{/* Cuarta fila: Graficos/Distribuciones */}
{/* Info adicional: Rango de presupuestos */}
Rango de Presupuestos
Minimo
{stats.minBudget.toLocaleString("es-ES")}
Maximo
{stats.maxBudget.toLocaleString("es-ES")}
Proyectos por Progreso
{projects.filter((p) => p.progress < 25).length}
0-25%
{projects.filter((p) => p.progress >= 25 && p.progress < 50).length}
25-50%
{projects.filter((p) => p.progress >= 50 && p.progress < 75).length}
50-75%
{projects.filter((p) => p.progress >= 75).length}
75-100%
);
}
// Panel de estadísticas para "Proyectos Activos"
interface ActiveProjectsStatsProps {
projects: Project[];
}
function ActiveProjectsStats({ projects }: ActiveProjectsStatsProps) {
// Filtrar solo proyectos activos (no cerrados y no completados al 100%)
const activeProjects = projects.filter((p) => p.status !== "2" && p.progress < 100);
const stats = calculateStats(activeProjects);
if (activeProjects.length === 0) {
return (
No hay proyectos activos
Todos los proyectos estan completados o cerrados
);
}
// Proyectos que necesitan atencion (bajo progreso o alto consumo de presupuesto)
const needsAttention = activeProjects.filter((p) => {
const spentPercentage = p.budget > 0 ? (p.spent / p.budget) * 100 : 0;
return p.progress < 25 || spentPercentage > 80;
});
// Proyectos proximos a terminar
const nearCompletion = activeProjects.filter((p) => p.progress >= 75);
return (
{/* Cards principales */}
}
highlight
/>
}
/>
}
/>
}
/>
{/* Alertas */}
Requieren Atencion
{needsAttention.length}
Proyectos con bajo progreso o alto consumo de presupuesto
Proximos a Completar
{nearCompletion.length}
Proyectos con 75% o mas de progreso
{/* Estadísticas adicionales */}
}
/>
}
/>
}
/>
{/* Distribucion y Budget */}
{/* Proyectos por progreso */}
Distribucion por Progreso
{activeProjects.filter((p) => p.progress < 25).length}
Inicio (0-25%)
{activeProjects.filter((p) => p.progress >= 25 && p.progress < 50).length}
En curso (25-50%)
{activeProjects.filter((p) => p.progress >= 50 && p.progress < 75).length}
Avanzado (50-75%)
{activeProjects.filter((p) => p.progress >= 75).length}
Casi listo (75-99%)
);
}
// Panel de estadísticas para "Proyectos Completados"
interface CompletedProjectsStatsProps {
projects: Project[];
}
function CompletedProjectsStats({ projects }: CompletedProjectsStatsProps) {
// Filtrar proyectos completados (cerrados o 100% progreso)
const completedProjects = projects.filter((p) => p.status === "2" || p.progress >= 100);
const stats = calculateStats(completedProjects);
if (completedProjects.length === 0) {
return (
No hay proyectos completados
Aun no se ha completado ningun proyecto
);
}
// Proyectos que terminaron dentro del presupuesto
const withinBudget = completedProjects.filter((p) => p.spent <= p.budget);
const overBudget = completedProjects.filter((p) => p.spent > p.budget);
// Eficiencia promedio (presupuesto restante / presupuesto total)
const avgEfficiency =
completedProjects.length > 0
? Math.round(
completedProjects.reduce((acc, p) => {
if (p.budget === 0) return acc;
return acc + ((p.budget - p.spent) / p.budget) * 100;
}, 0) / completedProjects.length
)
: 0;
return (
{/* Cards principales */}
}
highlight
/>
}
/>
}
/>
}
/>
{/* Cumplimiento de presupuesto */}
Dentro del Presupuesto
{withinBudget.length}
{stats.total > 0 ? Math.round((withinBudget.length / stats.total) * 100) : 0}% de los proyectos completados
Excedieron Presupuesto
{overBudget.length}
{stats.total > 0 ? Math.round((overBudget.length / stats.total) * 100) : 0}% de los proyectos completados
{/* Estadísticas adicionales */}
}
/>
}
/>
}
/>
{/* Distribucion y Budget */}
Rango de Presupuestos Completados
Minimo
{stats.minBudget.toLocaleString("es-ES")}
Maximo
{stats.maxBudget.toLocaleString("es-ES")}
{/* Timeline */}
Rango de Fechas
Primer Proyecto
{stats.oldestProject || "N/A"}
Ultimo Proyecto
{stats.newestProject || "N/A"}
);
}
// Componente de skeleton para carga
function StatisticsSkeleton() {
return (
{Array.from({ length: 4 }).map((_, i) => (
))}
{Array.from({ length: 4 }).map((_, i) => (
))}
);
}
// Componente principal de la página
export default function StatisticsPage() {
const [projects, setProjects] = useState
([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function loadProjects() {
try {
setLoading(true);
const data = await getProjects();
setProjects(data);
setError(null);
} catch (err) {
console.error("Error loading projects:", err);
setError("Error al cargar los proyectos");
} finally {
setLoading(false);
}
}
loadProjects();
}, []);
if (error) {
return (
{error}
);
}
const activeCount = projects.filter((p) => p.status !== "2" && p.progress < 100).length;
const completedCount = projects.filter((p) => p.status === "2" || p.progress >= 100).length;
return (
{/* Header */}
Estadisticas
Analisis detallado de {projects.length} proyectos
{/* Main content */}
{loading ? (
) : (
Todos
{projects.length}
Activos
{activeCount}
Completados
{completedCount}
)}
);
}