diff --git a/cosiap_api/cosiap_api/.env b/cosiap_api/cosiap_api/.env index 1b59abace1cfbdfced37e164e8a9617b5a7b145f..1b8c473086c249755da5edce9d772d86374b1358 100644 --- a/cosiap_api/cosiap_api/.env +++ b/cosiap_api/cosiap_api/.env @@ -14,8 +14,8 @@ DATABASES_DEFAULT_PORT="3306" EMAIL_HOST="sandbox.smtp.mailtrap.io" EMAIL_FROM="cosiap@example.com" -EMAIL_HOST_USER="52e12fdcb768cc" -EMAIL_HOST_PASSWORD="03520623807dea" +EMAIL_HOST_USER="3b48193365f615" +EMAIL_HOST_PASSWORD="37f89fc1d98f48" EMAIL_PORT="2525" EMAIL_USE_TLS=True diff --git a/cosiap_api/users/serializers.py b/cosiap_api/users/serializers.py index f7825c94373fa2eba6fb9417667ee594ce887f86..e71bed61253441ef51e28a9d5107bac3c28ed5ce 100644 --- a/cosiap_api/users/serializers.py +++ b/cosiap_api/users/serializers.py @@ -123,7 +123,7 @@ class SolicitanteSerializer(serializers.ModelSerializer): # indicamos el modelo a utilziar model = Solicitante # indicamos los campos que debe ingresar el usuario - fields = ['nombre','ap_paterno', 'ap_materno', 'telefono', 'RFC','sexo', 'direccion', 'codigo_postal', 'municipio', 'poblacion', 'INE'] + fields = ['curp', 'email', 'nombre','ap_paterno', 'ap_materno', 'telefono', 'RFC','sexo', 'direccion', 'codigo_postal', 'municipio', 'poblacion', 'INE'] # Agregamos validadores para asegurar que los campos puedan estar vacíos extra_kwargs = {'ap_paterno': {'required': False}, 'ap_materno': {'required': False},'telefono': {'required': False},'RFC': {'required': False}, 'sexo':{'required':False}, 'direccion': {'required': False},'codigo_postal': {'required': False},'municipio': {'required': False},'poblacion': {'required': False}, @@ -145,6 +145,8 @@ class SolicitanteSerializer(serializers.ModelSerializer): # Definimos una función para la actualización del solicitante, recibiendo una instancia def update(self, instance, validated_data): # Actualizar los campos del Solicitante + instance.curp = validated_data.get('curp', instance.curp) + instance.email = validated_data.get('email', instance.email) instance.nombre = validated_data.get('nombre', instance.nombre) instance.ap_paterno = validated_data.get('ap_paterno', instance.ap_paterno) instance.ap_materno = validated_data.get('ap_materno', instance.ap_materno) diff --git a/cosiap_frontend/src/App.css b/cosiap_frontend/src/App.css index 695f37cfb1f7f4a1c72f152fce0294e7547a0fad..b6ec5a7be721fc86e95919ca14b935a61a79b0d0 100644 --- a/cosiap_frontend/src/App.css +++ b/cosiap_frontend/src/App.css @@ -809,4 +809,24 @@ font-size: 16px; .search-btn:hover { background-color: #555; -} \ No newline at end of file +} + +.table-container { + overflow-x: auto; + max-width: 100%; + margin-top: 16px; +} + +table { + border-collapse: collapse; + width: 100%; +} + +th, td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #ddd; + white-space: nowrap; +} + + diff --git a/cosiap_frontend/src/components/admin/TablaUsuarios.jsx b/cosiap_frontend/src/components/admin/TablaUsuarios.jsx index bd2411f1fa90b9b32c4ccec75911e3051f5e52b9..4f6cffa31235e7be5f38da673288e64720b3f237 100644 --- a/cosiap_frontend/src/components/admin/TablaUsuarios.jsx +++ b/cosiap_frontend/src/components/admin/TablaUsuarios.jsx @@ -1,73 +1,115 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import api from '../../api'; -import Tabla from "../common/utility/ReusableTable"; // Importa la tabla reutilizable +import Tabla from "../common/utility/ReusableTable"; import MainContainer from "../common/utility/MainContainer"; import '@/App.css'; - -// Componente para recuperar la lista de usuarios solicitantes del sistema const ListaUsuarios = () => { const [usuarios, setUsuarios] = useState([]); const [searchQuery, setSearchQuery] = useState(''); + const [editRow, setEditRow] = useState(null); + const [registerChange, setRegisterChange] = useState({}); + const inputRefs = useRef({}); + const tableContainerRef = useRef(null); // Referencia al contenedor con scroll + const [alertMessage, setAlertMessage] = useState(''); + const [isSuccess, setIsSuccess] = useState(false); - - // obtenemos a los usuarios al cargar la página useEffect(() => { const fetchUsuarios = async () => { try { const response = await api.usuarios.solicitantes.get(); - setUsuarios(response.data.data) + setUsuarios(response.data.data); } catch (error) { - console.log("Error al recuperar la lista de usuarios.", error); + console.error("Error al recuperar la lista de usuarios.", error); setUsuarios([]); } }; fetchUsuarios(); }, []); - // Definimos las columnas a mostrar en la tabla + useEffect(() => { + const handleClickOutside = (event) => { + if (tableContainerRef.current?.contains(event.target)) { + return; // Ignorar el clic si es en el scroll + } + + const isOutside = !Object.values(inputRefs.current).some((input) => + input?.contains(event.target) + ); + + if (isOutside && editRow !== null) { + handleUpdate(editRow); + setEditRow(null); + setRegisterChange({}); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [editRow, registerChange]); + + const handleChange = (e, rowId, key) => { + const value = e.target.value; + + setRegisterChange((prev) => ({ + ...prev, + [rowId]: { + ...prev[rowId], + [key]: value === '' ? '' : value // Permitir valores vacíos + }, + })); + }; + const columnas = [ - { - label: "CURP", - render: (fila) => fila.curp - }, - { - label: "Nombre", - render: (fila) => fila.nombre - }, - { - label: "Apellido Paterno", - render: (fila) => fila.ap_paterno - }, - { - label: "Apellido Materno", - render: (fila) => fila.ap_materno - }, - { - label: "RFC", - render: (fila) => fila.RFC - }, - { - label: "E-mail", - render: (fila) => fila.email - }, - { - label: "Estado", - render: (fila) => fila.municipio__estado__nombre - }, - { - label: "Municipio", - render: (fila) => fila.municipio__nombre - } + { label: "CURP", render: (fila) => renderCell(fila, "curp") }, + { label: "Nombre", render: (fila) => renderCell(fila, "nombre") }, + { label: "Apellido Paterno", render: (fila) => renderCell(fila, "ap_paterno") }, + { label: "Apellido Materno", render: (fila) => renderCell(fila, "ap_materno") }, + { label: "RFC", render: (fila) => renderCell(fila, "RFC") }, + { label: "E-mail", render: (fila) => renderCell(fila, "email") }, ]; - // Actualiza el estado de `searchQuery` con cada cambio en el input + const renderCell = (fila, key) => { + const isEditing = editRow === fila.id; + return isEditing ? ( + handleChange(e, fila.id, key)} + ref={(el) => (inputRefs.current[fila.id + key] = el)} + style={{ + fontSize: "14px", + padding: "2px 4px", + borderRadius: "4px", + height: "1.6em", + lineHeight: "1", + border: "1px solid #d3d3d3", + display: "inline-block", + width: "auto", + maxWidth: "100px", + }} + autoFocus + /> + ) : ( + setEditRow(fila.id)}>{fila[key]} + ); + }; + + const showAlert = (message, isSuccess) => { + setAlertMessage(message); + setIsSuccess(isSuccess); + + setTimeout(() => { + setAlertMessage(''); + }, 3000); + }; + const handleInputChange = (e) => setSearchQuery(e.target.value); const handleSearch = async () => { try { const response = await api.usuarios.solicitantes.get({ - params: { search_query: searchQuery , model_name: "Solicitante", columns: "__all__"} + params: { search_query: searchQuery, model_name: "Solicitante", columns: "__all__" }, }); setUsuarios(response.data.data); } catch (error) { @@ -75,26 +117,46 @@ const ListaUsuarios = () => { } }; + const handleUpdate = async (id) => { + console.log(registerChange[id]); + try { + await api.usuarios.solicitantes.update(id, registerChange[id]) + const response = await api.usuarios.solicitantes.get(); + setUsuarios(response.data.data); + } catch (error) { + const errorData = error.response.data; + for (const key in errorData) { + if (errorData.hasOwnProperty(key)) { + showAlert(errorData[key], false); + } + } + } + }; + return ( + {alertMessage && ( +
+ {alertMessage} +
+ )}
- - + /> -
-
+
+
); }; -export default ListaUsuarios; \ No newline at end of file +export default ListaUsuarios;