feat: Implement AppShellWithMenu component; refactor pages to utilize new layout structure and enhance styling

This commit is contained in:
2025-05-06 12:13:08 +02:00
parent 9ab92c521a
commit 234639a34a
9 changed files with 215 additions and 191 deletions
+12 -6
View File
@@ -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;
@@ -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 */}
<AppShell.Main>
{/* Aquí va el contenido principal */}
Main Content
</AppShell.Main>
{children}
</AppShell.Main>
</AppShell>
);
}
+127
View File
@@ -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<number | null>(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 (
<Box style={{ flex: 1, padding: 40 }}>
<Center style={{ height: '100%' }}>
<Stack style={{ width: 600 }}>
<TextInput
label="Dirección"
placeholder="http://localhost:8000/api/..."
value={direccion}
onChange={(e) => setDireccion(e.currentTarget.value)}
/>
<MetodoSelect metodo={metodo} setMetodo={setMetodo} />
<Textarea
label="Contenido (JSON)"
placeholder='{"contenido": "Hola"}'
value={contenido}
onChange={(e) => setContenido(e.currentTarget.value)}
autosize
minRows={3}
/>
<Button onClick={llamarAPI} color="blue">
Enviar solicitud
</Button>
{codigoRespuesta !== null && (
<Group>
<Badge color={colorCodigo(codigoRespuesta)} size="lg">
Código: {codigoRespuesta}
</Badge>
</Group>
)}
<Textarea
label="Respuesta de la API"
value={respuesta}
readOnly
autosize
minRows={6}
/>
</Stack>
</Center>
</Box>
);
}
+35 -29
View File
@@ -2,42 +2,48 @@ import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/
import { IconArrowLeft } from '@tabler/icons-react';
import { Link } from 'react-router-dom';
import { MantineCardWithShader } from '../components/HoloShader'; // Ajusta ruta si es necesario
import { AppShellWithMenu } from '../components/Appshell';
export function Error_404() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<AppShellWithMenu>
<Box style={{
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start', // alinea arriba
padding: '2rem',
paddingTop: '0.5rem', // agrega espacio desde arriba si deseas
}}>
<Box style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '2rem' }}>
<Stack align="center" maw={500} mx="auto">
<Box style={{ flex: 1, display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '2rem' }}>
<Stack align="center" maw={500} mx="auto">
<MantineCardWithShader />
<Title order={1}>
Página no encontrada
</Title>
<Text size="lg">
Parece que la página que estás buscando no existe o fue removida. Pero no te preocupes, puedes volver al inicio fácilmente.
</Text>
<MantineCardWithShader />
<Title order={1}>
Página no encontrada
</Title>
<Text size="lg">
Parece que la página que estás buscando no existe o fue removida. Pero no te preocupes, puedes volver al inicio fácilmente.
</Text>
<Group mt="md">
<Button
component={Link}
to="/"
size="md"
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
leftSection={<IconArrowLeft size={18} />}
>
Volver al inicio
</Button>
</Group>
</Stack>
<Group mt="md">
<Button
component={Link}
to="/"
size="md"
variant="gradient"
gradient={{ from: 'blue', to: 'cyan' }}
leftSection={<IconArrowLeft size={18} />}
>
Volver al inicio
</Button>
</Group>
</Stack>
</Box>
</Box>
</Box>
</AppShellWithMenu>
);
}
+3 -8
View File
@@ -2,19 +2,14 @@ import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeTo
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { DoubleNavbar } from '../components/DoubleNavbar';
import { AppShellWithMenu } from '../components/Appshell';
export function Prueba_1() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<DoubleNavbar /> {/* Sidebar fijo a la izquierda */}
<AppShellWithMenu>
{/* Contenido principal */}
</Box>
</AppShellWithMenu>
);
}
+7 -132
View File
@@ -1,137 +1,12 @@
import { useState } from 'react';
import {
TextInput,
Textarea,
Button,
Box,
Center,
Stack,
Badge,
Group,
} from '@mantine/core';
import { MetodoSelect } from '../components/MetodoSelect'; // 👈 Importación del nuevo componente
import { useEffect } from 'react';
import { Box } from '@mantine/core';
import { LlamadorAPI } from '../components/LlamadorAPI';
import { AppShellWithMenu } from '../components/Appshell';
export function Consulta_API() {
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<number | null>(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 (
<Box style={{ display: 'flex', height: '100vh' }}>
<Box style={{ flex: 1, padding: 40 }}>
<Center style={{ height: '100%' }}>
<Stack style={{ width: 600 }}>
<TextInput
label="Dirección"
placeholder="http://localhost:8000/api/..."
value={direccion}
onChange={(e) => setDireccion(e.currentTarget.value)}
/>
<MetodoSelect metodo={metodo} setMetodo={setMetodo} />
<Textarea
label="Contenido (JSON)"
placeholder='{"contenido": "Hola"}'
value={contenido}
onChange={(e) => setContenido(e.currentTarget.value)}
autosize
minRows={3}
/>
<Button onClick={llamarAPI} color="blue">
Enviar solicitud
</Button>
{codigoRespuesta !== null && (
<Group>
<Badge color={colorCodigo(codigoRespuesta)} size="lg">
Código: {codigoRespuesta}
</Badge>
</Group>
)}
<Textarea
label="Respuesta de la API"
value={respuesta}
readOnly
autosize
minRows={6}
/>
</Stack>
</Center>
</Box>
</Box>
<AppShellWithMenu>
<LlamadorAPI />
</AppShellWithMenu>
);
}
}
+3 -9
View File
@@ -1,16 +1,10 @@
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { MantineCardWithShader } from '../components/HoloShader';
import { AppShellWithMenu } from '../components/Appshell';
export function HomePage() {
return (
<Box style={{ display: 'flex', height: '100vh' }}>
<AppShellWithMenu>
</Box>
</AppShellWithMenu>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { AppShellWithMenu } from '../components/Appshell';
export function Plantilla() {
return (
<AppShellWithMenu>
</AppShellWithMenu>
);
}
+7 -3
View File
@@ -3,11 +3,15 @@ import { Welcome } from '../components/Welcome/Welcome';
import MiBoton from '../components/botoncito';
import { Center, Box } from '@mantine/core';
import { MantineCardWithShader } from '../components/HoloShader';
import {AppShellWithMenu } from '../components/Appshell_collapse';
import { AppShellWithMenu } from '../components/Appshell';
export function Prueba_appshell() {
return (
<AppShellWithMenu />
<AppShellWithMenu>
</AppShellWithMenu>
);
}