From 8a4a3d66410b7c50a2363a48dd5b84735e6c16e8 Mon Sep 17 00:00:00 2001 From: Administrator Date: Fri, 11 Sep 2026 15:32:19 -0500 Subject: [PATCH] Actualizar frontend/src/pages/Usuarios.jsx --- frontend/src/pages/Usuarios.jsx | 408 ++++++++++++++++++++++++++++---- 1 file changed, 357 insertions(+), 51 deletions(-) diff --git a/frontend/src/pages/Usuarios.jsx b/frontend/src/pages/Usuarios.jsx index e4da63d..1dc3243 100644 --- a/frontend/src/pages/Usuarios.jsx +++ b/frontend/src/pages/Usuarios.jsx @@ -17,6 +17,39 @@ export default function Usuarios() { const [rol, setRol] = useState("COBRANZA"); const [creando, setCreando] = useState(false); const [msg, setMsg] = useState(null); + const [modalCrear, setModalCrear] = useState(false); // pop-up crear usuario + + // Roles dinámicos (tabla permisos) + módulos disponibles para el checklist + const [rolesLista, setRolesLista] = useState([]); // ["ADMINISTRADOR","VENTAS",...] + const [modulosDisp, setModulosDisp] = useState([]); // ["ocupabilidad","ventas",...] + const [modalRol, setModalRol] = useState(false); // pop-up crear rol + const [rolNombre, setRolNombre] = useState(""); + const [rolModulos, setRolModulos] = useState([]); // marcados + const [guardandoRol, setGuardandoRol] = useState(false); + const [msgRol, setMsgRol] = useState(null); + + const cargarRoles = useCallback(() => { + api.rolesListar().then((r)=>setRolesLista((r.roles||[]).map((x)=>x.rol))).catch(()=>{}); + api.rolesModulos().then((r)=>setModulosDisp(r.modulos||[])).catch(()=>{}); + }, []); + useEffect(() => { cargarRoles(); }, [cargarRoles]); + + async function guardarRol() { + setMsgRol(null); + if (!rolNombre.trim()) { setMsgRol({ ok:false, txt:"Escribe el nombre del rol." }); return; } + if (rolModulos.length === 0) { setMsgRol({ ok:false, txt:"Marca al menos un apartado." }); return; } + setGuardandoRol(true); + try { + await api.rolGuardar(rolNombre.trim().toUpperCase(), rolModulos); + setMsgRol({ ok:true, txt:`Rol ${rolNombre.trim().toUpperCase()} guardado.` }); + setRol(rolNombre.trim().toUpperCase()); // seleccionarlo en el form de usuario + cargarRoles(); + setTimeout(()=>{ setModalRol(false); setRolNombre(""); setRolModulos([]); setMsgRol(null); }, 900); + } catch (e) { + setMsgRol({ ok:false, txt:"Error: " + e.message }); + } + setGuardandoRol(false); + } const cargar = useCallback(() => { setLoading(true); setError(null); @@ -36,6 +69,7 @@ export default function Usuarios() { setMsg({ ok:true, txt:`Usuario ${email} creado como ${rol}.` }); setEmail(""); setPassword(""); setNombre(""); setRol("COBRANZA"); cargar(); + setTimeout(() => { setModalCrear(false); setMsg(null); }, 900); } catch (e) { setMsg({ ok:false, txt:"Error: " + e.message }); } @@ -53,67 +87,323 @@ export default function Usuarios() { catch (e) { alert("No se pudo eliminar: " + e.message); } } + // Edición de correo/contraseña (modal) + const [editUser, setEditUser] = useState(null); // { id, email } + const [editEmail, setEditEmail] = useState(""); + const [editPass, setEditPass] = useState(""); + const [editando, setEditando] = useState(false); + const [editMsg, setEditMsg] = useState(null); + + function abrirEdit(u) { + setEditUser(u); setEditEmail(u.email || ""); setEditPass(""); setEditMsg(null); + } + + async function guardarEdit() { + setEditando(true); setEditMsg(null); + try { + const nuevoEmail = editEmail.trim(); + if (nuevoEmail && nuevoEmail !== editUser.email) { + await api.usuariosActualizarEmail(editUser.id, nuevoEmail); + } + if (editPass.trim()) { + await api.usuariosActualizarPassword(editUser.id, editPass.trim()); + } + setEditMsg({ ok:true, txt:"Cambios guardados." }); + cargar(); + setTimeout(() => setEditUser(null), 900); + } catch (e) { + setEditMsg({ ok:false, txt:"Error: " + e.message }); + } + setEditando(false); + } + return (

🔐 Usuarios

- {/* Crear nuevo usuario */} -
-

➕ Crear nuevo usuario

-
- - setEmail(e.target.value)} placeholder="usuario@escuela.com" style={inp} /> - - - setPassword(e.target.value)} placeholder="mín. 6 caracteres" style={inp} /> - - - setNombre(e.target.value)} placeholder="Nombre completo" style={inp} /> - - - cambiarRol(u.id, e.target.value)} - style={{ padding:"5px 8px", border:"1px solid #cbd5e1", borderRadius:6, fontSize:12 }}> - {ROLES.map((r)=>)} - - - - - - - ))} - {usuarios.length === 0 && ( - Sin usuarios. - )} - - + usuarios.length === 0 ? +
Sin usuarios.
: +
+ {usuarios.map((u) => { + const rc = ROL_COLOR[u.rol] || ROL_COLOR.DEFAULT; + const inicial = (u.nombre || u.email || "?").trim().charAt(0).toUpperCase(); + return ( +
+ {/* Avatar */} +
{inicial}
+ {/* Correo + nombre */} +
+
{u.email}
+
{u.nombre || "—"}
+
+ {/* Selector de rol con badge de color */} +
+ + ▼ +
+ {/* Acciones */} +
+ + +
+
+ ); + })}
} + + {modalCrear && ( +
setModalCrear(false)} + style={{ position:"fixed", inset:0, background:"rgba(15,23,42,.55)", backdropFilter:"blur(2px)", + display:"flex", alignItems:"center", justifyContent:"center", zIndex:1000, padding:16 }}> +
e.stopPropagation()} + style={{ background:"#fff", borderRadius:18, width:470, maxWidth:"94vw", overflow:"hidden", + boxShadow:"0 30px 80px rgba(15,23,42,.4)", border:"1px solid #e2e8f0" }}> +
+ +
➕
+
+
Crear nuevo usuario
+
Completa los datos del nuevo acceso
+
+
+
+
+ + setEmail(e.target.value)} placeholder="usuario@escuela.com" + onFocus={foco} onBlur={desfoco} style={inpModal} /> +
+
+ + setPassword(e.target.value)} placeholder="mín. 6 caracteres" + onFocus={foco} onBlur={desfoco} style={inpModal} /> +
+
+ + setNombre(e.target.value)} placeholder="Nombre completo" + onFocus={foco} onBlur={desfoco} style={inpModal} /> +
+
+ +
+ + +
+
+ {msg && ( +
+ {msg.ok ? "✅" : "⚠️"} {msg.txt} +
+ )} +
+ + +
+
+
+
+ )} + + {modalRol && ( +
setModalRol(false)} + style={{ position:"fixed", inset:0, background:"rgba(15,23,42,.55)", backdropFilter:"blur(2px)", + display:"flex", alignItems:"center", justifyContent:"center", zIndex:1100, padding:16 }}> +
e.stopPropagation()} + style={{ background:"#fff", borderRadius:18, width:480, maxWidth:"94vw", overflow:"hidden", + boxShadow:"0 30px 80px rgba(15,23,42,.4)", border:"1px solid #e2e8f0" }}> +
+ +
🛡️
+
+
Crear rol
+
Nombre + apartados que podrá ver
+
+
+
+
+ + setRolNombre(e.target.value)} placeholder="Ej: SUPERVISOR" + onFocus={foco} onBlur={desfoco} style={inpModal} /> +
+ +
+ {modulosDisp.map((m) => { + const on = rolModulos.includes(m); + return ( + + ); + })} +
+ {msgRol && ( +
+ {msgRol.ok ? "✅" : "⚠️"} {msgRol.txt} +
+ )} +
+ + +
+
+
+
+ )} + + {editUser && ( +
setEditUser(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 }}> +
e.stopPropagation()} + style={{ background:"#fff", borderRadius:18, width:470, maxWidth:"94vw", overflow:"hidden", + boxShadow:"0 30px 80px rgba(15,23,42,.4)", border:"1px solid #e2e8f0" }}> + {/* Cabecera con avatar */} +
+ +
+
+ {(editUser.email || "?").trim().charAt(0).toUpperCase()} +
+
+
Editar usuario
+
{editUser.email}
+
+
+
+
+
+ + setEditEmail(e.target.value)} placeholder="correo@escuela.com" + onFocus={(e)=>{e.target.style.borderColor="#2d5580";e.target.style.boxShadow="0 0 0 3px rgba(45,85,128,.15)";}} + onBlur={(e)=>{e.target.style.borderColor="#cbd5e1";e.target.style.boxShadow="none";}} + style={{ width:"100%", boxSizing:"border-box", padding:"11px 13px", border:"1px solid #cbd5e1", + borderRadius:10, fontSize:14, outline:"none", background:"#fff", transition:"border-color .15s, box-shadow .15s" }} /> +
+
+ + setEditPass(e.target.value)} placeholder="Dejar vacío para no cambiar" + onFocus={(e)=>{e.target.style.borderColor="#2d5580";e.target.style.boxShadow="0 0 0 3px rgba(45,85,128,.15)";}} + onBlur={(e)=>{e.target.style.borderColor="#cbd5e1";e.target.style.boxShadow="none";}} + style={{ width:"100%", boxSizing:"border-box", padding:"11px 13px", border:"1px solid #cbd5e1", + borderRadius:10, fontSize:14, outline:"none", background:"#fff", transition:"border-color .15s, box-shadow .15s" }} /> +
+ {editMsg && ( +
+ {editMsg.ok ? "✅" : "⚠️"} {editMsg.txt} +
+ )} +
+ + +
+
+
+
+ )}
); } @@ -128,3 +418,19 @@ function Campo({ label, children }) { } const inp = { padding:"8px 12px", border:"1px solid #cbd5e1", borderRadius:8, fontSize:13, minWidth:180 }; + +// Estilos de los inputs/labels dentro de los pop-ups +const lblModal = { display:"flex", alignItems:"center", gap:6, fontSize:11.5, fontWeight:700, + color:"#475569", marginBottom:7, textTransform:"uppercase", letterSpacing:.3 }; +const inpModal = { width:"100%", boxSizing:"border-box", padding:"11px 13px", border:"1px solid #cbd5e1", + borderRadius:10, fontSize:14, outline:"none", background:"#fff", transition:"border-color .15s, box-shadow .15s" }; +const foco = (e)=>{ e.target.style.borderColor="#2d5580"; e.target.style.boxShadow="0 0 0 3px rgba(45,85,128,.15)"; }; +const desfoco = (e)=>{ e.target.style.borderColor="#cbd5e1"; e.target.style.boxShadow="none"; }; + +// Colores por rol: avatar (g1/g2 gradiente), badge (bg/fg/bd) +const ROL_COLOR = { + ADMINISTRADOR: { g1:"#6366f1", g2:"#4338ca", bg:"#eef2ff", fg:"#4338ca", bd:"#c7d2fe" }, + VENTAS: { g1:"#10b981", g2:"#047857", bg:"#ecfdf5", fg:"#047857", bd:"#a7f3d0" }, + COBRANZA: { g1:"#f59e0b", g2:"#b45309", bg:"#fffbeb", fg:"#b45309", bd:"#fde68a" }, + DEFAULT: { g1:"#64748b", g2:"#334155", bg:"#f1f5f9", fg:"#334155", bd:"#e2e8f0" }, +};