Actualizar frontend/src/pages/Vendedores.jsx

This commit is contained in:
2026-09-19 11:46:28 -05:00
parent bd864dbd34
commit 2e25005d63

View File

@@ -14,6 +14,11 @@ export default function Vendedores() {
const [mes, setMes] = useState(String(_hoy.getMonth() + 1)); // mes actual
const [dia, setDia] = useState("TODOS");
const [modoTR, setModoTR] = useState("asig"); // "asig" | "resp" para el grafico de tiempo
// Pop-up de detalle al hacer click en un gráfico
const [detDia, setDetDia] = useState(null);
const [detData, setDetData] = useState(null);
const [detLoading, setDetLoading] = useState(false);
const [detModo, setDetModo] = useState("asignacion");
const [filtros, setFiltros] = useState({ anos: [2026] });
const [data, setData] = useState(null);
@@ -103,6 +108,16 @@ export default function Vendedores() {
return data.tiempo_resp_r_total || [];
}, [data, vend]);
const abrirDetalle = (diaNum, modoForzado) => {
if (!diaNum) return;
const modo = modoForzado || (modoTR === "resp" ? "respuesta" : "asignacion");
setDetDia(diaNum); setDetModo(modo); setDetData(null); setDetLoading(true);
api.tiempoRespuestaDetalle(diaNum, modo, vend, ano, mes)
.then((r) => setDetData(r))
.catch(() => setDetData({ asesores: [], total: { cantidad: 0, promedio_min: 0 } }))
.finally(() => setDetLoading(false));
};
return (
<div>
<h1 className="page-title">🧑‍💼 Vendedores</h1>
@@ -222,7 +237,9 @@ export default function Vendedores() {
</div>
<div style={{ flex: 1 }}>
<ResponsiveContainer width="100%" height="100%">
<LineChart data={serie} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}>
<LineChart data={serie} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}
onClick={(p) => abrirDetalle(p && p.activePayload && p.activePayload[0] && p.activePayload[0].payload.dia)}
style={{ cursor: "pointer" }}>
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" vertical={false} />
<XAxis dataKey="dia" tick={{ fontSize: 10, fill: "#94a3b8" }} interval={0}
axisLine={{ stroke: "#e2e8f0" }} tickLine={false} />
@@ -247,7 +264,7 @@ export default function Vendedores() {
<Line type="linear" dataKey="promedio_min" name="Promedio (min)" stroke={color}
strokeWidth={2.5}
dot={{ r: 3, fill: color, stroke: "#fff", strokeWidth: 1.5 }}
activeDot={{ r: 6, fill: colorSoft, stroke: "#fff", strokeWidth: 2 }} />
activeDot={{ r: 7, fill: colorSoft, stroke: "#fff", strokeWidth: 2, cursor: "pointer" }} />
</LineChart>
</ResponsiveContainer>
</div>
@@ -278,7 +295,9 @@ export default function Vendedores() {
}
return (
<ResponsiveContainer width="100%" height="100%">
<BarChart data={datosNR} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}>
<BarChart data={datosNR} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}
onClick={(p) => abrirDetalle(p && p.activePayload && p.activePayload[0] && p.activePayload[0].payload.dia, "sin_responder")}
style={{ cursor: "pointer" }}>
<defs>
<linearGradient id="gradNoResp" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#fb7185" stopOpacity={1} />
@@ -357,6 +376,73 @@ export default function Vendedores() {
</div>
</div>}
{/* Pop-up: detalle del día (leads por asesor con T1, T2 y minutos) */}
{detDia != null && (
<div onClick={() => setDetDia(null)}
style={{ position: "fixed", inset: 0, background: "rgba(15,23,42,0.45)",
display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, padding: 20 }}>
<div onClick={(e) => e.stopPropagation()}
style={{ background: "#fff", borderRadius: 14, width: "100%", maxWidth: 820,
maxHeight: "85vh", overflow: "auto", boxShadow: "0 20px 60px rgba(0,0,0,0.3)" }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
padding: "16px 20px", borderBottom: "1px solid #f1f5f9", position: "sticky", top: 0, background: "#fff" }}>
<div style={{ fontWeight: 800, fontSize: 16, color: "#0f172a" }}>
{detModo === "sin_responder" ? "🚫" : "⏱️"} Detalle Día {detDia} · {
detModo === "sin_responder" ? "Sin Responder"
: detModo === "respuesta" ? "Según Respuesta" : "Según Asignación"}
{detData && <span style={{ fontWeight: 600, color: "#64748b", fontSize: 13 }}>
{" · "}{detModo === "sin_responder"
? `${detData.total?.cantidad || 0} sin responder`
: `Promedio: ${detData.total?.promedio_min || 0} min (${detData.total?.cantidad || 0} leads)`}</span>}
</div>
<button onClick={() => setDetDia(null)}
style={{ border: "none", background: "#f1f5f9", borderRadius: 8, width: 32, height: 32,
cursor: "pointer", fontSize: 16, color: "#334155" }}>✕</button>
</div>
<div style={{ padding: "14px 20px" }}>
{detLoading ? (
<div style={{ padding: 30, textAlign: "center", color: "#94a3b8" }}>Cargando…</div>
) : !detData || (detData.asesores || []).length === 0 ? (
<div style={{ padding: 30, textAlign: "center", color: "#94a3b8" }}>Sin leads para este día.</div>
) : (
(detData.asesores || []).map((a) => (
<div key={a.asesor} style={{ marginBottom: 18 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
background: "#eff6ff", borderRadius: 8, padding: "8px 12px", marginBottom: 6 }}>
<span style={{ fontWeight: 800, color: "#1e40af", fontSize: 13.5 }}>👤 {a.asesor}</span>
<span style={{ fontSize: 12.5, color: "#334155", fontWeight: 700 }}>
{detModo === "sin_responder"
? `${a.cantidad} sin responder`
: `Promedio: ${a.promedio_min} min · ${a.cantidad} leads`}</span>
</div>
<table className="cob-table" style={{ width: "100%" }}>
<thead>
<tr>
<th style={{ textAlign: "center" }}>TELÉFONO</th>
<th style={{ textAlign: "center" }}>ASIGNACIÓN (T1)</th>
<th style={{ textAlign: "center" }}>RESPUESTA (T2)</th>
<th style={{ textAlign: "center" }}>MINUTOS</th>
</tr>
</thead>
<tbody>
{a.leads.map((l, i) => (
<tr key={i}>
<td style={{ textAlign: "center", whiteSpace: "nowrap", color: "#334155" }}>{l.telefono || "-"}</td>
<td style={{ textAlign: "center", whiteSpace: "nowrap" }}>{l.t1}</td>
<td style={{ textAlign: "center", whiteSpace: "nowrap" }}>{l.t2}</td>
<td style={{ textAlign: "center", fontWeight: 700, color: "#b45309" }}>{l.minutos}</td>
</tr>
))}
</tbody>
</table>
</div>
))
)}
</div>
</div>
</div>
)}
</div>
);
}