F3.1 WEB: ampliar alta de usuarios e Inspector
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user