diff --git a/web-v2/src/pages/UserDetailPage.tsx b/web-v2/src/pages/UserDetailPage.tsx index e0be2a7..9ad3f38 100644 --- a/web-v2/src/pages/UserDetailPage.tsx +++ b/web-v2/src/pages/UserDetailPage.tsx @@ -1,25 +1,28 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import type { FormEvent } from 'react'; import { Link, useLocation, useParams } from 'react-router'; import { useAuth } from '../auth/AuthContext'; import { Alert, LoadingBlock, errorMessage } from '../components/Feedback'; import { Icon } from '../components/Icon'; import { - getUser, listRoles, replaceUserRoles, resetUserPassword, - updateUser, updateUserStatus, } from '../lib/api'; -import type { AdministrativeRole, AdministrativeUser } from '../lib/api'; +import type { AdministrativeRole } from '../lib/api'; +import { + getUserProfile, + updateUserProfile, +} from '../lib/userProfileApi'; +import type { AdministrativeUserProfile } from '../lib/userProfileApi'; import { formatDate, initials } from '../lib/format'; export function UserDetailPage() { const { id = '' } = useParams(); const location = useLocation(); const { user: currentUser, hasPermission } = useAuth(); - const [user, setUser] = useState(null); + const [user, setUser] = useState(null); const [roles, setRoles] = useState([]); const [roleIds, setRoleIds] = useState([]); const [loading, setLoading] = useState(true); @@ -33,7 +36,7 @@ export function UserDetailPage() { const load = async () => { setLoading(true); try { - const [loadedUser, loadedRoles] = await Promise.all([getUser(id), listRoles()]); + const [loadedUser, loadedRoles] = await Promise.all([getUserProfile(id), listRoles()]); setUser(loadedUser); setRoles(loadedRoles); setRoleIds(loadedUser.roles.map((role) => role.id)); @@ -46,27 +49,45 @@ export function UserDetailPage() { useEffect(() => { void load(); }, [id]); + const inspectorRoleId = useMemo(() => roles.find((role) => role.code === 'inspector')?.id ?? null, [roles]); + const inspectorSelected = inspectorRoleId ? roleIds.includes(inspectorRoleId) : false; + const saveProfile = async (event: FormEvent) => { event.preventDefault(); - setError(''); setSuccess(''); setSaving('profile'); + setError(''); setSuccess(''); const data = new FormData(event.currentTarget); + const email = String(data.get('email') ?? '').trim(); + if (inspectorSelected && !email) { + setError('El email es obligatorio para un Inspector porque allí recibe la documentación de sus inspecciones.'); + return; + } + setSaving('profile'); try { - const updated = await updateUser(id, { + const updated = await updateUserProfile(id, { firstName: String(data.get('firstName')), lastName: String(data.get('lastName')), username: String(data.get('username')), - email: String(data.get('email') ?? '') || null, + 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, }); - setUser(updated); setSuccess('Datos del usuario actualizados'); + setUser(updated); setSuccess('Datos personales y de contacto actualizados'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSaving(''); } }; const saveRoles = async () => { + if (inspectorSelected && !user?.email) { + setError('Antes de asignar el rol Inspector, cargá y guardá un email válido.'); + return; + } setError(''); setSuccess(''); setSaving('roles'); try { - const updated = await replaceUserRoles(id, roleIds); - setUser(updated); setSuccess('Roles actualizados correctamente'); + await replaceUserRoles(id, roleIds); + const refreshed = await getUserProfile(id); + setUser(refreshed); setSuccess('Roles actualizados correctamente'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSaving(''); } }; @@ -78,8 +99,9 @@ export function UserDetailPage() { if (!window.confirm(`¿Confirmás que querés ${verb} a ${user.username}?`)) return; setError(''); setSuccess(''); setSaving('status'); try { - const updated = await updateUserStatus(id, next); - setUser(updated); setSuccess(`Usuario ${next === 'ACTIVE' ? 'activado' : 'desactivado'}`); + await updateUserStatus(id, next); + const refreshed = await getUserProfile(id); + setUser(refreshed); setSuccess(`Usuario ${next === 'ACTIVE' ? 'activado' : 'desactivado'}`); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSaving(''); } }; @@ -91,8 +113,9 @@ export function UserDetailPage() { if (!window.confirm(`¿Restablecer la contraseña de ${user?.username ?? 'este usuario'}? Se cerrarán todas sus sesiones activas.`)) return; setError(''); setSuccess(''); setSaving('password'); try { - const updated = await resetUserPassword(id, resetPasswordValue, forcePasswordChange); - setUser(updated); + await resetUserPassword(id, resetPasswordValue, forcePasswordChange); + const refreshed = await getUserProfile(id); + setUser(refreshed); setResetPasswordValue(''); setResetPasswordConfirm(''); setSuccess('Contraseña restablecida. Las sesiones anteriores fueron revocadas.'); } catch (requestError) { setError(errorMessage(requestError)); } @@ -110,13 +133,23 @@ export function UserDetailPage() { return
Usuarios/{user.username}
-
{initials(user.firstName, user.lastName)}
DETALLE DE USUARIO

{user.firstName} {user.lastName}

@{user.username} · Creado {formatDate(user.createdAt)}

{canChangeStatus && }
+
{initials(user.firstName, user.lastName)}
DETALLE DE USUARIO

{user.firstName} {user.lastName}

@{user.username} · {user.jobTitle || 'Función sin informar'} · Creado {formatDate(user.createdAt)}

{canChangeStatus && }
{error && {error}}{success && {success}}
-
CUENTA

Datos personales

{user.status === 'ACTIVE' ? 'Activo' : 'Inactivo'}
-
+
PERFIL

Datos personales y contacto

{user.status === 'ACTIVE' ? 'Activo' : 'Inactivo'}
+
+ + + + + + + + +
+ {inspectorSelected &&

Destinatario del Inspector: al finalizar una inspección, la documentación se enviará también a {user.email || 'este email cuando lo completes'}.

}
Último acceso{formatDate(user.lastLoginAt)}
Último cambio de clave{formatDate(user.passwordChangedAt)}
Intentos fallidos{user.failedLoginAttempts}
Bloqueado hasta{formatDate(user.lockedUntil)}
{user.mustChangePassword && Este usuario debe cambiar su contraseña temporal en el próximo ingreso.} {canUpdate &&
} @@ -134,7 +167,8 @@ export function UserDetailPage() {
AUTORIZACIÓN

Roles asignados

{roleIds.length}
{roles.map((role) => )}
- {canAssign &&
} + {inspectorSelected && !user.email && Para guardar el rol Inspector, primero cargá y guardá un email válido.} + {canAssign &&
}
;