diff --git a/app/bulkCreate/page.js b/app/bulkCreate/page.js new file mode 100644 index 0000000..b352752 --- /dev/null +++ b/app/bulkCreate/page.js @@ -0,0 +1,115 @@ +"use client" +import React, { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Container, Button } from "@mui/material"; +import Navbar from '@/components/Navbar'; +import RegisterForm from '@/components/registerForm'; +import AuthService from '@/services/AuthService'; +import SimpleSnackbar from '@/components/SimpleSnackbar'; + +const BulkCreate = () => { + const [forms, setForms] = useState([{ id: 0, formData: {}, errors: {} }]); + const [message, setMessage] = useState(""); + const [openSnack, setOpenSnack] = useState(false); + const router = useRouter(); + + useEffect(() => { + const token = localStorage.getItem('token'); + if (!token) { + router.push('/login'); + } + }, [router]); + + const addForm = () => { + setForms([...forms, { id: forms.length, formData: {}, errors: {} }]); + }; + + const removeForm = (id) => { + setForms(forms.filter(form => form.id !== id)); + }; + + const updateFormData = (id, data) => { + setForms(forms.map(form => form.id === id ? { ...form, formData: data } : form)); + }; + + const validateEmail = (email) => { + const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return re.test(String(email).toLowerCase()); + }; + + const handleSubmit = async () => { + const token = localStorage.getItem('token'); + if (!token) { + setMessage('Por favor, inicia sesión primero'); + setOpenSnack(true); + return; + } + + const newForms = forms.map((form) => { + const newErrors = {}; + if (!form.formData.name) newErrors.name = "Este campo es obligatorio."; + if (!form.formData.email) newErrors.email = "Este campo es obligatorio."; + if (!form.formData.password) newErrors.password = "Este campo es obligatorio."; + if (form.formData.password !== form.formData.password_second) newErrors.password_second = "Las contraseñas no coinciden."; + if (!form.formData.cellphone) newErrors.cellphone = "Este campo es obligatorio."; + if (form.formData.email && !validateEmail(form.formData.email)) newErrors.email = "El formato del email no es válido."; + return { ...form, errors: newErrors }; + }); + + setForms(newForms); + + const hasErrors = newForms.some(form => Object.keys(form.errors).length > 0); + if (hasErrors) { + setMessage("Por favor, corrige los errores en los formularios."); + setOpenSnack(true); + return; + } + + let users = newForms.map(form => ({ + name: form.formData.name, + email: form.formData.email, + password: form.formData.password, + password_second: form.formData.password_second, + cellphone: form.formData.cellphone + })); + + let objetos = { users }; + + console.log('Users to be sent:', objetos); // Asegúrate de que los datos son correctos + + try { + const response = await AuthService.bulkCreateUsers(objetos); // Enviar como objeto con clave 'users' + if (response && response.message) { + setMessage(`Usuarios creados: ${response.message.successful}, fallidos: ${response.message.failed}`); + } else { + setMessage('Error inesperado al crear usuarios'); + } + } catch (error) { + setMessage('Error al conectar con el servidor'); + } + setOpenSnack(true); + }; + + return ( + + +

Registrar Múltiples Usuarios

+ setOpenSnack(!openSnack)} /> + {forms.map((form, index) => ( +
+ + +
+ ))} + + +
+ ); +}; + +export default BulkCreate; diff --git a/app/filters/page.js b/app/filters/page.js new file mode 100644 index 0000000..64d441c --- /dev/null +++ b/app/filters/page.js @@ -0,0 +1,124 @@ +// app/filters/page.js +"use client" +import React, { useEffect, useState } from 'react'; +import { Container, Select, MenuItem, FormControl, InputLabel, TextField, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material'; +import { useRouter, useSearchParams } from 'next/navigation'; +import AuthService from '@/services/AuthService'; +import Navbar from '@/components/Navbar'; + +const Filters = () => { + const [users, setUsers] = useState([]); + const router = useRouter(); + const searchParams = useSearchParams(); + const status = searchParams.get('status') || ''; + const name = searchParams.get('name') || ''; + const logAfter = searchParams.get('logAfter') || ''; + const logBefore = searchParams.get('logBefore') || ''; + + useEffect(() => { + const token = localStorage.getItem('token'); + if (!token) { + router.push('/login'); + return; + } + + const fetchFilteredUsers = async () => { + const filters = { + status, + name, + logAfter, + logBefore, + }; + const data = await AuthService.findUsers(filters); + setUsers(data); + }; + fetchFilteredUsers(); + }, [status, name, logAfter, logBefore, router]); + + const handleInputChange = (e) => { + const { name, value } = e.target; + const params = new URLSearchParams(window.location.search); + if (value) { + params.set(name, value); + } else { + params.delete(name); + } + router.push(`/filters?${params.toString()}`); + }; + + return ( + + +

