Actualizar frontend/src/pages/Asesores.jsx
This commit is contained in:
@@ -14,6 +14,27 @@ export default function Asesores() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = 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(() => {
|
||||
setLoading(true); setError(null);
|
||||
@@ -55,51 +76,114 @@ export default function Asesores() {
|
||||
{loading ? <Loader text="Cargando asesores de Chatwoot..." /> :
|
||||
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}}>
|
||||
<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" 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 className="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>ASESOR</th><th>EMAIL</th><th>ROL</th><th>ESTADO</th><th>ACCIÓN</th></tr></thead>
|
||||
<tbody>
|
||||
<div style={{ display:"flex", flexDirection:"column", gap:6 }}>
|
||||
{agentes.map((ag)=>{
|
||||
const est = ESTADO_INFO[ag.availability_status] || ESTADO_INFO.offline;
|
||||
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 (
|
||||
<tr key={ag.id}>
|
||||
<td className="col-name">{ag.name || ag.available_name || "—"}</td>
|
||||
<td>{ag.email || "—"}</td>
|
||||
<td>{ag.role || "agent"}</td>
|
||||
<td>
|
||||
<span style={{background:est.bg,color:est.fg,padding:"3px 10px",borderRadius:20,
|
||||
fontSize:11,fontWeight:700}}>● {est.txt}</span>
|
||||
</td>
|
||||
<td>
|
||||
<button className="btn" disabled={cambiando===ag.id}
|
||||
style={{padding:"5px 12px",fontSize:11,
|
||||
background:isOnline?"#fee2e2":"#d1fae5",
|
||||
color:isOnline?"#991b1b":"#065f46"}}
|
||||
onClick={()=>toggle(ag)}>
|
||||
{cambiando===ag.id ? "..." : (isOnline?"Apagar":"Encender")}
|
||||
<div key={ag.id}
|
||||
style={{ display:"flex", alignItems:"center", gap:12, background:"#fff",
|
||||
border:"1px solid #e8edf3", borderRadius:10, padding:"8px 14px",
|
||||
boxShadow:"0 1px 2px rgba(16,24,40,.04)" }}>
|
||||
{/* Avatar con punto de estado */}
|
||||
<div style={{ position:"relative", flexShrink:0 }}>
|
||||
<div style={{ width:34, height:34, borderRadius:10,
|
||||
background:`linear-gradient(135deg, ${est.color}, ${est.fg})`, color:"#fff",
|
||||
display:"flex", alignItems:"center", justifyContent:"center", fontSize:14, fontWeight:700 }}>
|
||||
{inicial}
|
||||
</div>
|
||||
<span style={{ position:"absolute", bottom:-2, right:-2, width:11, height:11, borderRadius:"50%",
|
||||
background:est.color, border:"2px solid #fff" }} />
|
||||
</div>
|
||||
{/* 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>
|
||||
</td>
|
||||
</tr>
|
||||
{/* Switch ON / OFF */}
|
||||
<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>
|
||||
</>}
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user