From ec81b85534683b2ad2b8dcfc248331a9595044e4 Mon Sep 17 00:00:00 2001 From: sandro-70 Date: Sun, 21 Jun 2026 10:43:43 -0600 Subject: [PATCH 1/2] Correccion y form para editar user --- .../features/createUser/SeccionContacto.tsx | 59 +++++----- .../features/createUser/SeccionDireccion.tsx | 69 +++++++---- .../src/features/createUser/SeccionEstado.tsx | 41 +++++++ .../features/createUser/SeccionPersonal.tsx | 17 ++- .../src/features/createUser/SeccionRol.tsx | 44 ++++++- frontend/src/features/createUser/types.ts | 64 +++++----- .../features/createUser/useCreateUserForm.tsx | 102 ++++++++++------ frontend/src/features/create_user_form.tsx | 80 ++++++++++--- .../gestionUsuarios/GestionarUsuarios.tsx | 109 ++++++++++++++++-- .../features/gestionUsuarios/UserFilters.tsx | 14 ++- .../src/features/gestionUsuarios/userCard.tsx | 10 +- 11 files changed, 463 insertions(+), 146 deletions(-) create mode 100644 frontend/src/features/createUser/SeccionEstado.tsx diff --git a/frontend/src/features/createUser/SeccionContacto.tsx b/frontend/src/features/createUser/SeccionContacto.tsx index f25f720..d544bd5 100644 --- a/frontend/src/features/createUser/SeccionContacto.tsx +++ b/frontend/src/features/createUser/SeccionContacto.tsx @@ -9,12 +9,16 @@ interface Props { e: React.ChangeEvent, ) => void; onCelularChange: (e: React.ChangeEvent) => void; + isEditing?: boolean; + disable?: boolean; } export function SeccionContacto({ formData, onChange, onCelularChange, + isEditing, + disable, }: Props) { const [showPassword, setShowPassword] = useState(false); @@ -35,6 +39,7 @@ export function SeccionContacto({ placeholder="ejemplo@correo.com" required className={INPUT_CLS} + disabled={disable} />
@@ -44,42 +49,44 @@ export function SeccionContacto({

+504 seguido de 8 dígitos

- -
- -
- - + {!isEditing && ( +
+ +
+ + +
-
+ )}
); -} \ No newline at end of file +} diff --git a/frontend/src/features/createUser/SeccionDireccion.tsx b/frontend/src/features/createUser/SeccionDireccion.tsx index 3282a22..be86850 100644 --- a/frontend/src/features/createUser/SeccionDireccion.tsx +++ b/frontend/src/features/createUser/SeccionDireccion.tsx @@ -1,13 +1,22 @@ import { ChevronRight } from "lucide-react"; import type { CreateUserFormData } from "./types"; -import { CALLES, BLOQUES, INPUT_CLS, LABEL_CLS, SECTION_TITLE_CLS, SELECT_CLS } from "./types"; - +import { + CALLES, + BLOQUES, + INPUT_CLS, + LABEL_CLS, + SECTION_TITLE_CLS, + SELECT_CLS, +} from "./types"; interface Props { formData: CreateUserFormData; - onChange: (e: React.ChangeEvent) => void; + onChange: ( + e: React.ChangeEvent, + ) => void; onTipoViviendaChange: (tipo: string) => void; onCasaHabilitadaChange: (value: boolean) => void; + disable?: boolean; } function SelectField({ @@ -18,6 +27,7 @@ function SelectField({ options, placeholder, required, + disable, }: { label: string; name: string; @@ -26,6 +36,7 @@ function SelectField({ options: { value: string; label: string }[]; placeholder: string; required?: boolean; + disable?: boolean; }) { return (
@@ -39,6 +50,7 @@ function SelectField({ onChange={onChange} required={required} className={SELECT_CLS} + disabled={disable} > {options.map((o) => ( @@ -61,9 +73,11 @@ export function SeccionDireccion({ onChange, onTipoViviendaChange, onCasaHabilitadaChange, + disable, }: Props) { const tieneApts = - formData.tipoVivienda === "Apartamentos" || formData.tipoVivienda === "Ambos"; + formData.tipoVivienda === "Apartamentos" || + formData.tipoVivienda === "Ambos"; const tieneCasa = formData.tipoVivienda === "Casa" || formData.tipoVivienda === "Ambos"; @@ -76,20 +90,22 @@ export function SeccionDireccion({
@@ -102,13 +118,14 @@ export function SeccionDireccion({
@@ -122,6 +139,7 @@ export function SeccionDireccion({ onChange={(e) => onTipoViviendaChange(e.target.value)} required className={SELECT_CLS} + disabled={disable} > @@ -142,22 +160,25 @@ export function SeccionDireccion({ ¿La casa está habilitada? *
- {([{ label: "Sí", value: true }, { label: "No", value: false }] as const).map( - ({ label, value }) => ( - - ), - )} + {( + [ + { label: "Sí", value: true }, + { label: "No", value: false }, + ] as const + ).map(({ label, value }) => ( + + ))}
)} @@ -179,6 +200,7 @@ export function SeccionDireccion({ step="1" required className={INPUT_CLS} + disabled={disable} />
@@ -195,6 +217,7 @@ export function SeccionDireccion({ step="1" required className={INPUT_CLS} + disabled={disable} />
diff --git a/frontend/src/features/createUser/SeccionEstado.tsx b/frontend/src/features/createUser/SeccionEstado.tsx new file mode 100644 index 0000000..c9975cc --- /dev/null +++ b/frontend/src/features/createUser/SeccionEstado.tsx @@ -0,0 +1,41 @@ +import type { CreateUserFormData } from "./types"; +import { SECTION_TITLE_CLS } from "./types"; + +interface Props { + formData: CreateUserFormData; + onEstadoChange: (estado: boolean) => void; + disable: boolean; +} + +export function SeccionEstado({ formData, onEstadoChange, disable }: Props) { + const isActive = formData.estado ?? true; + const estado = isActive ? "Activo" : "Inactivo"; + + return ( +
+

Estado del Usuario

+
+ {/* Badge Activo/Inactivo */} +
+

+ {estado.toUpperCase()} +

+
+ + {/* Switch Toggle */} + +
+
+ ); +} diff --git a/frontend/src/features/createUser/SeccionPersonal.tsx b/frontend/src/features/createUser/SeccionPersonal.tsx index f7febf9..7c9d461 100644 --- a/frontend/src/features/createUser/SeccionPersonal.tsx +++ b/frontend/src/features/createUser/SeccionPersonal.tsx @@ -7,9 +7,15 @@ interface Props { e: React.ChangeEvent, ) => void; onDniChange: (e: React.ChangeEvent) => void; + disable?: boolean; } -export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { +export function SeccionPersonal({ + formData, + onChange, + onDniChange, + disable, +}: Props) { return (

Información Personal

@@ -27,6 +33,7 @@ export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { placeholder="Ej: Juan" required className={INPUT_CLS} + disabled={disable} />
@@ -38,6 +45,7 @@ export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { onChange={onChange} placeholder="Ej: Carlos" className={INPUT_CLS} + disabled={disable} />
@@ -55,6 +63,7 @@ export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { placeholder="Ej: López" required className={INPUT_CLS} + disabled={disable} />
@@ -66,6 +75,7 @@ export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { onChange={onChange} placeholder="Ej: García" className={INPUT_CLS} + disabled={disable} />
@@ -77,13 +87,14 @@ export function SeccionPersonal({ formData, onChange, onDniChange }: Props) { ); -} \ No newline at end of file +} diff --git a/frontend/src/features/createUser/SeccionRol.tsx b/frontend/src/features/createUser/SeccionRol.tsx index 0388be0..492d54b 100644 --- a/frontend/src/features/createUser/SeccionRol.tsx +++ b/frontend/src/features/createUser/SeccionRol.tsx @@ -1,6 +1,7 @@ import { ChevronRight } from "lucide-react"; import type { CreateUserFormData } from "./types"; import { LABEL_CLS, SECTION_TITLE_CLS, SELECT_CLS } from "./types"; +import type { User } from "../gestionUsuarios/GestionarUsuarios"; const ROLES = [ { value: "0", rol: "DuenoDeCasa", label: "Cliente / Dueño de Casa" }, @@ -17,9 +18,45 @@ interface Props { onChange: ( e: React.ChangeEvent, ) => void; + disable: boolean; + users: User[]; } -export function SeccionRol({ formData, onChange }: Props) { + +export function SeccionRol({ formData, onChange, disable, users }: Props) { + const ROLE_LIMITS: Record = { + Presidente: 1, + Tesorero: 1, + Secretario: 1, + Vocal: 3, + Vicepresidente: 1, + Fiscal: 1, + + + DuenoDeCasa: Infinity, +}; + + const getRoleCounts = (users: User[]) =>{ + return users.reduce((acc,user)=>{ + acc[user.rol] = (acc[user.rol] || 0)+1; + return acc; + },{}as Record); + }; + const getRoleOptions = (users: User[]) => { + const counts = getRoleCounts(users); + + return ROLES.map((role) => { + const limit = ROLE_LIMITS[role.rol] ?? 1; + const current = counts[role.rol] ?? 0; + + return { + ...role, + disabled: current >= limit, + }; + }); +}; +const availableRoles = getRoleOptions(users); + return (

Tipo de Usuario

@@ -34,9 +71,10 @@ export function SeccionRol({ formData, onChange }: Props) { onChange={onChange} required className={SELECT_CLS} + disabled={disable} > - {ROLES.map((r) => ( - ))} diff --git a/frontend/src/features/createUser/types.ts b/frontend/src/features/createUser/types.ts index 3a99c59..ef863c0 100644 --- a/frontend/src/features/createUser/types.ts +++ b/frontend/src/features/createUser/types.ts @@ -3,18 +3,23 @@ export interface CreateUserFormData { segundoNombre: string; primerApellido: string; segundoApellido: string; - identificacion: string; + dni: string; correo: string; - celular: string; + telefono: string; contrasena: string; - calle: string; - bloque: string; - numerolote: string; + domicilios: { + calle: string; + codigoBloque: string; + loteCasa: string; + estructura: string; + }; tipoVivienda: string; casaHabilitada: boolean; cantidadApartamentos: string; apartamentosHabitados: string; rol: string; + idUsuario: number; + estado?: boolean; } export const INITIAL_FORM_DATA: CreateUserFormData = { @@ -22,42 +27,47 @@ export const INITIAL_FORM_DATA: CreateUserFormData = { segundoNombre: "", primerApellido: "", segundoApellido: "", - identificacion: "", + dni: "", correo: "", - celular: "", + telefono: "", contrasena: "", - calle: "", - bloque: "", - numerolote: "", + domicilios: { + calle: "", + codigoBloque: "", + loteCasa: "", + estructura: "", + }, tipoVivienda: "Casa", casaHabilitada: true, cantidadApartamentos: "", apartamentosHabitados: "", rol: "DuenoDeCasa", + idUsuario: -1, + estado: true, }; export const CALLES = [ - { value: "Calle1A", label: "Calle 1A" }, - { value: "Calle1B", label: "Calle 1B" }, - { value: "Calle2A", label: "Calle 2A" }, - { value: "Calle2B", label: "Calle 2B" }, - { value: "Calle3A", label: "Calle 3A" }, - { value: "Calle3B", label: "Calle 3B" }, - { value: "Calle4A", label: "Calle 4A" }, + { value: "Calle1A", calle: "Calle1A", label: "Calle 1A" }, + { value: "Calle1B", calle: "Calle1B", label: "Calle 1B" }, + { value: "Calle2A", calle: "Calle2A", label: "Calle 2A" }, + { value: "Calle2B", calle: "Calle2B", label: "Calle 2B" }, + { value: "Calle3A", calle: "Calle3A", label: "Calle 3A" }, + { value: "Calle3B", calle: "Calle3B", label: "Calle 3B" }, + { value: "Calle4A", calle: "Calle4A", label: "Calle 4A" }, ]; export const BLOQUES = [ { value: "FGAD", label: "FGAD" }, - { value: "A", label: "Bloque A" }, - { value: "B", label: "Bloque B" }, - { value: "C", label: "Bloque C" }, - { value: "D", label: "Bloque D" }, - { value: "E", label: "Bloque E" }, - { value: "F", label: "Bloque F" }, - { value: "G", label: "Bloque G" }, - { value: "H", label: "Bloque H" }, - { value: "I", label: "Bloque I" }, - { value: "J", label: "Bloque J" }, + { value: "A", codigoBloque: "A", label: "Bloque A" }, + { value: "B", codigoBloque: "B", label: "Bloque B" }, + { value: "C", codigoBloque: "C", label: "Bloque C" }, + { value: "D", codigoBloque: "D", label: "Bloque D" }, + { value: "E", codigoBloque: "E", label: "Bloque E" }, + { value: "F", codigoBloque: "F", label: "Bloque F" }, + { value: "G", codigoBloque: "G", label: "Bloque G" }, + { value: "H", codigoBloque: "H", label: "Bloque H" }, + { value: "I", codigoBloque: "I", label: "Bloque I" }, + { value: "J", codigoBloque: "J", label: "Bloque J" }, ]; export const INPUT_CLS = diff --git a/frontend/src/features/createUser/useCreateUserForm.tsx b/frontend/src/features/createUser/useCreateUserForm.tsx index 0d777b2..a2447f4 100644 --- a/frontend/src/features/createUser/useCreateUserForm.tsx +++ b/frontend/src/features/createUser/useCreateUserForm.tsx @@ -1,18 +1,20 @@ import { useState } from "react"; -import { toast } from 'react-toastify' +import { toast } from "react-toastify"; import { api } from "../../services/api"; import type { CreateUserFormData } from "./types"; import { INITIAL_FORM_DATA } from "./types"; import type { User } from "../gestionUsuarios/GestionarUsuarios"; - export function useCreateUserForm( isSuperAdmin: boolean, onUserCreated: (user: User) => void, onClose?: () => void, + initialData?: CreateUserFormData, + mode?: string, ) { - const [formData, setFormData] = - useState(INITIAL_FORM_DATA); + const [formData, setFormData] = useState( + initialData ?? INITIAL_FORM_DATA, + ); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -24,13 +26,26 @@ export function useCreateUserForm( }; const handleCelularChange = (e: React.ChangeEvent) => { - let value = e.target.value.replace(/\D/g, ""); - if (!value.startsWith("504")) value = "504" + value; - value = value.substring(0, 11); - let formatted = "+504"; - if (value.length > 3) formatted += " " + value.substring(3); - setFormData((prev) => ({ ...prev, celular: formatted })); - }; + let value = e.target.value; + + value = value.replace(/\D/g, ""); + + if (value.startsWith("504")) { + value = value.slice(3); + } + + + value = value.substring(0, 8); + + const formatted = value.length + ? `+504 ${value}` + : "+504 "; + + setFormData((prev) => ({ + ...prev, + telefono: formatted, + })); +}; const handleDniChange = (e: React.ChangeEvent) => { let value = e.target.value.replace(/\D/g, ""); @@ -51,7 +66,7 @@ export function useCreateUserForm( setFormData((prev) => ({ ...prev, - identificacion: formatted, + dni: formatted, })); }; @@ -74,28 +89,33 @@ export function useCreateUserForm( if (!formData.primerApellido.trim()) return "El primer apellido es obligatorio"; - if (!formData.identificacion.trim()) - return "La identificación es obligatoria"; - if (!formData.contrasena.trim()) return "La contraseña es obligatoria"; - if (formData.contrasena.length < 6) - return "La contraseña debe tener al menos 6 caracteres"; + if (!formData.dni.trim()) return "La identificación es obligatoria"; + const needsPassword = mode === "create"; + if (needsPassword) { + if (!formData.contrasena.trim()) return "La contraseña es obligatoria"; + if (formData.contrasena.length < 6) + return "La contraseña debe tener al menos 6 caracteres"; + } - const celularDigits = formData.celular.replace(/\D/g, ""); + const celularDigits = formData.telefono.replace(/\D/g, ""); if (celularDigits.length !== 11) return "El celular debe tener 8 dígitos después de +504"; if (!formData.correo.trim()) return "El correo electronico es obligatorio"; - const dni = formData.identificacion.trim().replace(/-/g, ""); + const dni = formData.dni.trim().replace(/-/g, ""); if (dni.length !== 13) return "El numero de identificacion debe tener 13 digitos"; - if (!formData.identificacion.trim()) + if (!formData.dni.trim()) return "El numero de identificacion es obligatorio"; const needsAddress = !isSuperAdmin || formData.rol === "DuenoDeCasa"; if (needsAddress) { - if (!formData.calle) return "La calle es obligatoria"; - if (!formData.bloque) return "El bloque es obligatorio"; - if (!formData.numerolote || parseInt(formData.numerolote) <= 0) + if (!formData.domicilios.calle) return "La calle es obligatoria"; + if (!formData.domicilios.codigoBloque) return "El bloque es obligatorio"; + if ( + !formData.domicilios.loteCasa || + parseInt(formData.domicilios.loteCasa) <= 0 + ) return "El número de lote es obligatorio"; const tieneApts = @@ -130,20 +150,22 @@ export function useCreateUserForm( try { const isDuenoDeCasa = formData.rol === "DuenoDeCasa"; + const telefono = formData.telefono.replace("+504 ", ""); const tieneApts = formData.tipoVivienda === "Apartamentos" || formData.tipoVivienda === "Ambos"; const payload: Record = { primerNombre: formData.primerNombre.trim(), - segundoNombre: formData.segundoNombre.trim() || null, + segundoNombre: formData.segundoNombre?.trim() || "", primerApellido: formData.primerApellido.trim(), - segundoApellido: formData.segundoApellido.trim(), - dni: formData.identificacion.trim().replace(/-/g, ""), + segundoApellido: formData.segundoApellido?.trim() || "", + dni: formData.dni.trim().replace(/-/g, ""), correo: formData.correo.trim(), - telefono: formData.celular, + telefono: telefono, password: formData.contrasena, rol: formData.rol, + Estado: formData.estado !== undefined ? formData.estado : true, TipoVivienda: formData.tipoVivienda, CasaHabilitada: @@ -160,20 +182,32 @@ export function useCreateUserForm( if (isDuenoDeCasa) { payload.domicilio = { - calle: formData.calle, - codigoBloque: formData.bloque, - loteCasa: parseInt(formData.numerolote) || 0, + calle: formData.domicilios.calle, + codigoBloque: formData.domicilios.codigoBloque, + loteCasa: parseInt(formData.domicilios.loteCasa) || 0, estructura: 1, }; } console.log(payload); - const { data } = await api.post("/api/usuarios", payload); - toast.success("Usuario creado exitosamente ") + + let response; + + if (mode === "create") { + response = await api.post("/api/usuarios", payload); + toast.success("Usuario creado exitosamente "); + } else { + const idUsuario = formData.idUsuario; + response = await api.patch(`/api/usuarios/${idUsuario}`, payload); + toast.success("Usuario actualizado exitosamente "); + } + + const { data } = response; + onUserCreated(data); if (onClose) onClose(); } catch (err: any) { console.error("Error al crear usuario:", err); - toast.error("Ocurrió un error al crear el usuario.") + toast.error("Ocurrió un error al crear el usuario."); } finally { setIsLoading(false); } @@ -190,4 +224,4 @@ export function useCreateUserForm( resetVivienda, handleSubmit, }; -} \ No newline at end of file +} diff --git a/frontend/src/features/create_user_form.tsx b/frontend/src/features/create_user_form.tsx index 442325d..b5eed92 100644 --- a/frontend/src/features/create_user_form.tsx +++ b/frontend/src/features/create_user_form.tsx @@ -4,18 +4,28 @@ import { SeccionPersonal } from "./createUser/SeccionPersonal"; import { SeccionContacto } from "./createUser/SeccionContacto"; import { SeccionDireccion } from "./createUser/SeccionDireccion"; import { SeccionRol } from "./createUser/SeccionRol"; +import { SeccionEstado } from "./createUser/SeccionEstado"; import type { User } from "./gestionUsuarios/GestionarUsuarios"; +import type { CreateUserFormData } from "./createUser/types"; interface CreateUserFormProps { onClose?: () => void; isSuperAdmin?: boolean; + data?: CreateUserFormData; + onEdit?: () => void; + mode: "create" | "edit" | "view"; onUserCreated: (user: User) => void; + users: User[]; } export default function CreateUserForm({ onClose, isSuperAdmin = false, + data, + mode, onUserCreated, + onEdit, + users, }: CreateUserFormProps) { const { formData, @@ -27,9 +37,19 @@ export default function CreateUserForm({ setField, resetVivienda, handleSubmit, - } = useCreateUserForm(isSuperAdmin, onUserCreated, onClose); + } = useCreateUserForm(isSuperAdmin, onUserCreated, onClose, data, mode); const showAddress = !isSuperAdmin; + console.log(mode); + console.log(data); + const title = + mode === "create" + ? "Crear Usuario" + : mode === "edit" + ? "Editar Usuario" + : "Detalles de Usuario"; + + const viewOnly = mode === "view"; return ( <> @@ -37,12 +57,22 @@ export default function CreateUserForm({
{/* Header */} -
+

- Crear Usuario + {title}

+ {mode === "view" && ( + + )} +
)} - -
- -
+ {mode !== "view" && ( +
+ +
+ )}
diff --git a/frontend/src/features/gestionUsuarios/GestionarUsuarios.tsx b/frontend/src/features/gestionUsuarios/GestionarUsuarios.tsx index 0e0edfc..d1fc7d6 100644 --- a/frontend/src/features/gestionUsuarios/GestionarUsuarios.tsx +++ b/frontend/src/features/gestionUsuarios/GestionarUsuarios.tsx @@ -6,6 +6,7 @@ import { useIsTablet } from "../historial/ingresos/useBreakpoint"; import CreateUserForm from "../create_user_form"; import { useAuthStore } from "../auth/store/authStore"; import { api } from "../../services/api"; +import type { CreateUserFormData } from "../createUser/types"; const CARDS_PER_PAGE = 4; export type UserRole = "Presidente" | "MiembroJav"; @@ -19,11 +20,28 @@ export interface User { dni: string; rol: string; estado: boolean; + correo: string; + telefono: string; + contrasena: string; + domicilios: { + calle: string; + codigoBloque: string; + loteCasa: string; + estructura: string; + }[]; + tipoVivienda: string; + casaHabilitada: boolean; + cantidadApartamentos: string; + apartamentosHabitados: string; } export default function GestionarUsuarios() { const isTablet = useIsTablet(); const [users, setUsers] = useState([]); + const [mode, setMode] = useState<"view" | "edit">("view"); + const [selectedUser, setSelectedUser] = useState< + CreateUserFormData | undefined + >(undefined); const [search, setSearch] = useState(""); const [filters, setFilters] = useState(DEFAULT_FILTERS); const [activeFilters, setActiveFilters] = useState(DEFAULT_FILTERS); @@ -36,6 +54,46 @@ export default function GestionarUsuarios() { console.log(user); console.log(token); + const handleCloseEdit = () => { + setMode("view"); + setShowEditUser(false); + }; + + function userToFormData(user: User): CreateUserFormData { + const domicilio = user.domicilios[0]; + const telefono = `+504 ${user.telefono}`; + return { + primerNombre: user.primerNombre, + segundoNombre: user.segundoNombre, + primerApellido: user.primerApellido, + segundoApellido: user.segundoApellido, + dni: user.dni, + correo: user.correo, + telefono: telefono, + + contrasena: "", + domicilios: { + calle: domicilio?.calle, + codigoBloque: domicilio?.codigoBloque, + loteCasa: domicilio?.loteCasa, + estructura: domicilio?.estructura, + }, + + tipoVivienda: user.tipoVivienda, + casaHabilitada: user.casaHabilitada, + cantidadApartamentos: user.cantidadApartamentos, + apartamentosHabitados: user.apartamentosHabitados, + rol: user.rol, + idUsuario: user.idUsuario, + estado: user.estado, + }; + } + + const handleUserClicked = (user: User) => { + setSelectedUser(userToFormData(user)); + setShowEditUser(true); + }; + const filtered = useMemo( () => users.filter((user) => { @@ -76,6 +134,7 @@ export default function GestionarUsuarios() { ); const [showCreateUser, setShowCreateUser] = useState(false); + const [showEditUser, setShowEditUser] = useState(false); const handleApply = () => { setActiveFilters(filters); @@ -84,6 +143,15 @@ export default function GestionarUsuarios() { const handleCrearUsuario = (newUser: User) => { setUsers((prev) => [...prev, newUser]); }; + const handleEditarUsuario = (updatedUser: User) => { + setUsers((prev) => + prev.map((user) => + user.idUsuario === updatedUser.idUsuario + ? updatedUser + : user + ) + ); + }; useEffect(() => { const fetchUsers = async () => { @@ -172,7 +240,11 @@ export default function GestionarUsuarios() { {/* Cards de usuarios */}
{paginatedUsers.map((user) => ( - + handleUserClicked(user)} + /> ))} {filtered.length === 0 && (
)} - {showCreateUser && ( - setShowCreateUser(false)} - isSuperAdmin={isSuperAdmin} - onUserCreated={handleCrearUsuario} - /> - )} + { + //Crear + showCreateUser && ( + setShowCreateUser(false)} + isSuperAdmin={isSuperAdmin} + onUserCreated={handleCrearUsuario} + mode="create" + users={users} + /> + ) + } + { + //Editar + showEditUser && ( + setMode("edit")} + users={users} + /> + ) + }
); -} \ No newline at end of file +} diff --git a/frontend/src/features/gestionUsuarios/UserFilters.tsx b/frontend/src/features/gestionUsuarios/UserFilters.tsx index 96d65aa..50c0f7b 100644 --- a/frontend/src/features/gestionUsuarios/UserFilters.tsx +++ b/frontend/src/features/gestionUsuarios/UserFilters.tsx @@ -16,6 +16,12 @@ export const DEFAULT_FILTERS: Filters = { role: "", status: "" }; export function UserFilters({ filters, onChange, onApply }: Props) { const set = (key: keyof Filters, value: string) => onChange({ ...filters, [key]: value }); + onApply(); + + const clearFilters = () => { + onChange(DEFAULT_FILTERS); + onApply(); + }; return (
- + {/** */}
); @@ -144,4 +150,4 @@ function CheckRow({ {label} ); -} \ No newline at end of file +} diff --git a/frontend/src/features/gestionUsuarios/userCard.tsx b/frontend/src/features/gestionUsuarios/userCard.tsx index befc0ff..f1fc23f 100644 --- a/frontend/src/features/gestionUsuarios/userCard.tsx +++ b/frontend/src/features/gestionUsuarios/userCard.tsx @@ -2,9 +2,10 @@ import type { User } from "./GestionarUsuarios"; interface Props { user: User; + onClick?: () => void; } -export default function UserCard({ user }: Props) { +export default function UserCard({ user, onClick }: Props) { const isActive = user.estado === true; const estado = isActive ? "Activo" : "Inactivo"; const nombre = [ @@ -17,7 +18,10 @@ export default function UserCard({ user }: Props) { .join(" "); return ( -
+
{/**Nombre y dni */}

Nombre: {nombre}

@@ -32,4 +36,4 @@ export default function UserCard({ user }: Props) {
); -} \ No newline at end of file +} From e0e193f718d73d2567afbaf75e683eecc4fa7521 Mon Sep 17 00:00:00 2001 From: sandro-70 Date: Sun, 21 Jun 2026 15:58:14 -0600 Subject: [PATCH 2/2] Validaciones de maximo de tipos de usuario --- .../src/features/createUser/SeccionRol.tsx | 56 ++++++++++--------- .../features/createUser/useCreateUserForm.tsx | 32 +++++------ frontend/src/features/create_user_form.tsx | 22 +++++--- .../gestionUsuarios/GestionarUsuarios.tsx | 10 ++-- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/frontend/src/features/createUser/SeccionRol.tsx b/frontend/src/features/createUser/SeccionRol.tsx index 492d54b..84e6350 100644 --- a/frontend/src/features/createUser/SeccionRol.tsx +++ b/frontend/src/features/createUser/SeccionRol.tsx @@ -22,40 +22,43 @@ interface Props { users: User[]; } - export function SeccionRol({ formData, onChange, disable, users }: Props) { const ROLE_LIMITS: Record = { - Presidente: 1, - Tesorero: 1, - Secretario: 1, - Vocal: 3, - Vicepresidente: 1, - Fiscal: 1, + Presidente: 1, + Tesorero: 1, + Secretario: 1, + Vocal: 3, + Vicepresidente: 1, + Fiscal: 1, - - DuenoDeCasa: Infinity, -}; + DuenoDeCasa: Infinity, + }; - const getRoleCounts = (users: User[]) =>{ - return users.reduce((acc,user)=>{ - acc[user.rol] = (acc[user.rol] || 0)+1; - return acc; - },{}as Record); + const getRoleCounts = (users: User[]) => { + return users + .filter((user) => user.estado) + .reduce( + (acc, user) => { + acc[user.rol] = (acc[user.rol] || 0) + 1; + return acc; + }, + {} as Record, + ); }; const getRoleOptions = (users: User[]) => { - const counts = getRoleCounts(users); + const counts = getRoleCounts(users); - return ROLES.map((role) => { - const limit = ROLE_LIMITS[role.rol] ?? 1; - const current = counts[role.rol] ?? 0; + return ROLES.map((role) => { + const limit = ROLE_LIMITS[role.rol] ?? 1; + const current = counts[role.rol] ?? 0; - return { - ...role, - disabled: current >= limit, - }; - }); -}; -const availableRoles = getRoleOptions(users); + return { + ...role, + disabled: current >= limit, + }; + }); + }; + const availableRoles = getRoleOptions(users); return (
@@ -76,6 +79,7 @@ const availableRoles = getRoleOptions(users); {availableRoles.map((r) => ( ))} diff --git a/frontend/src/features/createUser/useCreateUserForm.tsx b/frontend/src/features/createUser/useCreateUserForm.tsx index a2447f4..1f8a4b9 100644 --- a/frontend/src/features/createUser/useCreateUserForm.tsx +++ b/frontend/src/features/createUser/useCreateUserForm.tsx @@ -12,6 +12,7 @@ export function useCreateUserForm( initialData?: CreateUserFormData, mode?: string, ) { + console.log(isSuperAdmin); const [formData, setFormData] = useState( initialData ?? INITIAL_FORM_DATA, ); @@ -26,26 +27,23 @@ export function useCreateUserForm( }; const handleCelularChange = (e: React.ChangeEvent) => { - let value = e.target.value; + let value = e.target.value; - value = value.replace(/\D/g, ""); + value = value.replace(/\D/g, ""); - if (value.startsWith("504")) { - value = value.slice(3); - } + if (value.startsWith("504")) { + value = value.slice(3); + } - - value = value.substring(0, 8); + value = value.substring(0, 8); - const formatted = value.length - ? `+504 ${value}` - : "+504 "; + const formatted = value.length ? `+504 ${value}` : "+504 "; - setFormData((prev) => ({ - ...prev, - telefono: formatted, - })); -}; + setFormData((prev) => ({ + ...prev, + telefono: formatted, + })); + }; const handleDniChange = (e: React.ChangeEvent) => { let value = e.target.value.replace(/\D/g, ""); @@ -108,7 +106,7 @@ export function useCreateUserForm( if (!formData.dni.trim()) return "El numero de identificacion es obligatorio"; - const needsAddress = !isSuperAdmin || formData.rol === "DuenoDeCasa"; + const needsAddress = formData.rol === "DuenoDeCasa"; if (needsAddress) { if (!formData.domicilios.calle) return "La calle es obligatoria"; if (!formData.domicilios.codigoBloque) return "El bloque es obligatorio"; @@ -202,7 +200,7 @@ export function useCreateUserForm( } const { data } = response; - + onUserCreated(data); if (onClose) onClose(); } catch (err: any) { diff --git a/frontend/src/features/create_user_form.tsx b/frontend/src/features/create_user_form.tsx index b5eed92..6bf8e35 100644 --- a/frontend/src/features/create_user_form.tsx +++ b/frontend/src/features/create_user_form.tsx @@ -50,6 +50,9 @@ export default function CreateUserForm({ : "Detalles de Usuario"; const viewOnly = mode === "view"; + const hasPresidente = users.some( + (user) => user.rol === "Presidente" && user.estado, + ); return ( <> @@ -88,15 +91,16 @@ export default function CreateUserForm({ onSubmit={handleSubmit} className="flex flex-col gap-6 px-6 py-6 w-full max-w-125" > - {isSuperAdmin && ( - - )} - + {isSuperAdmin || + (!hasPresidente && ( + + ))} + {mode !== "create" && ( { setUsers((prev) => - prev.map((user) => - user.idUsuario === updatedUser.idUsuario - ? updatedUser - : user - ) - ); + prev.map((user) => + user.idUsuario === updatedUser.idUsuario ? updatedUser : user, + ), + ); }; useEffect(() => {