From 1273d69abcc62293763f3aedbe190c05ff65677d Mon Sep 17 00:00:00 2001 From: Sergio Barnachea Date: Wed, 25 Feb 2026 09:29:17 -0300 Subject: [PATCH 1/8] =?UTF-8?q?ADD=20archivos=20para=20conexi=C3=B3n=20a?= =?UTF-8?q?=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agenda-contactos/src/api/userService.ts | 36 +++++++++++++++++++++++ agenda-contactos/src/utils/request.ts | 39 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 agenda-contactos/src/api/userService.ts create mode 100644 agenda-contactos/src/utils/request.ts diff --git a/agenda-contactos/src/api/userService.ts b/agenda-contactos/src/api/userService.ts new file mode 100644 index 0000000..a22186b --- /dev/null +++ b/agenda-contactos/src/api/userService.ts @@ -0,0 +1,36 @@ +import request from '../utils/request'; +import { CreateUserDto } from '../types'; + +/** + * Obtiene lista paginada de usuarios, con búsqueda opcional + */ +export const getUsers = (page: number, limit: number, search?: string) => + request({ + method: 'GET', + url: 'users', + params: { + _page: page, + _limit: limit, + ...(search ? { q: search } : {}), + }, + }); + +/** + * Crea un nuevo usuario + */ +export const createUser = (data: CreateUserDto) => + request({ + method: 'POST', + url: 'users', + data, + headers: { 'Content-Type': 'application/json' }, + }); + +/** + * Elimina un usuario por id + */ +export const deleteUser = (id: number) => + request({ + method: 'DELETE', + url: `users/${id}`, + }); diff --git a/agenda-contactos/src/utils/request.ts b/agenda-contactos/src/utils/request.ts new file mode 100644 index 0000000..c444a4c --- /dev/null +++ b/agenda-contactos/src/utils/request.ts @@ -0,0 +1,39 @@ +import axios, { AxiosRequestConfig } from 'axios'; + +const client = axios.create({ + baseURL: process.env.REACT_APP_API_URL, +}); + +client.interceptors.response.use( + (response) => response, + (error) => Promise.reject(error.response || error.message), +); + +interface Options { + method: string; + url: string; + data?: any; + params?: object; + headers?: object; +} + +const request = async (options: Options) => { + const config: AxiosRequestConfig = { + method: options.method, + url: `/api/${options.url}`, + data: options.data, + params: options.params, + headers: options.headers, + }; + + const onSuccess = (response: any) => response; + + const onError = (error: any) => { + console.error('Error:', error?.status, error?.config?.url); + return Promise.reject(error?.data || error?.message || 'Error desconocido'); + }; + + return client(config).then(onSuccess).catch(onError); +}; + +export default request; From e6dd7a468b80884492413832d4a501fc32c53e49 Mon Sep 17 00:00:00 2001 From: Sergio Barnachea Date: Wed, 25 Feb 2026 09:30:19 -0300 Subject: [PATCH 2/8] ADD componentes para armar pagina principal --- .../src/components/ContactDrawer.tsx | 74 ++++++++++++ .../src/components/ContactList.css | 9 ++ .../src/components/ContactList.tsx | 108 ++++++++++++++++++ agenda-contactos/src/components/SearchBar.tsx | 48 ++++++++ 4 files changed, 239 insertions(+) create mode 100644 agenda-contactos/src/components/ContactDrawer.tsx create mode 100644 agenda-contactos/src/components/ContactList.css create mode 100644 agenda-contactos/src/components/ContactList.tsx create mode 100644 agenda-contactos/src/components/SearchBar.tsx diff --git a/agenda-contactos/src/components/ContactDrawer.tsx b/agenda-contactos/src/components/ContactDrawer.tsx new file mode 100644 index 0000000..f39ccf6 --- /dev/null +++ b/agenda-contactos/src/components/ContactDrawer.tsx @@ -0,0 +1,74 @@ +import { Drawer, Form, Input, Button, Space } from 'antd'; +import useUsers from '../hooks/useUsers'; +import { CreateUserDto } from '../types'; + +/** + * Drawer lateral para agregar un nuevo contacto. + */ +const ContactDrawer = () => { + const { drawerOpen, setDrawerOpen, addUser, loading } = useUsers(); + const [form] = Form.useForm(); + + const handleClose = () => { + form.resetFields(); + setDrawerOpen(false); + }; + + const handleSubmit = async (values: CreateUserDto) => { + await addUser(values); + form.resetFields(); + }; + + return ( + + + + + } + > +
+ + + + + + + + + + + +
+
+ ); +}; + +export default ContactDrawer; diff --git a/agenda-contactos/src/components/ContactList.css b/agenda-contactos/src/components/ContactList.css new file mode 100644 index 0000000..73491c2 --- /dev/null +++ b/agenda-contactos/src/components/ContactList.css @@ -0,0 +1,9 @@ +.contact-name { + display: flex; + align-items: center; + gap: 8px; +} + +.contact-name__link { + color: #1677ff; +} diff --git a/agenda-contactos/src/components/ContactList.tsx b/agenda-contactos/src/components/ContactList.tsx new file mode 100644 index 0000000..1bb9031 --- /dev/null +++ b/agenda-contactos/src/components/ContactList.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState } from 'react'; +import { Table, Avatar, Button, Popconfirm } from 'antd'; +import { DeleteOutlined, UserOutlined } from '@ant-design/icons'; +import { User } from '../types'; +import useUsers from '../hooks/useUsers'; +import SearchBar from './SearchBar'; +import './ContactList.css'; + +/** + * Lista paginada de contactos con buscador y opción de eliminar + */ +const ContactList = () => { + const { + users, + total, + loading, + fetchUsers, + removeUser, + PAGE_LIMIT, + searchText, + setSearchText, + } = useUsers(); + + const [currentPage, setCurrentPage] = useState(1); + + useEffect(() => { + fetchUsers(1); + }, [fetchUsers]); + + const handleSearch = (value: string) => { + setSearchText(value); + setCurrentPage(1); + fetchUsers(1, value); + }; + + const handleClear = () => { + setSearchText(''); + setCurrentPage(1); + fetchUsers(1, ''); + }; + + const handlePageChange = (page: number) => { + setCurrentPage(page); + fetchUsers(page, searchText); + }; + + const columns = [ + { + title: 'Nombre', + dataIndex: 'name', + key: 'name', + width: 200, + render: (name: string, record: User) => ( +
+ } size={32} /> + {name} +
+ ), + }, + { + title: 'Descripción', + dataIndex: 'description', + key: 'description', + }, + { + title: 'Acciones', + key: 'actions', + width: 100, + align: 'center' as const, + render: (_: any, record: User) => ( + removeUser(record.id)} + okText='Sí' + cancelText='No' + > +