Actualizar frontend/src/pages/Usuarios.jsx
This commit is contained in:
@@ -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 (
|
||||
<div>
|
||||
<h1 className="page-title">🔐 Usuarios</h1>
|
||||
|
||||
{/* Crear nuevo usuario */}
|
||||
<div style={{ background:"#fff", border:"1px solid #e2e8f0", borderRadius:12, padding:18, marginBottom:20 }}>
|
||||
<h3 style={{ fontSize:15, fontWeight:700, color:"#0f172a", marginBottom:14 }}>➕ Crear nuevo usuario</h3>
|
||||
<div style={{ display:"flex", gap:12, flexWrap:"wrap", alignItems:"flex-end" }}>
|
||||
<Campo label="Correo">
|
||||
<input value={email} onChange={(e)=>setEmail(e.target.value)} placeholder="usuario@escuela.com" style={inp} />
|
||||
</Campo>
|
||||
<Campo label="Contraseña">
|
||||
<input type="text" value={password} onChange={(e)=>setPassword(e.target.value)} placeholder="mín. 6 caracteres" style={inp} />
|
||||
</Campo>
|
||||
<Campo label="Nombre">
|
||||
<input value={nombre} onChange={(e)=>setNombre(e.target.value)} placeholder="Nombre completo" style={inp} />
|
||||
</Campo>
|
||||
<Campo label="Rol">
|
||||
<Select value={rol} options={ROLES} onChange={setRol} />
|
||||
</Campo>
|
||||
<button className="btn btn-primary" style={{height:38}} disabled={creando} onClick={crear}>
|
||||
{creando ? "Creando..." : "Crear usuario"}
|
||||
</button>
|
||||
</div>
|
||||
{msg && (
|
||||
<div style={{ marginTop:12, padding:10, borderRadius:8, fontSize:13,
|
||||
background: msg.ok ? "#d1fae5" : "#fee2e2", color: msg.ok ? "#065f46" : "#991b1b" }}>
|
||||
{msg.ok ? "✅" : "⚠️"} {msg.txt}
|
||||
</div>
|
||||
)}
|
||||
{/* Barra: título de lista + botón crear */}
|
||||
<div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:16 }}>
|
||||
<span style={{ fontSize:13.5, color:"#64748b", fontWeight:600 }}>
|
||||
{usuarios.length} usuario(s)
|
||||
</span>
|
||||
<button onClick={()=>{ setMsg(null); setEmail(""); setPassword(""); setNombre(""); setRol("COBRANZA"); setModalCrear(true); }}
|
||||
style={{ display:"inline-flex", alignItems:"center", gap:8, padding:"10px 20px", borderRadius:10,
|
||||
border:"none", background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)",
|
||||
color:"#fff", fontWeight:700, fontSize:13.5, cursor:"pointer",
|
||||
boxShadow:"0 4px 12px rgba(30,58,95,.3)" }}>
|
||||
➕ Crear usuario
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Lista de usuarios */}
|
||||
{loading ? <Loader text="Cargando usuarios..." /> :
|
||||
error ? <ErrorBox msg={error} /> :
|
||||
<div className="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>CORREO</th><th>NOMBRE</th><th>ROL</th><th>ACCIONES</th></tr></thead>
|
||||
<tbody>
|
||||
{usuarios.map((u) => (
|
||||
<tr key={u.id}>
|
||||
<td className="col-name">{u.email}</td>
|
||||
<td>{u.nombre || "—"}</td>
|
||||
<td>
|
||||
<select value={u.rol} onChange={(e)=>cambiarRol(u.id, e.target.value)}
|
||||
style={{ padding:"5px 8px", border:"1px solid #cbd5e1", borderRadius:6, fontSize:12 }}>
|
||||
{ROLES.map((r)=><option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<button className="btn" style={{ padding:"4px 10px", fontSize:11, background:"#fee2e2", color:"#991b1b" }}
|
||||
onClick={()=>eliminar(u.id, u.email)}>🗑️ Eliminar</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{usuarios.length === 0 && (
|
||||
<tr><td colSpan={4} style={{ padding:20, textAlign:"center", color:"#94a3b8" }}>Sin usuarios.</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
usuarios.length === 0 ?
|
||||
<div style={{ padding:40, textAlign:"center", color:"#94a3b8", background:"#fff",
|
||||
border:"1px solid #e2e8f0", borderRadius:12 }}>Sin usuarios.</div> :
|
||||
<div style={{ display:"flex", flexDirection:"column", gap:10 }}>
|
||||
{usuarios.map((u) => {
|
||||
const rc = ROL_COLOR[u.rol] || ROL_COLOR.DEFAULT;
|
||||
const inicial = (u.nombre || u.email || "?").trim().charAt(0).toUpperCase();
|
||||
return (
|
||||
<div key={u.id}
|
||||
style={{ display:"flex", alignItems:"center", gap:16, background:"#fff",
|
||||
border:"1px solid #e8edf3", borderRadius:14, padding:"14px 18px",
|
||||
boxShadow:"0 1px 3px rgba(16,24,40,.05)" }}>
|
||||
{/* Avatar */}
|
||||
<div style={{ width:44, height:44, borderRadius:12, flexShrink:0,
|
||||
background:`linear-gradient(135deg, ${rc.g1}, ${rc.g2})`, color:"#fff",
|
||||
display:"flex", alignItems:"center", justifyContent:"center",
|
||||
fontSize:18, fontWeight:700 }}>{inicial}</div>
|
||||
{/* Correo + nombre */}
|
||||
<div style={{ minWidth:0, flex:"1 1 260px" }}>
|
||||
<div style={{ fontSize:14, fontWeight:700, color:"#0f172a", whiteSpace:"nowrap",
|
||||
overflow:"hidden", textOverflow:"ellipsis" }}>{u.email}</div>
|
||||
<div style={{ fontSize:12.5, color:"#94a3b8", marginTop:2 }}>{u.nombre || "—"}</div>
|
||||
</div>
|
||||
{/* Selector de rol con badge de color */}
|
||||
<div style={{ position:"relative", flexShrink:0 }}>
|
||||
<select value={u.rol} onChange={(e)=>cambiarRol(u.id, e.target.value)}
|
||||
style={{ appearance:"none", WebkitAppearance:"none", padding:"7px 30px 7px 14px",
|
||||
border:`1px solid ${rc.bd}`, borderRadius:999, fontSize:12, fontWeight:700,
|
||||
color:rc.fg, background:rc.bg, cursor:"pointer" }}>
|
||||
{(rolesLista.length ? rolesLista : ROLES).map((r)=><option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
<span style={{ position:"absolute", right:12, top:"50%", transform:"translateY(-50%)",
|
||||
pointerEvents:"none", color:rc.fg, fontSize:10 }}>▼</span>
|
||||
</div>
|
||||
{/* Acciones */}
|
||||
<div style={{ display:"flex", gap:8, flexShrink:0 }}>
|
||||
<button onClick={()=>abrirEdit(u)}
|
||||
style={{ padding:"7px 14px", fontSize:12.5, fontWeight:600, borderRadius:9,
|
||||
border:"1px solid #cbd5e1", background:"#fff", color:"#334155", cursor:"pointer" }}>
|
||||
✏️ Editar
|
||||
</button>
|
||||
<button onClick={()=>eliminar(u.id, u.email)}
|
||||
style={{ padding:"7px 14px", fontSize:12.5, fontWeight:600, borderRadius:9,
|
||||
border:"1px solid #fecaca", background:"#fef2f2", color:"#dc2626", cursor:"pointer" }}>
|
||||
🗑️ Eliminar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>}
|
||||
|
||||
{modalCrear && (
|
||||
<div onClick={()=>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 }}>
|
||||
<div onClick={(e)=>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" }}>
|
||||
<div style={{ background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)", color:"#fff",
|
||||
padding:"22px 24px", position:"relative", display:"flex", alignItems:"center", gap:14 }}>
|
||||
<button onClick={()=>setModalCrear(false)}
|
||||
style={{ position:"absolute", top:16, right:16, background:"rgba(255,255,255,.15)",
|
||||
border:"none", color:"#fff", width:30, height:30, borderRadius:8, fontSize:18,
|
||||
cursor:"pointer", lineHeight:1, display:"flex", alignItems:"center", justifyContent:"center" }}>×</button>
|
||||
<div style={{ width:50, height:50, borderRadius:14, background:"rgba(255,255,255,.18)",
|
||||
display:"flex", alignItems:"center", justifyContent:"center", fontSize:22 }}>➕</div>
|
||||
<div>
|
||||
<div style={{ fontWeight:700, fontSize:16 }}>Crear nuevo usuario</div>
|
||||
<div style={{ fontSize:12.5, opacity:.85, marginTop:2 }}>Completa los datos del nuevo acceso</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding:"24px", background:"#f8fafc" }}>
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<label style={lblModal}>📧 Correo</label>
|
||||
<input value={email} onChange={(e)=>setEmail(e.target.value)} placeholder="usuario@escuela.com"
|
||||
onFocus={foco} onBlur={desfoco} style={inpModal} />
|
||||
</div>
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<label style={lblModal}>🔑 Contraseña</label>
|
||||
<input type="text" value={password} onChange={(e)=>setPassword(e.target.value)} placeholder="mín. 6 caracteres"
|
||||
onFocus={foco} onBlur={desfoco} style={inpModal} />
|
||||
</div>
|
||||
<div style={{ marginBottom:14 }}>
|
||||
<label style={lblModal}>👤 Nombre</label>
|
||||
<input value={nombre} onChange={(e)=>setNombre(e.target.value)} placeholder="Nombre completo"
|
||||
onFocus={foco} onBlur={desfoco} style={inpModal} />
|
||||
</div>
|
||||
<div>
|
||||
<label style={lblModal}>🛡️ Rol</label>
|
||||
<div style={{ display:"flex", gap:8 }}>
|
||||
<select value={rol} onChange={(e)=>setRol(e.target.value)}
|
||||
onFocus={foco} onBlur={desfoco} style={{ ...inpModal, flex:1 }}>
|
||||
{(rolesLista.length ? rolesLista : ROLES).map((r)=><option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
<button type="button" onClick={()=>{ setRolNombre(""); setRolModulos([]); setMsgRol(null); setModalRol(true); }}
|
||||
style={{ flexShrink:0, padding:"0 16px", borderRadius:10, border:"1px solid #cbd5e1",
|
||||
background:"#eef2f9", color:"#1e3a5f", fontWeight:700, fontSize:12.5, cursor:"pointer", whiteSpace:"nowrap" }}>
|
||||
➕ Crear rol
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{msg && (
|
||||
<div style={{ marginTop:14, padding:"10px 12px", borderRadius:10, fontSize:13, fontWeight:600,
|
||||
background: msg.ok ? "#d1fae5" : "#fee2e2", color: msg.ok ? "#065f46" : "#991b1b" }}>
|
||||
{msg.ok ? "✅" : "⚠️"} {msg.txt}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display:"flex", justifyContent:"flex-end", gap:10, marginTop:22 }}>
|
||||
<button onClick={()=>setModalCrear(false)} disabled={creando}
|
||||
style={{ padding:"10px 20px", borderRadius:10, border:"1px solid #cbd5e1", background:"#fff", color:"#475569", fontWeight:600, fontSize:13.5, cursor:"pointer" }}>
|
||||
Cancelar
|
||||
</button>
|
||||
<button onClick={crear} disabled={creando}
|
||||
style={{ padding:"10px 22px", borderRadius:10, border:"none",
|
||||
background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)",
|
||||
color:"#fff", fontWeight:700, fontSize:13.5, cursor:"pointer", opacity: creando ? .7 : 1,
|
||||
boxShadow:"0 4px 12px rgba(30,58,95,.3)" }}>
|
||||
{creando ? "Creando..." : "Crear usuario"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{modalRol && (
|
||||
<div onClick={()=>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 }}>
|
||||
<div onClick={(e)=>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" }}>
|
||||
<div style={{ background:"linear-gradient(135deg, #4338ca 0%, #6366f1 100%)", color:"#fff",
|
||||
padding:"22px 24px", position:"relative", display:"flex", alignItems:"center", gap:14 }}>
|
||||
<button onClick={()=>setModalRol(false)}
|
||||
style={{ position:"absolute", top:16, right:16, background:"rgba(255,255,255,.15)",
|
||||
border:"none", color:"#fff", width:30, height:30, borderRadius:8, fontSize:18,
|
||||
cursor:"pointer", lineHeight:1, display:"flex", alignItems:"center", justifyContent:"center" }}>×</button>
|
||||
<div style={{ width:50, height:50, borderRadius:14, background:"rgba(255,255,255,.18)",
|
||||
display:"flex", alignItems:"center", justifyContent:"center", fontSize:22 }}>🛡️</div>
|
||||
<div>
|
||||
<div style={{ fontWeight:700, fontSize:16 }}>Crear rol</div>
|
||||
<div style={{ fontSize:12.5, opacity:.85, marginTop:2 }}>Nombre + apartados que podrá ver</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding:"24px", background:"#f8fafc" }}>
|
||||
<div style={{ marginBottom:18 }}>
|
||||
<label style={lblModal}>🏷️ Nombre del rol</label>
|
||||
<input value={rolNombre} onChange={(e)=>setRolNombre(e.target.value)} placeholder="Ej: SUPERVISOR"
|
||||
onFocus={foco} onBlur={desfoco} style={inpModal} />
|
||||
</div>
|
||||
<label style={lblModal}>📂 Apartados que podrá ver</label>
|
||||
<div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:8, marginTop:4 }}>
|
||||
{modulosDisp.map((m) => {
|
||||
const on = rolModulos.includes(m);
|
||||
return (
|
||||
<label key={m} onClick={()=>setRolModulos((p)=> on ? p.filter((x)=>x!==m) : [...p, m])}
|
||||
style={{ display:"flex", alignItems:"center", gap:8, padding:"9px 12px", borderRadius:10,
|
||||
cursor:"pointer", fontSize:13, fontWeight:600, textTransform:"capitalize",
|
||||
border:`1px solid ${on?"#6366f1":"#e2e8f0"}`, background: on?"#eef2ff":"#fff",
|
||||
color: on?"#4338ca":"#475569" }}>
|
||||
<span style={{ width:18, height:18, borderRadius:5, flexShrink:0, display:"flex",
|
||||
alignItems:"center", justifyContent:"center",
|
||||
border: on?"none":"1.5px solid #cbd5e1", background: on?"#6366f1":"#fff" }}>
|
||||
{on && <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>}
|
||||
</span>
|
||||
{m}
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{msgRol && (
|
||||
<div style={{ marginTop:14, padding:"10px 12px", borderRadius:10, fontSize:13, fontWeight:600,
|
||||
background: msgRol.ok ? "#d1fae5" : "#fee2e2", color: msgRol.ok ? "#065f46" : "#991b1b" }}>
|
||||
{msgRol.ok ? "✅" : "⚠️"} {msgRol.txt}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display:"flex", justifyContent:"flex-end", gap:10, marginTop:22 }}>
|
||||
<button onClick={()=>setModalRol(false)} disabled={guardandoRol}
|
||||
style={{ padding:"10px 20px", borderRadius:10, border:"1px solid #cbd5e1", background:"#fff", color:"#475569", fontWeight:600, fontSize:13.5, cursor:"pointer" }}>
|
||||
Cancelar
|
||||
</button>
|
||||
<button onClick={guardarRol} disabled={guardandoRol}
|
||||
style={{ padding:"10px 22px", borderRadius:10, border:"none",
|
||||
background:"linear-gradient(135deg, #4338ca 0%, #6366f1 100%)",
|
||||
color:"#fff", fontWeight:700, fontSize:13.5, cursor:"pointer", opacity: guardandoRol ? .7 : 1,
|
||||
boxShadow:"0 4px 12px rgba(67,56,202,.3)" }}>
|
||||
{guardandoRol ? "Guardando..." : "Guardar rol"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{editUser && (
|
||||
<div onClick={()=>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 }}>
|
||||
<div onClick={(e)=>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 */}
|
||||
<div style={{ background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)", color:"#fff",
|
||||
padding:"22px 24px", position:"relative" }}>
|
||||
<button onClick={()=>setEditUser(null)}
|
||||
style={{ position:"absolute", top:16, right:16, background:"rgba(255,255,255,.15)",
|
||||
border:"none", color:"#fff", width:30, height:30, borderRadius:8, fontSize:18,
|
||||
cursor:"pointer", lineHeight:1, display:"flex", alignItems:"center", justifyContent:"center" }}>×</button>
|
||||
<div style={{ display:"flex", alignItems:"center", gap:14 }}>
|
||||
<div style={{ width:50, height:50, borderRadius:14, background:"rgba(255,255,255,.18)",
|
||||
display:"flex", alignItems:"center", justifyContent:"center", fontSize:22, fontWeight:700 }}>
|
||||
{(editUser.email || "?").trim().charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div style={{ minWidth:0 }}>
|
||||
<div style={{ fontWeight:700, fontSize:16 }}>Editar usuario</div>
|
||||
<div style={{ fontSize:12.5, opacity:.85, marginTop:2, whiteSpace:"nowrap",
|
||||
overflow:"hidden", textOverflow:"ellipsis", maxWidth:320 }}>{editUser.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding:"24px", background:"#f8fafc" }}>
|
||||
<div style={{ marginBottom:16 }}>
|
||||
<label style={{ display:"flex", alignItems:"center", gap:6, fontSize:11.5, fontWeight:700, color:"#475569", marginBottom:7, textTransform:"uppercase", letterSpacing:.3 }}>
|
||||
📧 Nuevo correo
|
||||
</label>
|
||||
<input value={editEmail} onChange={(e)=>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" }} />
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display:"flex", alignItems:"center", gap:6, fontSize:11.5, fontWeight:700, color:"#475569", marginBottom:7, textTransform:"uppercase", letterSpacing:.3 }}>
|
||||
🔑 Nueva contraseña
|
||||
</label>
|
||||
<input type="text" value={editPass} onChange={(e)=>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" }} />
|
||||
</div>
|
||||
{editMsg && (
|
||||
<div style={{ marginTop:14, padding:"10px 12px", borderRadius:10, fontSize:13, fontWeight:600,
|
||||
background: editMsg.ok ? "#d1fae5" : "#fee2e2", color: editMsg.ok ? "#065f46" : "#991b1b" }}>
|
||||
{editMsg.ok ? "✅" : "⚠️"} {editMsg.txt}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display:"flex", justifyContent:"flex-end", gap:10, marginTop:22 }}>
|
||||
<button onClick={()=>setEditUser(null)} disabled={editando}
|
||||
style={{ padding:"10px 20px", borderRadius:10, border:"1px solid #cbd5e1", background:"#fff", color:"#475569", fontWeight:600, fontSize:13.5, cursor:"pointer" }}>
|
||||
Cancelar
|
||||
</button>
|
||||
<button onClick={guardarEdit} disabled={editando}
|
||||
style={{ padding:"10px 22px", borderRadius:10, border:"none",
|
||||
background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)",
|
||||
color:"#fff", fontWeight:700, fontSize:13.5, cursor:"pointer", opacity: editando ? .7 : 1,
|
||||
boxShadow:"0 4px 12px rgba(30,58,95,.3)" }}>
|
||||
{editando ? "Guardando..." : "Guardar cambios"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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" },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user