Filtros

+ + Filtrar por estado + + + + + + + + + Nombre + Email + Estado + + + + {users.map(user => ( + + {user.name} + {user.email} + {user.status ? 'Activo' : 'Inactivo'} + + ))} + +
+
+ ); +}; + +export default Filters; diff --git a/app/login/page.js b/app/login/page.js index 9ebaef4..07cf8b9 100644 --- a/app/login/page.js +++ b/app/login/page.js @@ -1,27 +1,39 @@ "use client" -import React, {useState} from 'react'; -import {Card, CardContent, Container} from "@mui/material"; +import React, { useState } from 'react'; +import { Card, CardContent, Container } from "@mui/material"; import TextField from '@mui/material/TextField'; -import Button from "@mui/material/Button"; +import Button from '@mui/material/Button'; import SimpleSnackbar from '../../components/SimpleSnackbar'; import { useRouter } from 'next/navigation'; +import FormHelperText from '@mui/material/FormHelperText'; import AuthService from '../../services/AuthService'; import './page.css'; -export default function Login(){ +export default function Login() { const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [state, setState] = useState(true); + const [emailError, setEmailError] = useState(""); + const [generalError, setGeneralError] = useState(""); const handleLogin = async () => { - const login = await AuthService.handleLogin(email, password); - setState(login); - if(login){ + const response = await AuthService.handleLogin(email, password); + if (response.success) { + setState(true); router.push('/users'); + } else { + if (response.message === "Correo electrónico no encontrado en la base de datos") { + setEmailError("El email no existe, regístrate."); + setGeneralError(""); + } else { + setEmailError(""); + setGeneralError(response.message || "Usuario o contraseña incorrectos"); + } + setState(false); } } @@ -36,25 +48,36 @@ export default function Login(){

Inicia Sesión

- setState(true)}/> + setState(true)} />
setEmail(e.target.value)} + value={email} + onChange={(e) => { + setEmail(e.target.value); + setEmailError(""); + }} + error={emailError !== ""} + helperText={emailError} />
- +
+ + {emailError} + +
setPassword(e.target.value)} />
@@ -68,4 +91,4 @@ export default function Login(){ ); -} \ No newline at end of file +} diff --git a/app/register/page.js b/app/register/page.js index e5d96cb..76d7a37 100644 --- a/app/register/page.js +++ b/app/register/page.js @@ -1,104 +1,83 @@ "use client" import React from "react"; -import {Card, CardContent, Container} from "@mui/material"; -import TextField from "@mui/material/TextField"; -import Button from "@mui/material/Button"; +import { Container, Button } from "@mui/material"; import SimpleSnackbar from "@/components/SimpleSnackbar"; import AuthService from "@/services/AuthService"; +import RegisterForm from "@/components/registerForm"; +import { useRouter } from 'next/navigation'; import './page.css'; const Register = () => { - // Register from user -> name, email, password, cellphone - const [name, setName] = React.useState(""); - const [email, setEmail] = React.useState(""); - const [password, setPassword] = React.useState(""); - const [password_second, setPasswordSecond] = React.useState(""); - const [cellphone, setCellphone] = React.useState(""); - + const [forms, setForms] = React.useState([{ id: 0, formData: {}, errors: {} }]); const [message, setMessage] = React.useState(""); const [openSnack, setOpenSnack] = React.useState(false); - const handleRegister = async () => { - if(password !== password_second){ - setMessage("Las contraseñas no coinciden"); + const router = useRouter(); + + const updateFormData = (id, data) => { + setForms(forms.map(form => form.id === id ? { ...form, formData: data } : form)); + }; + + const handleRegisterAll = async () => { + const newForms = forms.map((form) => { + const newErrors = {}; + if (!form.formData.name) newErrors.name = "Este campo es obligatorio."; + if (!form.formData.email) newErrors.email = "Este campo es obligatorio."; + if (!form.formData.password) newErrors.password = "Este campo es obligatorio."; + if (form.formData.password !== form.formData.password_second) newErrors.password_second = "Las contraseñas no coinciden."; + if (!form.formData.cellphone) newErrors.cellphone = "Este campo es obligatorio."; + return { ...form, errors: newErrors }; + }); + + setForms(newForms); + + const hasErrors = newForms.some(form => Object.keys(form.errors).length > 0); + if (hasErrors) { + setMessage("Todos los campos deben ser completados correctamente."); setOpenSnack(true); return; } - const response = await AuthService.registerUser(name, email, password, password_second, cellphone); - if(!response){ - setMessage("Error al registrar usuario"); - setOpenSnack(true); - } else { - setMessage("Usuario Registrado Exitosamente!"); - setOpenSnack(true); + + for (const form of forms) { + const { name, email, password, password_second, cellphone } = form.formData; + const response = await AuthService.registerUser(name, email, password, password_second, cellphone); + if (!response) { + setMessage(`Error al registrar usuario ${form.id + 1}`); + setOpenSnack(true); + return; + } } - } + + setMessage("Todos los usuarios registrados exitosamente!"); + setOpenSnack(true); + }; + + const handleBack = () => { + router.push('/login'); + }; return ( - {setOpenSnack(!openSnack)}}/> - - -

Register User

-
- setName(e.target.value)} - /> -
-
- setEmail(e.target.value)} - /> -
-
- setPassword(e.target.value)} - /> -
-
- setPasswordSecond(e.target.value)} - /> -
-
- setCellphone(e.target.value)} - /> -
-
- -
-
-
+ { setOpenSnack(!openSnack); }} + /> + {forms.map((form, index) => ( +
+ +
+ ))} + +
- ) -} -export default Register; \ No newline at end of file + ); +}; + +export default Register; diff --git a/components/Navbar.js b/components/Navbar.js index c281d22..d34ebed 100644 --- a/components/Navbar.js +++ b/components/Navbar.js @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState } from 'react'; import './Navbar.css'; import Button from '@mui/material/Button'; import AuthService from '@/services/AuthService'; @@ -6,32 +6,33 @@ import { useRouter } from 'next/navigation'; const Navbar = () => { const router = useRouter(); - const [user, setUser] = useState({name:""}); + const [user, setUser] = useState({ name: "" }); - useEffect(() => { - setUser(JSON.parse(localStorage.getItem('user'))); - }, []); + useEffect(() => { + setUser(JSON.parse(localStorage.getItem('user'))); + }, []); - const handleLogout = async () => { + const handleLogout = async () => + { const token = localStorage.getItem('token'); const result = await AuthService.logOut(token); - if(result){ - router.push('/login'); - } + if (result) { + router.push('/login'); } + } - return ( -
-
- {user?.name} -
-
- -
-
- ) + return ( +
+
+ {user?.name} +
+
+ + + +
+
+ ) } export default Navbar; \ No newline at end of file diff --git a/components/SimpleSnackbar.js b/components/SimpleSnackbar.js index 7898c54..1c84c3a 100644 --- a/components/SimpleSnackbar.js +++ b/components/SimpleSnackbar.js @@ -1,14 +1,21 @@ "use client" import * as React from 'react'; import Snackbar from '@mui/material/Snackbar'; +import MuiAlert from '@mui/material/Alert'; import IconButton from '@mui/material/IconButton'; import CloseIcon from '@mui/icons-material/Close'; -const SimpleSnackbar = (props) => { +const Alert = React.forwardRef(function Alert(props, ref) { + return ; +}); + +const SimpleSnackbar = (props) => { const { message, openSnack, closeSnack } = props; const handleClose = (event, reason) => { - event.preventDefault(); + if (event) { + event.preventDefault(); + } if (reason === 'clickaway') { return; } @@ -35,11 +42,14 @@ const SimpleSnackbar = (props) => { open={openSnack} autoHideDuration={6000} onClose={handleClose} - message={message} - //action={action} - /> + action={action} + > + + {message} + + ); } -export default SimpleSnackbar; \ No newline at end of file +export default SimpleSnackbar; diff --git a/components/registerForm.js b/components/registerForm.js new file mode 100644 index 0000000..3d5efb8 --- /dev/null +++ b/components/registerForm.js @@ -0,0 +1,86 @@ +// components/RegisterForm.js +import React from 'react'; +import { Card, CardContent, TextField } from "@mui/material"; + +const RegisterForm = ({ index, formData, errors, updateFormData }) => { + const handleChange = (field, value) => { + updateFormData(index, { ...formData, [field]: value }); + }; + + return ( + + +

Register User {index + 1}

+
+ handleChange('name', e.target.value)} + error={!!errors.name} + helperText={errors.name} + /> +
+
+ handleChange('email', e.target.value)} + error={!!errors.email} + helperText={errors.email} + /> +
+
+ handleChange('password', e.target.value)} + error={!!errors.password} + helperText={errors.password} + /> +
+
+ handleChange('password_second', e.target.value)} + error={!!errors.password_second} + helperText={errors.password_second} + /> +
+
+ handleChange('cellphone', e.target.value)} + error={!!errors.cellphone} + helperText={errors.cellphone} + /> +
+
+
+ ); +}; + +export default RegisterForm; diff --git a/services/AuthService.js b/services/AuthService.js index 3b7920a..fecb33f 100644 --- a/services/AuthService.js +++ b/services/AuthService.js @@ -1,49 +1,26 @@ import axios from 'axios'; const handleLogin = async (user, pass) => { - try{ + try { const response = await axios.post('http://localhost:3001/api/v1/auth/login', { email: user, password: pass, }); - //response.data contains a token in BASE64 format + // response.data contains a token in BASE64 format const decoded = atob(response.data); localStorage.setItem('token', response.data); localStorage.setItem('user', decoded); - return true; + return { success: true }; } catch (e) { console.error(e); - return false; + return { success: false, message: 'Usuario o contraseña incorrectos' }; } } const getUsers = async () => { try { - //const response = await axios.get('fakeapi'); - const response = { - data: [ - { - id: 1, - name: 'muhammad fake', - email: 'a@b.cl', - status: true - }, - - { - id: 2, - name: 'muhammed fake', - email: 'b@b.cl', - status: true - }, - { - id: 3, - name: 'muhammid fake', - email: 'c@b.cl', - status: true - }, - ] - } + const response = await axios.get('http://localhost:3001/api/v1/users'); return response.data; } catch (e) { console.error(e); @@ -53,13 +30,13 @@ const getUsers = async () => { const getUserById = async (id, token) => { try { - const response = await axios.get('http://localhost:3001/api/v1/users/' + id, { + const response = await axios.get(`http://localhost:3001/api/v1/users/${id}`, { headers: { token, } }); return response.data; - } catch(e){ + } catch (e) { console.error(e); return null; } @@ -72,7 +49,7 @@ const logOut = async (token) => { 'token': token, } }); - if(response.status !== 200){ + if (response.status !== 200) { return false; } localStorage.removeItem('token'); @@ -85,7 +62,7 @@ const logOut = async (token) => { } const registerUser = async (name, email, password, password_second, cellphone) => { - try{ + try { const response = await axios.post('http://localhost:3001/api/v1/auth/register', { name, email, @@ -94,7 +71,7 @@ const registerUser = async (name, email, password, password_second, cellphone) = cellphone, }); return (response.status === 200); - }catch (e) { + } catch (e) { console.error(e); return false; } @@ -102,7 +79,7 @@ const registerUser = async (name, email, password, password_second, cellphone) = const updateUser = async (id, user, token) => { try { - const response = await axios.put('http://localhost:3001/api/v1/users/' + id, user, { + const response = await axios.put(`http://localhost:3001/api/v1/users/${id}`, user, { headers: { token, } @@ -113,6 +90,33 @@ const updateUser = async (id, user, token) => { return false; } } +const findUsers = async (filters) => { + try { + const token = localStorage.getItem('token'); + const response = await axios.get('http://localhost:3001/api/v1/users/findUsers', { + headers: { + token, + }, + params: filters + }); + return response.data; + } catch (e) { + console.error(e); + return []; + } +}; + +// Nueva función para la creación de usuarios en masa +const bulkCreateUsers = async (objetos) => { + console.log("Users received:", objetos); // Verifica los datos aquí + try { + const response = await axios.post('http://localhost:3001/api/v1/users/bulkCreate', objetos); + return response.data; // Asegúrate de que la respuesta contiene 'data' + } catch (error) { + console.error("Error:", error); // Más detalle del error + return { message: 'Error al crear usuarios' }; // Asegúrate de que siempre devuelve un objeto con 'message' + } + }; export default { handleLogin, @@ -121,4 +125,6 @@ export default { logOut, registerUser, updateUser, -}; \ No newline at end of file + bulkCreateUsers, // Asegúrate de exportar la nueva función + findUsers, +};