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 [rol, setRol] = useState("COBRANZA");
|
||||||
const [creando, setCreando] = useState(false);
|
const [creando, setCreando] = useState(false);
|
||||||
const [msg, setMsg] = useState(null);
|
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(() => {
|
const cargar = useCallback(() => {
|
||||||
setLoading(true); setError(null);
|
setLoading(true); setError(null);
|
||||||
@@ -36,6 +69,7 @@ export default function Usuarios() {
|
|||||||
setMsg({ ok:true, txt:`Usuario ${email} creado como ${rol}.` });
|
setMsg({ ok:true, txt:`Usuario ${email} creado como ${rol}.` });
|
||||||
setEmail(""); setPassword(""); setNombre(""); setRol("COBRANZA");
|
setEmail(""); setPassword(""); setNombre(""); setRol("COBRANZA");
|
||||||
cargar();
|
cargar();
|
||||||
|
setTimeout(() => { setModalCrear(false); setMsg(null); }, 900);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg({ ok:false, txt:"Error: " + e.message });
|
setMsg({ ok:false, txt:"Error: " + e.message });
|
||||||
}
|
}
|
||||||
@@ -53,67 +87,323 @@ export default function Usuarios() {
|
|||||||
catch (e) { alert("No se pudo eliminar: " + e.message); }
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="page-title">🔐 Usuarios</h1>
|
<h1 className="page-title">🔐 Usuarios</h1>
|
||||||
|
|
||||||
{/* Crear nuevo usuario */}
|
{/* Barra: título de lista + botón crear */}
|
||||||
<div style={{ background:"#fff", border:"1px solid #e2e8f0", borderRadius:12, padding:18, marginBottom:20 }}>
|
<div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:16 }}>
|
||||||
<h3 style={{ fontSize:15, fontWeight:700, color:"#0f172a", marginBottom:14 }}>➕ Crear nuevo usuario</h3>
|
<span style={{ fontSize:13.5, color:"#64748b", fontWeight:600 }}>
|
||||||
<div style={{ display:"flex", gap:12, flexWrap:"wrap", alignItems:"flex-end" }}>
|
{usuarios.length} usuario(s)
|
||||||
<Campo label="Correo">
|
</span>
|
||||||
<input value={email} onChange={(e)=>setEmail(e.target.value)} placeholder="usuario@escuela.com" style={inp} />
|
<button onClick={()=>{ setMsg(null); setEmail(""); setPassword(""); setNombre(""); setRol("COBRANZA"); setModalCrear(true); }}
|
||||||
</Campo>
|
style={{ display:"inline-flex", alignItems:"center", gap:8, padding:"10px 20px", borderRadius:10,
|
||||||
<Campo label="Contraseña">
|
border:"none", background:"linear-gradient(135deg, #1e3a5f 0%, #2d5580 100%)",
|
||||||
<input type="text" value={password} onChange={(e)=>setPassword(e.target.value)} placeholder="mín. 6 caracteres" style={inp} />
|
color:"#fff", fontWeight:700, fontSize:13.5, cursor:"pointer",
|
||||||
</Campo>
|
boxShadow:"0 4px 12px rgba(30,58,95,.3)" }}>
|
||||||
<Campo label="Nombre">
|
➕ Crear usuario
|
||||||
<input value={nombre} onChange={(e)=>setNombre(e.target.value)} placeholder="Nombre completo" style={inp} />
|
</button>
|
||||||
</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>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lista de usuarios */}
|
{/* Lista de usuarios */}
|
||||||
{loading ? <Loader text="Cargando usuarios..." /> :
|
{loading ? <Loader text="Cargando usuarios..." /> :
|
||||||
error ? <ErrorBox msg={error} /> :
|
error ? <ErrorBox msg={error} /> :
|
||||||
<div className="table-wrap">
|
usuarios.length === 0 ?
|
||||||
<table>
|
<div style={{ padding:40, textAlign:"center", color:"#94a3b8", background:"#fff",
|
||||||
<thead><tr><th>CORREO</th><th>NOMBRE</th><th>ROL</th><th>ACCIONES</th></tr></thead>
|
border:"1px solid #e2e8f0", borderRadius:12 }}>Sin usuarios.</div> :
|
||||||
<tbody>
|
<div style={{ display:"flex", flexDirection:"column", gap:10 }}>
|
||||||
{usuarios.map((u) => (
|
{usuarios.map((u) => {
|
||||||
<tr key={u.id}>
|
const rc = ROL_COLOR[u.rol] || ROL_COLOR.DEFAULT;
|
||||||
<td className="col-name">{u.email}</td>
|
const inicial = (u.nombre || u.email || "?").trim().charAt(0).toUpperCase();
|
||||||
<td>{u.nombre || "—"}</td>
|
return (
|
||||||
<td>
|
<div key={u.id}
|
||||||
<select value={u.rol} onChange={(e)=>cambiarRol(u.id, e.target.value)}
|
style={{ display:"flex", alignItems:"center", gap:16, background:"#fff",
|
||||||
style={{ padding:"5px 8px", border:"1px solid #cbd5e1", borderRadius:6, fontSize:12 }}>
|
border:"1px solid #e8edf3", borderRadius:14, padding:"14px 18px",
|
||||||
{ROLES.map((r)=><option key={r} value={r}>{r}</option>)}
|
boxShadow:"0 1px 3px rgba(16,24,40,.05)" }}>
|
||||||
</select>
|
{/* Avatar */}
|
||||||
</td>
|
<div style={{ width:44, height:44, borderRadius:12, flexShrink:0,
|
||||||
<td>
|
background:`linear-gradient(135deg, ${rc.g1}, ${rc.g2})`, color:"#fff",
|
||||||
<button className="btn" style={{ padding:"4px 10px", fontSize:11, background:"#fee2e2", color:"#991b1b" }}
|
display:"flex", alignItems:"center", justifyContent:"center",
|
||||||
onClick={()=>eliminar(u.id, u.email)}>🗑️ Eliminar</button>
|
fontSize:18, fontWeight:700 }}>{inicial}</div>
|
||||||
</td>
|
{/* Correo + nombre */}
|
||||||
</tr>
|
<div style={{ minWidth:0, flex:"1 1 260px" }}>
|
||||||
))}
|
<div style={{ fontSize:14, fontWeight:700, color:"#0f172a", whiteSpace:"nowrap",
|
||||||
{usuarios.length === 0 && (
|
overflow:"hidden", textOverflow:"ellipsis" }}>{u.email}</div>
|
||||||
<tr><td colSpan={4} style={{ padding:20, textAlign:"center", color:"#94a3b8" }}>Sin usuarios.</td></tr>
|
<div style={{ fontSize:12.5, color:"#94a3b8", marginTop:2 }}>{u.nombre || "—"}</div>
|
||||||
)}
|
</div>
|
||||||
</tbody>
|
{/* Selector de rol con badge de color */}
|
||||||
</table>
|
<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>}
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -128,3 +418,19 @@ function Campo({ label, children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inp = { padding:"8px 12px", border:"1px solid #cbd5e1", borderRadius:8, fontSize:13, minWidth:180 };
|
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