F3.1 WEB: ampliar alta de usuarios e Inspector

This commit is contained in:
2026-09-07 14:15:23 -03:00
parent 5d066d1819
commit ebc4c303e4
+41 -7
View File
@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { Link, useNavigate } from 'react-router'; import { Link, useNavigate } from 'react-router';
import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback';
import { Icon } from '../components/Icon'; import { Icon } from '../components/Icon';
import { createUser, listRoles } from '../lib/api'; import { listRoles } from '../lib/api';
import type { AdministrativeRole } from '../lib/api'; import type { AdministrativeRole } from '../lib/api';
import { createUserProfile } from '../lib/userProfileApi';
export function NewUserPage() { export function NewUserPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -18,17 +19,30 @@ export function NewUserPage() {
listRoles().then(setRoles).catch((requestError) => setError(errorMessage(requestError))).finally(() => setLoading(false)); listRoles().then(setRoles).catch((requestError) => setError(errorMessage(requestError))).finally(() => setLoading(false));
}, []); }, []);
const inspectorSelected = useMemo(
() => roles.some((role) => role.code === 'inspector' && roleIds.includes(role.id)),
[roles, roleIds],
);
const toggleRole = (id: string) => setRoleIds((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]); const toggleRole = (id: string) => setRoleIds((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]);
const submit = async (event: FormEvent<HTMLFormElement>) => { const submit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
setError(''); setError('');
setSubmitting(true);
const data = new FormData(event.currentTarget); const data = new FormData(event.currentTarget);
const email = String(data.get('email') ?? '').trim();
if (inspectorSelected && !email) {
setError('El email es obligatorio para usuarios con rol Inspector porque allí recibirán la documentación de sus inspecciones.');
return;
}
setSubmitting(true);
try { try {
const created = await createUser({ const created = await createUserProfile({
username: String(data.get('username')), username: String(data.get('username')),
email: String(data.get('email') ?? '') || undefined, email: email || null,
dni: String(data.get('dni') ?? '').trim() || null,
phone: String(data.get('phone') ?? '').trim() || null,
jobTitle: String(data.get('jobTitle') ?? '').trim() || null,
employeeNumber: String(data.get('employeeNumber') ?? '').trim() || null,
firstName: String(data.get('firstName')), firstName: String(data.get('firstName')),
lastName: String(data.get('lastName')), lastName: String(data.get('lastName')),
password: String(data.get('password')), password: String(data.get('password')),
@@ -45,10 +59,30 @@ export function NewUserPage() {
return <section className="narrow-section"> return <section className="narrow-section">
<div className="breadcrumb"><Link to="/admin/users">Usuarios</Link><span>/</span><strong>Nuevo usuario</strong></div> <div className="breadcrumb"><Link to="/admin/users">Usuarios</Link><span>/</span><strong>Nuevo usuario</strong></div>
<div className="page-heading"><div><span className="eyebrow">NUEVO ACCESO</span><h1>Crear usuario</h1><p>La contraseña inicial puede obligarse a cambiar en el primer ingreso.</p></div></div> <div className="page-heading"><div><span className="eyebrow">NUEVO ACCESO</span><h1>Crear usuario</h1><p>Además del acceso, registrá los datos personales que identifican al agente y permiten las comunicaciones oficiales.</p></div></div>
{error && <Alert>{error}</Alert>} {error && <Alert>{error}</Alert>}
{loading ? <LoadingBlock /> : <form className="panel form-panel" onSubmit={submit}> {loading ? <LoadingBlock /> : <form className="panel form-panel" onSubmit={submit}>
<div className="form-section"><h2>Datos personales</h2><div className="form-grid"><label className="field"><span>Nombre</span><input name="firstName" required maxLength={120} /></label><label className="field"><span>Apellido</span><input name="lastName" required maxLength={120} /></label><label className="field"><span>Usuario</span><input name="username" required minLength={3} maxLength={80} pattern="[a-zA-Z][a-zA-Z0-9._-]+" /></label><label className="field"><span>Email <em>opcional</em></span><input name="email" type="email" maxLength={320} /></label></div></div> <div className="form-section">
<div><h2>Datos personales</h2><p className="section-copy">Nombre y apellido identifican al agente. DNI, contacto y función quedan asociados a su historial operativo.</p></div>
<div className="form-grid">
<label className="field"><span>Nombre <em>obligatorio</em></span><input name="firstName" required maxLength={120} /></label>
<label className="field"><span>Apellido <em>obligatorio</em></span><input name="lastName" required maxLength={120} /></label>
<label className="field"><span>DNI</span><input name="dni" inputMode="numeric" maxLength={15} placeholder="Sin puntos" /></label>
<label className="field"><span>Teléfono</span><input name="phone" type="tel" maxLength={40} placeholder="Ej.: +54 261 ..." /></label>
<label className="field"><span>Cargo / función</span><input name="jobTitle" maxLength={160} placeholder="Ej.: Inspector de Hidrocarburos" /></label>
<label className="field"><span>Legajo / matrícula</span><input name="employeeNumber" maxLength={80} /></label>
</div>
</div>
<div className="form-section">
<div><h2>Acceso y contacto</h2><p className="section-copy">El email del Inspector se utiliza también como destinatario de la documentación al cerrar la inspección.</p></div>
<div className="form-grid">
<label className="field"><span>Usuario <em>obligatorio</em></span><input name="username" required minLength={3} maxLength={80} pattern="[a-zA-Z][a-zA-Z0-9._-]+" /></label>
<label className="field"><span>Email {inspectorSelected ? <em>obligatorio para Inspector</em> : <em>recomendado</em>}</span><input name="email" type="email" maxLength={320} required={inspectorSelected} /></label>
</div>
{inspectorSelected && <div className="temporal-notice"><Icon name="mail" /><p><strong>Inspector:</strong> este email recibirá copia de las Actas/Informe correspondientes al cierre de la inspección.</p></div>}
</div>
<div className="form-section"><h2>Seguridad</h2><label className="field"><span>Contraseña temporal</span><input name="password" type="password" minLength={12} maxLength={128} required autoComplete="new-password" /><small>Mínimo 12 caracteres.</small></label><label className="check-row"><input name="mustChangePassword" type="checkbox" defaultChecked /><span><strong>Exigir cambio de contraseña</strong><small>El usuario no podrá acceder a otros módulos hasta actualizarla.</small></span></label></div> <div className="form-section"><h2>Seguridad</h2><label className="field"><span>Contraseña temporal</span><input name="password" type="password" minLength={12} maxLength={128} required autoComplete="new-password" /><small>Mínimo 12 caracteres.</small></label><label className="check-row"><input name="mustChangePassword" type="checkbox" defaultChecked /><span><strong>Exigir cambio de contraseña</strong><small>El usuario no podrá acceder a otros módulos hasta actualizarla.</small></span></label></div>
<div className="form-section"><h2>Roles</h2><div className="choice-grid">{roles.map((role) => <label className={`choice-card ${roleIds.includes(role.id) ? 'selected' : ''}`} key={role.id}><input type="checkbox" checked={roleIds.includes(role.id)} onChange={() => toggleRole(role.id)} /><span><strong>{role.name}</strong><small>{role.description}</small></span><Icon name="check" /></label>)}</div></div> <div className="form-section"><h2>Roles</h2><div className="choice-grid">{roles.map((role) => <label className={`choice-card ${roleIds.includes(role.id) ? 'selected' : ''}`} key={role.id}><input type="checkbox" checked={roleIds.includes(role.id)} onChange={() => toggleRole(role.id)} /><span><strong>{role.name}</strong><small>{role.description}</small></span><Icon name="check" /></label>)}</div></div>
<div className="form-actions"><Link className="button secondary" to="/admin/users">Cancelar</Link><button className="button primary" disabled={submitting}>{submitting ? 'Creando…' : 'Crear usuario'}</button></div> <div className="form-actions"><Link className="button secondary" to="/admin/users">Cancelar</Link><button className="button primary" disabled={submitting}>{submitting ? 'Creando…' : 'Crear usuario'}</button></div>