From e1f0f37946d9a2cde1718f9c41333a6e35df0ca1 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 4 Sep 2026 15:57:42 -0500 Subject: [PATCH] =?UTF-8?q?A=C3=B1adir=20frontend/src/pages/Programa.jsx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Programa.jsx | 439 ++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) create mode 100644 frontend/src/pages/Programa.jsx diff --git a/frontend/src/pages/Programa.jsx b/frontend/src/pages/Programa.jsx new file mode 100644 index 0000000..eabff7c --- /dev/null +++ b/frontend/src/pages/Programa.jsx @@ -0,0 +1,439 @@ +import { useState, useEffect, useMemo } from "react"; +import { api } from "../lib/api"; +import { Loader, ErrorBox, Filters, Select } from "../components/UI"; +import { + AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, ReferenceLine, +} from "recharts"; + +const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"]; + +export default function Programa({ inicial = null, onConsumirInicial } = {}) { + const _hoy = new Date(); + const [ano, setAno] = useState(String(inicial?.ano ?? _hoy.getFullYear())); + const [mes, setMes] = useState(String(inicial?.mes ?? (_hoy.getMonth() + 1))); + const [programa, setPrograma] = useState("TODOS"); + + const [filtros, setFiltros] = useState({ anos: [2026] }); + const [progOpc, setProgOpc] = useState([]); // lista de programas del periodo + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [recargando, setRecargando] = useState(false); + const [error, setError] = useState(null); + const [reload, setReload] = useState(0); + // Programa que se debe seleccionar automaticamente al llegar por enlace (Leads). + const [progPendiente, setProgPendiente] = useState(inicial?.programa || null); + + useEffect(() => { api.leadsFiltros().then(setFiltros).catch(() => {}); }, []); + useEffect(() => { + const onActualizar = () => setReload((n) => n + 1); + window.addEventListener("datos-actualizar", onActualizar); + return () => window.removeEventListener("datos-actualizar", onActualizar); + }, []); + + // Cargar la lista de programas cuando cambia año/mes; resetea el programa elegido. + // NO depende de 'reload' para no perder el programa seleccionado al guardar leyenda. + useEffect(() => { + api.programaOpciones(ano, mes) + .then((r) => setProgOpc(r.programas || [])) + .catch(() => setProgOpc([])); + // Si venimos por enlace desde Leads, NO reseteamos a TODOS (el otro effect lo + // seleccionara cuando la lista de programas ya lo contenga). + if (!progPendiente) { setPrograma("TODOS"); setData(null); } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ano, mes]); + + // Al llegar por enlace: cuando la lista de programas ya incluye el pendiente, + // seleccionarlo una sola vez y consumir el estado inicial. + useEffect(() => { + if (!progPendiente) return; + if (progOpc.includes(progPendiente)) { + setPrograma(progPendiente); + setProgPendiente(null); + if (onConsumirInicial) onConsumirInicial(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [progOpc, progPendiente]); + + // Al guardar leyenda (reload), refrescar la lista de programas SIN resetear el elegido. + useEffect(() => { + if (reload === 0) return; + api.programaOpciones(ano, mes) + .then((r) => setProgOpc(r.programas || [])) + .catch(() => {}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [reload]); + + // Cargar datos del programa elegido + useEffect(() => { + if (programa === "TODOS") { setData(null); return; } + let activo = true; + if (data) setRecargando(true); else setLoading(true); + api.programa(ano, mes, programa) + .then((r) => { if (activo) { setData(r); setError(null); } }) + .catch((e) => { if (activo) setError(e.message); }) + .finally(() => { if (activo) { setLoading(false); setRecargando(false); } }); + return () => { activo = false; }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ano, mes, programa, reload]); + + const num = (v) => Number(v || 0).toLocaleString("es-PE"); + + const optAnos = useMemo(() => + (filtros.anos || [2026]).map((a) => ({ value: String(a), label: String(a) })), [filtros]); + const optMes = useMemo(() => + MESES.map((m, i) => ({ value: String(i + 1), label: m })), []); + const optProg = useMemo(() => + [{ value: "TODOS", label: "— Elige un programa —" }, + ...progOpc.map((p) => ({ value: p, label: p }))], [progOpc]); + + return ( +
+

📘 Programa

+ + + +