Actualizar dominios a dashboardapi y dashboardv1
This commit is contained in:
423
frontend/src/pages/Vendedores.jsx
Normal file
423
frontend/src/pages/Vendedores.jsx
Normal file
@@ -0,0 +1,423 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { api } from "../lib/api";
|
||||
import { Loader, ErrorBox, Filters, Select } from "../components/UI";
|
||||
import {
|
||||
AreaChart, Area, BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
||||
} from "recharts";
|
||||
|
||||
const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"];
|
||||
|
||||
export default function Vendedores() {
|
||||
const _hoy = new Date();
|
||||
const [vend, setVend] = useState("TODOS");
|
||||
const [ano, setAno] = useState(String(_hoy.getFullYear())); // año actual
|
||||
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
|
||||
|
||||
const [filtros, setFiltros] = useState({ anos: [2026] });
|
||||
const [data, setData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [recargando, setRecargando] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [reload, setReload] = useState(0);
|
||||
|
||||
useEffect(() => { api.leadsFiltros().then(setFiltros).catch(() => {}); }, []);
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setReload((n) => n + 1), 15 * 60 * 1000);
|
||||
const onActualizar = () => setReload((n) => n + 1); // recarga al guardar en Config
|
||||
window.addEventListener("datos-actualizar", onActualizar);
|
||||
return () => { clearInterval(id); window.removeEventListener("datos-actualizar", onActualizar); };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let activo = true;
|
||||
if (!data) setLoading(true);
|
||||
setRecargando(true); // atenuar contenido mientras se recalcula
|
||||
setError(null);
|
||||
api.vendedores(ano, mes, dia)
|
||||
.then((res) => { if (activo) { setData(res); setLoading(false); setRecargando(false); } })
|
||||
.catch((e) => { if (activo) { setError(e.message); setLoading(false); setRecargando(false); } });
|
||||
return () => { activo = false; };
|
||||
}, [ano, mes, dia, reload]);
|
||||
|
||||
const optVend = useMemo(() => ["TODOS", ...((data && data.vendedores) || [])], [data]);
|
||||
const optAnos = useMemo(() => {
|
||||
const anos = (filtros.anos || []).map(String);
|
||||
const actual = String(_hoy.getFullYear());
|
||||
if (!anos.includes(actual)) anos.push(actual); // asegura que el año actual sea opcion
|
||||
return ["TODOS", ...anos];
|
||||
}, [filtros]);
|
||||
const optMes = useMemo(() => [{ value: "TODOS", label: "TODOS" },
|
||||
...MESES.map((m,i)=>({ value: String(i+1), label: m }))], []);
|
||||
const optDia = useMemo(() => ["TODOS", ...Array.from({length:31}, (_,i)=>String(i+1))], []);
|
||||
|
||||
// Filas visibles: si hay vendedor filtrado, solo esa; si no, todas
|
||||
const filas = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend === "TODOS") return data.filas;
|
||||
return data.filas.filter((f) => f.vendedor === vend);
|
||||
}, [data, vend]);
|
||||
|
||||
const num = (v) => Number(v || 0).toLocaleString("es-PE");
|
||||
|
||||
// Resumen para las tarjetas: si hay 1 vendedor filtrado -> esa fila; si TODOS -> el total
|
||||
const resumen = useMemo(() => {
|
||||
if (!data) return null;
|
||||
if (vend !== "TODOS") return data.filas.find((f) => f.vendedor === vend) || null;
|
||||
return data.total || null;
|
||||
}, [data, vend]);
|
||||
|
||||
// Matriz por programa: si hay 1 vendedor -> la suya; si TODOS -> el total
|
||||
const matrizProg = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend !== "TODOS") return (data.matriz_prog && data.matriz_prog[vend]) || [];
|
||||
return data.matriz_prog_total || [];
|
||||
}, [data, vend]);
|
||||
|
||||
// Matriz Estado/Objeción (Ultima_Etiqueta): por vendedor o total
|
||||
const matrizEtiq = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend !== "TODOS") return (data.matriz_etiq && data.matriz_etiq[vend]) || [];
|
||||
return data.matriz_etiq_total || [];
|
||||
}, [data, vend]);
|
||||
|
||||
// Serie diaria (asignados + matriculas): por vendedor o total
|
||||
const serieDia = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend !== "TODOS") return (data.serie_dia && data.serie_dia[vend]) || [];
|
||||
return data.serie_dia_total || [];
|
||||
}, [data, vend]);
|
||||
|
||||
// Serie tiempo de respuesta (promedio min por dia de asignacion)
|
||||
const serieTR = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend !== "TODOS") return (data.tiempo_resp && data.tiempo_resp[vend]) || [];
|
||||
return data.tiempo_resp_total || [];
|
||||
}, [data, vend]);
|
||||
|
||||
// Serie tiempo de respuesta (promedio min por dia de RESPUESTA)
|
||||
const serieTRr = useMemo(() => {
|
||||
if (!data) return [];
|
||||
if (vend !== "TODOS") return (data.tiempo_resp_r && data.tiempo_resp_r[vend]) || [];
|
||||
return data.tiempo_resp_r_total || [];
|
||||
}, [data, vend]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="page-title">🧑💼 Vendedores</h1>
|
||||
|
||||
<Filters>
|
||||
<Select label="Vendedor" value={vend} options={optVend} onChange={setVend} />
|
||||
<Select label="Año" value={ano} options={optAnos} onChange={setAno} />
|
||||
<Select label="Mes" value={mes} options={optMes} onChange={setMes} />
|
||||
<Select label="Día" value={dia} options={optDia} onChange={setDia} />
|
||||
</Filters>
|
||||
|
||||
{loading ? <Loader text="Cargando vendedores..." /> :
|
||||
error ? <ErrorBox msg={error} /> :
|
||||
!data ? <ErrorBox msg="Sin datos" /> :
|
||||
<div style={{ opacity: recargando ? 0.45 : 1,
|
||||
filter: recargando ? "grayscale(0.2)" : "none",
|
||||
pointerEvents: recargando ? "none" : "auto",
|
||||
transition: "opacity 0.25s ease, filter 0.25s ease" }}>
|
||||
{resumen && <ResumenTarjetas r={resumen} num={num} />}
|
||||
|
||||
{/* Gráfico día a día + Estado/Objeción lado a lado */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "minmax(380px, 2fr) minmax(300px, 1fr)",
|
||||
gap: 16, marginTop: 16 }}>
|
||||
|
||||
{/* Gráfico día a día: Leads Procesados vs Matrículas */}
|
||||
<div className="card">
|
||||
<div className="card-title">
|
||||
Leads Asignados y Matrículas por Día{vend !== "TODOS" ? ` — ${vend}` : ""}
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={360}>
|
||||
<AreaChart data={serieDia} margin={{ top: 10, right: 24, left: -6, bottom: 8 }}>
|
||||
<defs>
|
||||
<linearGradient id="gradProcV" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#60a5fa" stopOpacity={0.55} />
|
||||
<stop offset="100%" stopColor="#60a5fa" stopOpacity={0.05} />
|
||||
</linearGradient>
|
||||
<linearGradient id="gradMatV" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#0f766e" stopOpacity={0.55} />
|
||||
<stop offset="100%" stopColor="#0f766e" stopOpacity={0.08} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#eef2f7" vertical={false} />
|
||||
<XAxis dataKey="dia" tick={{ fontSize: 10, fill: "#94a3b8" }} interval={0}
|
||||
axisLine={{ stroke: "#e2e8f0" }} tickLine={false} />
|
||||
<YAxis tick={{ fontSize: 11, fill: "#94a3b8" }} allowDecimals={false}
|
||||
axisLine={false} tickLine={false} width={32} />
|
||||
<Tooltip contentStyle={{ borderRadius: 10, border: "1px solid #e2e8f0",
|
||||
boxShadow: "0 6px 20px rgba(0,0,0,0.1)", fontSize: 12 }} />
|
||||
<Legend iconType="circle" wrapperStyle={{ fontSize: 12, paddingTop: 4 }} />
|
||||
<Area type="linear" dataKey="asignados" name="Leads Asignados" stroke="#60a5fa"
|
||||
fill="url(#gradProcV)" strokeWidth={2.5}
|
||||
dot={{ r: 2.5, fill: "#60a5fa", stroke: "#fff", strokeWidth: 1 }}
|
||||
activeDot={{ r: 5 }} />
|
||||
<Area type="linear" dataKey="matriculas" name="Matrículas" stroke="#0f766e"
|
||||
fill="url(#gradMatV)" strokeWidth={2.5}
|
||||
dot={{ r: 2.5, fill: "#0f766e", stroke: "#fff", strokeWidth: 1 }}
|
||||
activeDot={{ r: 5 }} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
{/* Matriz Estado/Objeción (Ultima_Etiqueta) — al costado del gráfico */}
|
||||
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
|
||||
<div className="card-title" style={{ padding: "14px 16px 6px" }}>
|
||||
Estado/Objeción{vend !== "TODOS" ? ` — ${vend}` : ""}
|
||||
</div>
|
||||
<div className="table-wrap" style={{ border: "none", maxHeight: 360, overflow: "auto" }}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-name" style={{ position: "sticky", top: 0, zIndex: 2, minWidth: 160 }}>Estado/Objeción</th>
|
||||
<th style={{ position: "sticky", top: 0, zIndex: 2 }}>Leads Asignados</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{matrizEtiq.map((e) => (
|
||||
<tr key={e.estado}>
|
||||
<td className="col-name">{e.estado}</td>
|
||||
<td>{num(e.cantidad)}</td>
|
||||
</tr>
|
||||
))}
|
||||
{matrizEtiq.length > 0 && (
|
||||
<tr className="total-row">
|
||||
<td className="col-name">Total</td>
|
||||
<td>{num(matrizEtiq.reduce((s, e) => s + (e.cantidad || 0), 0))}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Tiempo de respuesta + Sin responder, lado a lado */}
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 16, marginTop: 16 }}>
|
||||
{(() => {
|
||||
const esResp = modoTR === "resp";
|
||||
const serie = esResp ? serieTRr : serieTR;
|
||||
const color = esResp ? "#2563eb" : "#f59e0b";
|
||||
const colorSoft = esResp ? "#3b82f6" : "#fbbf24";
|
||||
return (
|
||||
<div>
|
||||
<div className="card" style={{ display: "flex", flexDirection: "column", height: 400 }}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
|
||||
height: 40, marginBottom: 8, gap: 8 }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: "#0f172a" }}>
|
||||
Tiempo de Respuesta por Día (min){vend !== "TODOS" ? ` — ${vend}` : ""}
|
||||
</div>
|
||||
<select value={modoTR} onChange={(e) => setModoTR(e.target.value)}
|
||||
style={{ fontSize: 12.5, padding: "6px 10px", borderRadius: 8,
|
||||
border: "1px solid #e2e8f0", background: "#f8fafc",
|
||||
color: "#334155", fontWeight: 600, cursor: "pointer" }}>
|
||||
<option value="asig">📌 Según Asignación</option>
|
||||
<option value="resp">✅ Según Respuesta</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={serie} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" vertical={false} />
|
||||
<XAxis dataKey="dia" tick={{ fontSize: 10, fill: "#94a3b8" }} interval={0}
|
||||
axisLine={{ stroke: "#e2e8f0" }} tickLine={false} />
|
||||
<YAxis tick={{ fontSize: 11, fill: "#94a3b8" }} allowDecimals={false}
|
||||
axisLine={false} tickLine={false} width={38} />
|
||||
<Tooltip content={({ active, payload, label }) => {
|
||||
if (!active || !payload || !payload.length) return null;
|
||||
const d = payload[0].payload || {};
|
||||
return (
|
||||
<div style={{ background: "#fff", borderRadius: 10, border: "1px solid #e2e8f0",
|
||||
boxShadow: "0 8px 24px rgba(0,0,0,0.12)", fontSize: 12,
|
||||
padding: "10px 13px", lineHeight: 1.7 }}>
|
||||
<div style={{ fontWeight: 700, color: "#0f172a", marginBottom: 4 }}>Día {label}</div>
|
||||
<div style={{ color, fontWeight: 700 }}>Promedio: {d.promedio_min || 0} min</div>
|
||||
<div style={{ color: "#0f766e" }}>Respondidos{esResp ? " ese día" : ""}: {d.respondidos || 0}</div>
|
||||
{esResp
|
||||
? <div style={{ color: "#94a3b8", fontSize: 11.5 }}>⚠ Respuesta tardía (+2 días): {d.tardios || 0}</div>
|
||||
: <div style={{ color: "#b91c1c" }}>Sin responder: {d.no_respondidos || 0}</div>}
|
||||
</div>
|
||||
);
|
||||
}} />
|
||||
<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 }} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Leads SIN responder por día (fecha de asignación) — barras */}
|
||||
<div>
|
||||
<div className="card" style={{ display: "flex", flexDirection: "column", height: 400 }}>
|
||||
<div className="card-title" style={{ display: "flex", alignItems: "center", gap: 6, height: 40, marginBottom: 8 }}>
|
||||
<span style={{ width: 10, height: 10, borderRadius: 3, background: "#f43f5e", display: "inline-block" }} />
|
||||
Leads Sin Responder por Día{vend !== "TODOS" ? ` — ${vend}` : ""}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
{(() => {
|
||||
const datosNR = serieTR.filter((d) => (d.no_respondidos || 0) >= 1);
|
||||
if (datosNR.length === 0) {
|
||||
return (
|
||||
<div style={{ height: "100%", display: "flex", flexDirection: "column",
|
||||
alignItems: "center", justifyContent: "center", color: "#94a3b8", gap: 8 }}>
|
||||
<div style={{ fontSize: 34 }}>✅</div>
|
||||
<div style={{ fontSize: 13.5, fontWeight: 600, color: "#0f766e" }}>Sin leads pendientes</div>
|
||||
<div style={{ fontSize: 12 }}>Todos los leads del periodo fueron respondidos.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={datosNR} margin={{ top: 12, right: 24, left: -6, bottom: 8 }}>
|
||||
<defs>
|
||||
<linearGradient id="gradNoResp" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#fb7185" stopOpacity={1} />
|
||||
<stop offset="100%" stopColor="#f43f5e" stopOpacity={0.85} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#f1f5f9" vertical={false} />
|
||||
<XAxis dataKey="dia" tick={{ fontSize: 10, fill: "#94a3b8" }} interval={0}
|
||||
axisLine={{ stroke: "#e2e8f0" }} tickLine={false} />
|
||||
<YAxis tick={{ fontSize: 11, fill: "#94a3b8" }} allowDecimals={false}
|
||||
axisLine={false} tickLine={false} width={32} />
|
||||
<Tooltip cursor={{ fill: "#fef2f2" }} content={({ active, payload, label }) => {
|
||||
if (!active || !payload || !payload.length) return null;
|
||||
const d = payload[0].payload || {};
|
||||
return (
|
||||
<div style={{ background: "#fff", borderRadius: 10, border: "1px solid #e2e8f0",
|
||||
boxShadow: "0 8px 24px rgba(0,0,0,0.12)", fontSize: 12,
|
||||
padding: "10px 13px", lineHeight: 1.7 }}>
|
||||
<div style={{ fontWeight: 700, color: "#0f172a", marginBottom: 4 }}>Día {label}</div>
|
||||
<div style={{ color: "#e11d48", fontWeight: 700 }}>Sin responder: {d.no_respondidos || 0}</div>
|
||||
<div style={{ color: "#0f766e" }}>Respondidos: {d.respondidos || 0}</div>
|
||||
</div>
|
||||
);
|
||||
}} />
|
||||
<Bar dataKey="no_respondidos" name="Sin responder" fill="url(#gradNoResp)" radius={[4, 4, 0, 0]} maxBarSize={26} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Matriz por tipo de programa (al final) */}
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div className="card" style={{ padding: 0, overflow: "hidden" }}>
|
||||
<div className="card-title" style={{ padding: "14px 16px 6px" }}>
|
||||
Matrículas por Tipo de Programa{vend !== "TODOS" ? ` — ${vend}` : ""}
|
||||
</div>
|
||||
<div className="table-wrap" style={{ border: "none", maxHeight: 420, overflow: "auto" }}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="col-name" style={{ position: "sticky", top: 0, zIndex: 2, minWidth: 150 }}>Tipo de Programa</th>
|
||||
<th style={{ position: "sticky", top: 0, zIndex: 2 }}>Total Matrículas</th>
|
||||
<th style={{ position: "sticky", top: 0, zIndex: 2 }}>Retiradas</th>
|
||||
<th style={{ position: "sticky", top: 0, zIndex: 2 }}>% Retirados</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{matrizProg.map((p) => (
|
||||
<tr key={p.programa}>
|
||||
<td className="col-name">{p.programa}</td>
|
||||
<td style={{ fontWeight: 600 }}>{num(p.total)}</td>
|
||||
<td>{num(p.retiradas)}</td>
|
||||
<td>{Number(p.pct_retiradas || 0).toLocaleString("es-PE")}%</td>
|
||||
</tr>
|
||||
))}
|
||||
{matrizProg.length > 0 && (
|
||||
<tr className="total-row">
|
||||
<td className="col-name">Total</td>
|
||||
<td>{num(matrizProg.reduce((s, p) => s + (p.total || 0), 0))}</td>
|
||||
<td>{num(matrizProg.reduce((s, p) => s + (p.retiradas || 0), 0))}</td>
|
||||
<td>{(() => {
|
||||
const t = matrizProg.reduce((s, p) => s + (p.total || 0), 0);
|
||||
const r = matrizProg.reduce((s, p) => s + (p.retiradas || 0), 0);
|
||||
return `${(t ? Math.round(r / t * 1000) / 10 : 0).toLocaleString("es-PE")}%`;
|
||||
})()}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── Tarjetas de resumen: 3 grupos (Cartera / Matrículas / Leads) en lista ─────
|
||||
function ResumenTarjetas({ r, num }) {
|
||||
const pct = Number(r.pct_retiradas || 0);
|
||||
const pctCont = Number(r.pct_contactados || 0);
|
||||
const grupos = [
|
||||
{
|
||||
titulo: "Cartera", acento: "#2563eb",
|
||||
filas: [
|
||||
{ label: "Total", valor: num(r.cartera_total) },
|
||||
{ label: "Copito", valor: num(r.cartera_copito) },
|
||||
{ label: "Otros", valor: num(r.cartera_otros) },
|
||||
],
|
||||
},
|
||||
{
|
||||
titulo: "Matrículas", acento: "#0f766e",
|
||||
filas: [
|
||||
{ label: "Total", valor: num(r.mat_total) },
|
||||
{ label: "Retiradas", valor: num(r.mat_retiradas) },
|
||||
{ label: "% Retirados", valor: `${pct.toLocaleString("es-PE")}%`, alerta: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
titulo: "Leads", acento: "#7c3aed",
|
||||
filas: [
|
||||
{ label: "Asignados", valor: num(r.leads_asignados) },
|
||||
{ label: "Contactados", valor: num(r.leads_contactados) },
|
||||
{ label: "% Contactados", valor: `${pctCont.toLocaleString("es-PE")}%`, resalta: "#7c3aed" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
|
||||
gap: 12, marginBottom: 18 }}>
|
||||
{grupos.map((g) => (
|
||||
<div key={g.titulo} style={{
|
||||
background: "#fff", border: "1px solid #e8ecf3", borderRadius: 12,
|
||||
padding: "14px 16px", boxShadow: "0 1px 3px rgba(16,24,40,0.05)" }}>
|
||||
<div style={{ fontSize: 11, fontWeight: 700, color: g.acento,
|
||||
textTransform: "uppercase", letterSpacing: 0.5, marginBottom: 10 }}>
|
||||
{g.titulo}
|
||||
</div>
|
||||
{g.filas.map((f, i) => (
|
||||
<div key={f.label} style={{
|
||||
display: "flex", justifyContent: "space-between", alignItems: "center",
|
||||
padding: "6px 0",
|
||||
borderBottom: i < g.filas.length - 1 ? "1px solid #f1f5f9" : "none" }}>
|
||||
<span style={{ fontSize: 12.5, color: "#64748b" }}>{f.label}</span>
|
||||
<span style={{ fontSize: 15, fontWeight: 700,
|
||||
color: f.alerta ? "#b91c1c" : (f.resalta || "#0f172a") }}>
|
||||
{f.valor}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user