diff --git a/app/(private)/Reportes/ReportesClient.tsx b/app/(private)/Reportes/ReportesClient.tsx index 8919e67..125b280 100644 --- a/app/(private)/Reportes/ReportesClient.tsx +++ b/app/(private)/Reportes/ReportesClient.tsx @@ -19,7 +19,7 @@ export default function ReportesClient({ const [periodo2, setPeriodo2] = useState(null); return ( -
+

REPORTES

+
{ setPeriodo1(p1); setPeriodo2(p2); }} /> - - - + {periodo1 && periodo2 && ( + + )} +
), } ]} diff --git a/app/Components/Reportes/porServicio.tsx b/app/Components/Reportes/porServicio.tsx index c1e85f3..b1e8fe6 100644 --- a/app/Components/Reportes/porServicio.tsx +++ b/app/Components/Reportes/porServicio.tsx @@ -4,6 +4,17 @@ import { useEffect, useState } from "react"; import axios from "axios"; import Cookies from "js-cookie"; +import { + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, + Line, + LineChart, +} from "recharts"; + interface Periodo { id_periodo: number; semestre: string; @@ -20,6 +31,7 @@ export default function PorServicio({ periodo1, periodo2 }: Props) { const [data, setData] = useState([]); const [periodos, setPeriodos] = useState([]); const [loading, setLoading] = useState(false); + const [serviciosSeleccionados, setServiciosSeleccionados] = useState([]); useEffect(() => { if (!periodo1 || !periodo2) return; @@ -70,8 +82,8 @@ export default function PorServicio({ periodo1, periodo2 }: Props) { }); const finalData = Array.from(map.values()); - setData(finalData); + setServiciosSeleccionados(finalData.map((item: any) => item.servicio)); } catch (error) { console.error("Error comparando servicios", error); } finally { @@ -82,43 +94,179 @@ export default function PorServicio({ periodo1, periodo2 }: Props) { fetchServicios(); }, [periodo1, periodo2]); + // 🔥 TRANSFORMAR DATA PARA GRAFICA + const transformDataForChart = () => { + if (!data.length || !periodos.length) return []; + + return periodos.map((p) => { + const row: any = { periodo: p.semestre }; + + data.forEach((servicio) => { + row[servicio.servicio] = servicio[p.semestre] || 0; + }); + + return row; + }); + }; + + // FORMATO DE NUMEROS + const formatNumber = (value: number) => { + return value.toLocaleString("en-US", { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }); + }; + + // TOOLTIP ORDENADO + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + const sorted = [...payload] + .filter((item) => item.value > 0) + .sort((a, b) => b.value - a.value); + + return ( +
+

+ Periodo: {label} +

+ + {sorted.map((entry: any, index: number) => ( +
+ {entry.name}: ${formatNumber(entry.value)} +
+ ))} +
+ ); + } + + return null; + }; + + const chartData = transformDataForChart(); + + const colors = ["black", "red", "blue", "orange", "purple", "green"]; + return ( -
- {loading &&

Cargando...

} +
- - - - + {/* TABLA */} +
+ {loading &&

Cargando...

} - {periodos.map((p) => ( -
- ))} - - - - - {!loading && data.length === 0 && ( +
Servicio{p.semestre}
+ - - - )} - - {data.map((row) => ( - - + {periodos.map((p) => ( - + ))} - ))} - -
- No hay datos en este rango -
{row.servicio}Servicio - ${(row[p.semestre] || 0).toFixed(2)} - {p.semestre}
+ + + + {!loading && data.length === 0 && ( + + + No hay datos en este rango + + + )} + + {data.map((row) => ( + + {row.servicio} + + {periodos.map((p) => ( + + ${formatNumber(row[p.semestre] || 0)} + + ))} + + ))} + + +
+
+ {data.map((servicio) => ( + + ))} +
+ {/* GRAFICA */} +
+ {chartData.length > 0 && ( + + + + + value.toLocaleString()} /> + + } /> + + + + {data + .filter((servicio) => + serviciosSeleccionados.includes(servicio.servicio) + ) + .map((servicio, i) => ( + + ))} + + + )} +
); } \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index eea513f..e0f1915 100644 --- a/app/globals.css +++ b/app/globals.css @@ -87,7 +87,7 @@ footer p { height: 600px; min-height: 520px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - overflow: hidden; + overflow: visible; background-color: #f9f9f9; z-index: 0; animation: fadeInUp 1s ease forwards; @@ -99,6 +99,7 @@ footer p { height: 100%; display: flex; flex-direction: column; + overflow: visible; } .containerForm {