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 { listRoles, replaceUserRoles, resetUserPassword, updateUserStatus, } 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 [roles, setRoles] = useState([]); const [roleIds, setRoleIds] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(''); const [error, setError] = useState(''); const [success, setSuccess] = useState((location.state as { created?: boolean } | null)?.created ? 'Usuario creado correctamente' : ''); const [resetPasswordValue, setResetPasswordValue] = useState(''); const [resetPasswordConfirm, setResetPasswordConfirm] = useState(''); const [forcePasswordChange, setForcePasswordChange] = useState(true); const load = async () => { setLoading(true); try { const [loadedUser, loadedRoles] = await Promise.all([getUserProfile(id), listRoles()]); setUser(loadedUser); setRoles(loadedRoles); setRoleIds(loadedUser.roles.map((role) => role.id)); } catch (requestError) { setError(errorMessage(requestError)); } finally { setLoading(false); } }; 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(''); 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 updateUserProfile(id, { firstName: String(data.get('firstName')), lastName: String(data.get('lastName')), username: String(data.get('username')), 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 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 { await replaceUserRoles(id, roleIds); const refreshed = await getUserProfile(id); setUser(refreshed); setSuccess('Roles actualizados correctamente'); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSaving(''); } }; const changeStatus = async () => { if (!user) return; const next = user.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'; const verb = next === 'INACTIVE' ? 'desactivar' : 'activar'; if (!window.confirm(`¿Confirmás que querés ${verb} a ${user.username}?`)) return; setError(''); setSuccess(''); setSaving('status'); try { await updateUserStatus(id, next); const refreshed = await getUserProfile(id); setUser(refreshed); setSuccess(`Usuario ${next === 'ACTIVE' ? 'activado' : 'desactivado'}`); } catch (requestError) { setError(errorMessage(requestError)); } finally { setSaving(''); } }; const resetPassword = async (event: FormEvent) => { event.preventDefault(); if (resetPasswordValue.length < 12) { setError('La contraseña temporal debe tener al menos 12 caracteres.'); return; } if (resetPasswordValue !== resetPasswordConfirm) { setError('Las contraseñas no coinciden.'); return; } 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 { 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)); } finally { setSaving(''); } }; const toggleRole = (roleId: string) => setRoleIds((current) => current.includes(roleId) ? current.filter((value) => value !== roleId) : [...current, roleId]); if (loading) return ; if (!user) return {error || 'Usuario no encontrado'}; const canUpdate = hasPermission('users.update'); const canAssign = hasPermission('users.assign_roles'); const canChangeStatus = hasPermission('users.change_status'); return
Usuarios/{user.username}
{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}}
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 &&
}
{canUpdate &&
SEGURIDAD

Restablecer contraseña

El restablecimiento revoca todas las sesiones activas del usuario. La contraseña nunca se guarda en auditoría.
}
AUTORIZACIÓN

Roles asignados

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