diff --git a/frontend/src/components/Appshell.module.css b/frontend/src/components/Appshell.module.css index d9afaf2..7b2ba21 100644 --- a/frontend/src/components/Appshell.module.css +++ b/frontend/src/components/Appshell.module.css @@ -10,11 +10,19 @@ } - +.title { + font-family: + Greycliff CF, + var(--mantine-font-family); + margin-bottom: var(--mantine-spacing-sm); + background-color: var(--mantine-color-body); + padding: var(--mantine-spacing-md); + padding-top: 18px; + height: px; + border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7)); +} - .collapsed { - width: 70px; /* Ancho reducido cuando colapsa */ - } + .wrapper { display: flex; @@ -65,8 +73,6 @@ - - .link { display: block; text-decoration: none; diff --git a/frontend/src/components/Appshell_collapse.tsx b/frontend/src/components/Appshell.tsx similarity index 96% rename from frontend/src/components/Appshell_collapse.tsx rename to frontend/src/components/Appshell.tsx index 4ef5c88..a390ab8 100644 --- a/frontend/src/components/Appshell_collapse.tsx +++ b/frontend/src/components/Appshell.tsx @@ -70,7 +70,13 @@ import { ], }; - export function AppShellWithMenu() { + + type AppShellWithMenuProps = { + children?: React.ReactNode; // <- ahora es opcional + }; + + export function AppShellWithMenu({ children }: AppShellWithMenuProps) { + const location = useLocation(); const isMobile = useMediaQuery('(max-width: 768px)'); const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false); @@ -174,9 +180,10 @@ import { {/* Main Content */} - {/* Aquí va el contenido principal */} - Main Content - + {children} + + + ); } diff --git a/frontend/src/components/LlamadorAPI.tsx b/frontend/src/components/LlamadorAPI.tsx new file mode 100644 index 0000000..bbe27c9 --- /dev/null +++ b/frontend/src/components/LlamadorAPI.tsx @@ -0,0 +1,127 @@ +import { useState, useEffect } from 'react'; +import { + TextInput, + Textarea, + Button, + Box, + Center, + Stack, + Badge, + Group, +} from '@mantine/core'; +import { MetodoSelect } from './MetodoSelect'; + +export function LlamadorAPI() { + const [direccion, setDireccion] = useState('http://localhost:8000/api/saludo'); + const [metodo, setMetodo] = useState('GET'); + const [contenido, setContenido] = useState(''); + const [respuesta, setRespuesta] = useState(''); + const [codigoRespuesta, setCodigoRespuesta] = useState(null); + + const colorCodigo = (status: number): string => { + if (status >= 200 && status < 300) return 'green'; + if (status >= 300 && status < 400) return 'yellow'; + if (status >= 400 && status < 500) return 'orange'; + return 'red'; + }; + + const llamarAPI = async () => { + try { + const options: RequestInit = { + method: metodo, + mode: 'cors', + headers: {}, + }; + + if (metodo !== 'GET') { + options.headers = { + 'Content-Type': 'application/json', + }; + try { + JSON.parse(contenido); + options.body = contenido; + } catch (err) { + setRespuesta('Error: El contenido no es un JSON válido'); + setCodigoRespuesta(null); + return; + } + } + + const res = await fetch(direccion, options); + setCodigoRespuesta(res.status); + + const contentType = res.headers.get('content-type'); + + if (contentType?.includes('application/json')) { + const data = await res.json(); + const formatted = JSON.stringify(data, null, 2); + setRespuesta(formatted); + } else { + const text = await res.text(); + setRespuesta(text); + } + } catch (error: any) { + console.error('Error en la API:', error); + setRespuesta(`Error: ${error.message || error}`); + setCodigoRespuesta(null); + } + }; + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.ctrlKey && e.key === 'Enter') { + e.preventDefault(); + llamarAPI(); + } + }; + + window.addEventListener('keydown', handler); + return () => window.removeEventListener('keydown', handler); + }, [metodo, direccion, contenido]); + + return ( + +
+ + setDireccion(e.currentTarget.value)} + /> + + + +