Actualizar frontend/src/pages/Asesores.jsx

This commit is contained in:
2026-09-11 15:33:14 -05:00
parent 8a4a3d6641
commit 9e9ac1f901

View File

@@ -14,6 +14,27 @@ export default function Asesores() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(null); const [error, setError] = useState(null);
const [cambiando, setCambiando] = useState(null); const [cambiando, setCambiando] = useState(null);
const [histAsesor, setHistAsesor] = useState(null); // { nombre, email }
const [histFilas, setHistFilas] = useState([]);
const [histLoading, setHistLoading] = useState(false);
async function verHistorial(ag) {
const nombre = ag.name || ag.available_name || "—";
setHistAsesor({ nombre, email: ag.email });
setHistFilas([]); setHistLoading(true);
try {
const r = await api.asesorHistorial(ag.email);
setHistFilas(r.historial || []);
} catch (e) { setHistFilas([]); }
setHistLoading(false);
}
const fmtHora = (iso) => {
if (!iso) return "—";
const d = new Date(iso);
return d.toLocaleString("es-PE", { day:"2-digit", month:"2-digit", year:"numeric",
hour:"2-digit", minute:"2-digit", hour12:true });
};
const cargar = useCallback(() => { const cargar = useCallback(() => {
setLoading(true); setError(null); setLoading(true); setError(null);
@@ -55,51 +76,114 @@ export default function Asesores() {
{loading ? <Loader text="Cargando asesores de Chatwoot..." /> : {loading ? <Loader text="Cargando asesores de Chatwoot..." /> :
error ? <ErrorBox msg={error} /> : error ? <ErrorBox msg={error} /> :
<> <>
<div className="kpis" style={{gridTemplateColumns:"repeat(4,1fr)"}}>
<div className="kpi"><div className="ico">👥</div><div className="label">Total Asesores</div><div className="value">{total}</div></div>
<div className="kpi" style={{background:"#d1fae5"}}><div className="ico">🟢</div><div className="label">En Línea</div><div className="value" style={{color:"#065f46"}}>{enLinea}</div></div>
<div className="kpi" style={{background:"#fef9c3"}}><div className="ico">🟡</div><div className="label">Ocupados</div><div className="value" style={{color:"#854d0e"}}>{ocupados}</div></div>
<div className="kpi" style={{background:"#fee2e2"}}><div className="ico">🔴</div><div className="label">Fuera de Línea</div><div className="value" style={{color:"#991b1b"}}>{offline}</div></div>
</div>
<div style={{display:"flex",gap:10,marginBottom:16}}> <div style={{display:"flex",gap:10,marginBottom:16}}>
<button className="btn btn-primary" disabled={cambiando==="all"} onClick={()=>todos(true)}>🟢 Encender todos</button> <button className="btn btn-primary" disabled={cambiando==="all"} onClick={()=>todos(true)}>🟢 Encender todos</button>
<button className="btn btn-ghost" disabled={cambiando==="all"} onClick={()=>todos(false)}>🔴 Apagar todos</button> <button className="btn btn-ghost" disabled={cambiando==="all"} onClick={()=>todos(false)}>🔴 Apagar todos</button>
<button className="btn btn-ghost" title="Refrescar" style={{fontSize:18}} onClick={cargar}>🔄</button> <button className="btn btn-ghost" title="Refrescar y guardar historial" style={{fontSize:18}}
onClick={async ()=>{ try { await api.asesoresRegistrar(); } catch(e){} cargar(); }}>🔄</button>
</div> </div>
<div className="table-wrap"> <div style={{ display:"flex", flexDirection:"column", gap:6 }}>
<table>
<thead><tr><th>ASESOR</th><th>EMAIL</th><th>ROL</th><th>ESTADO</th><th>ACCIÓN</th></tr></thead>
<tbody>
{agentes.map((ag)=>{ {agentes.map((ag)=>{
const est = ESTADO_INFO[ag.availability_status] || ESTADO_INFO.offline; const est = ESTADO_INFO[ag.availability_status] || ESTADO_INFO.offline;
const isOnline = ag.availability_status==="online"; const isOnline = ag.availability_status==="online";
const nombre = ag.name || ag.available_name || "—";
const inicial = nombre.trim().charAt(0).toUpperCase();
const esAdmin = String(ag.role||"agent").toLowerCase().includes("admin");
return ( return (
<tr key={ag.id}> <div key={ag.id}
<td className="col-name">{ag.name || ag.available_name || "—"}</td> style={{ display:"flex", alignItems:"center", gap:12, background:"#fff",
<td>{ag.email || "—"}</td> border:"1px solid #e8edf3", borderRadius:10, padding:"8px 14px",
<td>{ag.role || "agent"}</td> boxShadow:"0 1px 2px rgba(16,24,40,.04)" }}>
<td> {/* Avatar con punto de estado */}
<span style={{background:est.bg,color:est.fg,padding:"3px 10px",borderRadius:20, <div style={{ position:"relative", flexShrink:0 }}>
fontSize:11,fontWeight:700}}>● {est.txt}</span> <div style={{ width:34, height:34, borderRadius:10,
</td> background:`linear-gradient(135deg, ${est.color}, ${est.fg})`, color:"#fff",
<td> display:"flex", alignItems:"center", justifyContent:"center", fontSize:14, fontWeight:700 }}>
<button className="btn" disabled={cambiando===ag.id} {inicial}
style={{padding:"5px 12px",fontSize:11, </div>
background:isOnline?"#fee2e2":"#d1fae5", <span style={{ position:"absolute", bottom:-2, right:-2, width:11, height:11, borderRadius:"50%",
color:isOnline?"#991b1b":"#065f46"}} background:est.color, border:"2px solid #fff" }} />
onClick={()=>toggle(ag)}> </div>
{cambiando===ag.id ? "..." : (isOnline?"Apagar":"Encender")} {/* Nombre + email */}
<div style={{ minWidth:0, flex:"1 1 280px" }}>
<div style={{ display:"flex", alignItems:"center", gap:8 }}>
<span style={{ fontSize:13, fontWeight:700, color:"#0f172a" }}>{nombre}</span>
{esAdmin && (
<span style={{ fontSize:9, fontWeight:700, color:"#4338ca", background:"#eef2ff",
border:"1px solid #c7d2fe", borderRadius:999, padding:"1px 7px" }}>ADMIN</span>
)}
</div>
<div style={{ fontSize:11.5, color:"#94a3b8", whiteSpace:"nowrap",
overflow:"hidden", textOverflow:"ellipsis" }}>{ag.email || "—"}</div>
</div>
{/* Historial */}
<button onClick={()=>verHistorial(ag)}
style={{ flexShrink:0, padding:"6px 12px", fontSize:11.5, fontWeight:600, borderRadius:8,
border:"1px solid #cbd5e1", background:"#fff", color:"#334155", cursor:"pointer" }}>
📋 Historial
</button> </button>
</td> {/* Switch ON / OFF */}
</tr> <div style={{ display:"flex", alignItems:"center", gap:8, flexShrink:0 }}>
<span style={{ fontSize:10.5, fontWeight:700, color:isOnline?"#10b981":"#94a3b8", width:24 }}>
{isOnline ? "ON" : "OFF"}
</span>
<div onClick={()=> cambiando===ag.id ? null : toggle(ag)}
style={{ width:40, height:22, borderRadius:999, cursor: cambiando===ag.id ? "wait" : "pointer",
background: isOnline ? "#10b981" : "#cbd5e1", position:"relative",
transition:"background .2s", opacity: cambiando===ag.id ? .6 : 1 }}>
<div style={{ width:16, height:16, borderRadius:"50%", background:"#fff",
position:"absolute", top:3, left: isOnline ? 21 : 3,
transition:"left .2s", boxShadow:"0 1px 2px rgba(0,0,0,.3)" }} />
</div>
</div>
</div>
); );
})} })}
</tbody>
</table>
</div> </div>
</>} </>}
{histAsesor && (
<div onClick={()=>setHistAsesor(null)}
style={{ position:"fixed", inset:0, background:"rgba(15,23,42,.55)", backdropFilter:"blur(2px)",
display:"flex", alignItems:"center", justifyContent:"center", zIndex:1000, padding:16 }}>
<div onClick={(e)=>e.stopPropagation()}
style={{ background:"#fff", borderRadius:18, width:520, maxWidth:"94vw", maxHeight:"85vh",
display:"flex", flexDirection:"column", overflow:"hidden",
boxShadow:"0 30px 80px rgba(15,23,42,.4)", border:"1px solid #e2e8f0" }}>
<div style={{ background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)", color:"#fff",
padding:"18px 22px", display:"flex", justifyContent:"space-between", alignItems:"center" }}>
<div>
<div style={{ fontWeight:700, fontSize:16 }}>📋 Historial de conexión</div>
<div style={{ fontSize:12.5, opacity:.85, marginTop:2 }}>{histAsesor.nombre}</div>
</div>
<button onClick={()=>setHistAsesor(null)} style={{ background:"rgba(255,255,255,.15)", border:"none", color:"#fff", width:30, height:30, borderRadius:8, fontSize:18, cursor:"pointer" }}>×</button>
</div>
<div style={{ padding:"18px 22px", overflowY:"auto", background:"#f8fafc" }}>
{histLoading ? <div style={{ textAlign:"center", color:"#94a3b8", padding:20 }}>Cargando...</div> :
histFilas.length === 0 ? <div style={{ textAlign:"center", color:"#94a3b8", padding:20 }}>Sin registros.</div> :
<div style={{ display:"flex", flexDirection:"column", gap:8 }}>
{histFilas.map((h, i) => {
const on = h.accion === "ON";
return (
<div key={i} style={{ display:"flex", alignItems:"center", gap:12, background:"#fff",
border:"1px solid #e8edf3", borderRadius:10, padding:"10px 14px" }}>
<span style={{ width:10, height:10, borderRadius:"50%", flexShrink:0,
background: on ? "#10b981" : "#94a3b8" }} />
<span style={{ fontWeight:700, fontSize:13, color: on ? "#047857" : "#64748b", width:110 }}>
{on ? "🟢 Se conectó" : "⚪ Se desconectó"}
</span>
<span style={{ flex:1, fontSize:12.5, color:"#334155" }}>{fmtHora(h.fecha_hora)}</span>
<span style={{ fontSize:10.5, fontWeight:700, color:"#64748b", background:"#f1f5f9",
borderRadius:999, padding:"2px 8px" }}>{h.origen}</span>
</div>
);
})}
</div>}
</div>
</div>
</div>
)}
</div> </div>
); );
} }