Feat: Estetico

- Se aplican estilos al resto de controles por uniformidad.
This commit is contained in:
2025-12-05 13:21:24 -03:00
parent 5cef67a2bf
commit 7e9e3ba87e
5 changed files with 267 additions and 173 deletions

View File

@@ -1,12 +1,12 @@
// EN: src/components/AdminPanel.tsx // EN: src/components/AdminPanel.tsx
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Box, Typography, AppBar, Toolbar, IconButton, Tabs, Tab } from '@mui/material'; import { Box, Typography, AppBar, Toolbar, IconButton, Tabs, Tab, Container } from '@mui/material';
import LogoutIcon from '@mui/icons-material/Logout'; import LogoutIcon from '@mui/icons-material/Logout';
import SmartToyIcon from '@mui/icons-material/SmartToy';
import ContextManager from './ContextManager'; import ContextManager from './ContextManager';
import LogsViewer from './LogsViewer'; import LogsViewer from './LogsViewer';
import SourceManager from './SourceManager'; import SourceManager from './SourceManager';
import SystemPromptManager from './SystemPromptManager'; import SystemPromptManager from './SystemPromptManager';
interface AdminPanelProps { interface AdminPanelProps {
@@ -21,28 +21,57 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ onLogout }) => {
}; };
return ( return (
<Box> <Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
<AppBar position="static"> <AppBar
<Toolbar> position="sticky"
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> elevation={0}
Panel de Administración del Chatbot sx={{
background: 'rgba(18, 18, 18, 0.8)',
backdropFilter: 'blur(12px)',
borderBottom: '1px solid rgba(255,255,255,0.08)'
}}
>
<Container maxWidth="xl">
<Toolbar disableGutters>
<SmartToyIcon sx={{ mr: 2, color: 'primary.main' }} />
<Typography
variant="h6"
component="div"
sx={{
flexGrow: 1,
fontWeight: 700,
letterSpacing: '-0.5px'
}}
>
Chatbot Admin
</Typography> </Typography>
<IconButton color="inherit" onClick={onLogout} aria-label="Cerrar sesión"> <IconButton onClick={onLogout} color="error" title="Cerrar sesión">
<LogoutIcon /> <LogoutIcon />
</IconButton> </IconButton>
</Toolbar> </Toolbar>
<Tabs value={currentTab} onChange={handleTabChange} textColor="inherit" indicatorColor="secondary"> <Tabs
<Tab label="Gestor de Contexto" /> value={currentTab}
<Tab label="Historial de Conversaciones" /> onChange={handleTabChange}
<Tab label="Gestor de Fuentes" /> textColor="inherit"
<Tab label="Gestor de Prompts" /> indicatorColor="secondary"
sx={{ minHeight: '48px' }}
>
<Tab label="Contexto" sx={{ fontWeight: 600 }} />
<Tab label="Historial" sx={{ fontWeight: 600 }} />
<Tab label="Fuentes" sx={{ fontWeight: 600 }} />
<Tab label="Prompts / IA" sx={{ fontWeight: 600 }} />
</Tabs> </Tabs>
</Container>
</AppBar> </AppBar>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Container maxWidth="xl">
{currentTab === 0 && <ContextManager onAuthError={onLogout} />} {currentTab === 0 && <ContextManager onAuthError={onLogout} />}
{currentTab === 1 && <LogsViewer onAuthError={onLogout} />} {currentTab === 1 && <LogsViewer onAuthError={onLogout} />}
{currentTab === 2 && <SourceManager onAuthError={onLogout} />} {currentTab === 2 && <SourceManager onAuthError={onLogout} />}
{currentTab === 3 && <SystemPromptManager onAuthError={onLogout} />} {currentTab === 3 && <SystemPromptManager onAuthError={onLogout} />}
</Container>
</Box>
</Box> </Box>
); );
}; };

View File

@@ -1,9 +1,9 @@
// src/components/AdminPanel.tsx // src/components/ContextManager.tsx
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; import axios from 'axios';
import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid'; import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid';
import type { GridColDef } from '@mui/x-data-grid'; import type { GridColDef } from '@mui/x-data-grid';
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Alert, DialogContentText, TextField } from '@mui/material'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Alert, DialogContentText, TextField, Card, Typography } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
@@ -122,34 +122,44 @@ const ContextManager: React.FC<ContextManagerProps> = ({ onAuthError }) => {
icon={<EditIcon />} icon={<EditIcon />}
label="Editar" label="Editar"
onClick={() => handleOpen(params.row as ContextoItem)} onClick={() => handleOpen(params.row as ContextoItem)}
color="inherit"
/>, />,
<GridActionsCellItem <GridActionsCellItem
icon={<DeleteIcon />} icon={<DeleteIcon />}
label="Eliminar" label="Eliminar"
// Llama a la función que abre el diálogo
onClick={() => handleDeleteClick(params.id as number)} onClick={() => handleDeleteClick(params.id as number)}
color="inherit"
/>, />,
], ],
}, },
]; ];
return ( return (
<Box sx={{ p: 4 }}> <Box>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>} <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 1 }}> <Typography variant="h4">Gestor de Contexto</Typography>
Añadir Nuevo Item <Button
startIcon={<AddIcon />}
variant="contained"
color="secondary"
onClick={() => handleOpen()}
>
Nuevo Item
</Button> </Button>
<Box sx={{ height: 600, width: '100%' }}>
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50, 100]} />
</Box> </Box>
<Box sx={{ p: 4 }}>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>} {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 1 }}>
Añadir Nuevo Item <Card>
</Button>
<Box sx={{ height: 600, width: '100%' }}> <Box sx={{ height: 600, width: '100%' }}>
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50, 100]} /> <DataGrid
rows={rows}
columns={columns}
pageSizeOptions={[10, 25, 50, 100]}
sx={{ border: 'none' }}
/>
</Box> </Box>
</Card>
{/* --- DIÁLOGO DE EDICIÓN/CREACIÓN --- */} {/* --- DIÁLOGO DE EDICIÓN/CREACIÓN --- */}
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm"> <Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
@@ -206,7 +216,6 @@ const ContextManager: React.FC<ContextManagerProps> = ({ onAuthError }) => {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</Box> </Box>
</Box>
); );
}; };

View File

@@ -1,6 +1,7 @@
// src/components/Login.tsx // src/components/Login.tsx
import React from 'react'; import React from 'react';
import { Box, Button, TextField, Typography, Paper, Alert } from '@mui/material'; import { Box, Button, TextField, Typography, Card, CardContent, Alert } from '@mui/material';
import apiClient from '../api/apiClient';
interface LoginProps { interface LoginProps {
onLoginSuccess: (token: string) => void; onLoginSuccess: (token: string) => void;
@@ -10,70 +11,94 @@ const Login: React.FC<LoginProps> = ({ onLoginSuccess }) => {
const [username, setUsername] = React.useState(''); const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState(''); const [password, setPassword] = React.useState('');
const [error, setError] = React.useState(''); const [error, setError] = React.useState('');
const [loading, setLoading] = React.useState(false);
const handleLogin = async (event: React.FormEvent) => { const handleLogin = async (event: React.FormEvent) => {
event.preventDefault(); event.preventDefault();
setError(''); setError('');
setLoading(true);
try { try {
// Usamos la variable de entorno para la URL de la API // Using apiClient ensures consistently handled headers/configs
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/auth/login`, { const response = await apiClient.post('/api/auth/login', { username, password });
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password }),
});
if (response.ok) { // Axios returns data directly in response.data
const data = await response.json(); if (response.data && response.data.token) {
onLoginSuccess(data.token); onLoginSuccess(response.data.token);
} else { } else {
setError('Usuario o contraseña incorrectos.'); setError('Respuesta inesperada del servidor.');
} }
} catch (err) { } catch (err: any) {
setError('No se pudo conectar con el servidor. Inténtalo de nuevo más tarde.'); console.error(err);
if (err.response && err.response.status === 401) {
setError('Usuario o contraseña incorrectos.');
} else {
setError('No se pudo conectar con el servidor.');
}
} finally {
setLoading(false);
} }
}; };
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}> <Box sx={{
<Paper elevation={6} sx={{ padding: 4, width: '100%', maxWidth: 400 }}> display: 'flex',
<Typography variant="h4" component="h1" gutterBottom textAlign="center"> justifyContent: 'center',
Iniciar Sesión alignItems: 'center',
height: '100vh',
background: 'radial-gradient(circle at 50% 50%, #1a1a1a 0%, #000 100%)' // Fallback/Enhancement
}}>
<Card sx={{ maxWidth: 450, width: '100%', mx: 2 }}>
<CardContent sx={{ p: 4, display: 'flex', flexDirection: 'column', gap: 2 }}>
<Box sx={{ textAlign: 'center', mb: 1 }}>
<Typography variant="h4" gutterBottom>
Bienvenido
</Typography> </Typography>
<Typography variant="body2" textAlign="center" sx={{ mb: 3 }}> <Typography variant="body2" color="text.secondary">
Gestor de Contexto del Chatbot Inicia sesión para gestionar el Chatbot
</Typography> </Typography>
<form onSubmit={handleLogin}> </Box>
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<TextField <TextField
label="Usuario" label="Usuario"
variant="outlined" variant="outlined"
fullWidth fullWidth
margin="normal"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
autoFocus autoFocus
sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}
/> />
<TextField <TextField
label="Contraseña" label="Contraseña"
type="password" type="password"
variant="outlined" variant="outlined"
fullWidth fullWidth
margin="normal"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}
/> />
{error && <Alert severity="error" sx={{ mt: 2 }}>{error}</Alert>}
{error && <Alert severity="error">{error}</Alert>}
<Button <Button
type="submit" type="submit"
variant="contained" variant="contained"
fullWidth fullWidth
size="large" size="large"
sx={{ mt: 3 }} disabled={loading}
sx={{
mt: 1,
py: 1.5,
background: 'linear-gradient(45deg, #7c4dff 30%, #ff4081 90%)',
fontSize: '1rem'
}}
> >
Entrar {loading ? 'Entrando...' : 'Iniciar Sesión'}
</Button> </Button>
</form> </form>
</Paper> </CardContent>
</Card>
</Box> </Box>
); );
}; };

View File

@@ -2,7 +2,7 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; import axios from 'axios';
import { DataGrid, type GridColDef } from '@mui/x-data-grid'; import { DataGrid, type GridColDef } from '@mui/x-data-grid';
import { Box, Typography, Alert } from '@mui/material'; import { Box, Typography, Alert, Card } from '@mui/material';
import apiClient from '../api/apiClient'; import apiClient from '../api/apiClient';
interface ConversacionLog { interface ConversacionLog {
@@ -66,11 +66,14 @@ const LogsViewer: React.FC<LogsViewerProps> = ({ onAuthError }) => {
]; ];
return ( return (
<Box sx={{ p: 4 }}> <Box>
<Typography variant="h4" gutterBottom> <Box sx={{ mb: 3 }}>
Historial de Conversaciones <Typography variant="h4">Historial de Conversaciones</Typography>
</Typography> </Box>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>} {error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
<Card>
<Box sx={{ height: 700, width: '100%' }}> <Box sx={{ height: 700, width: '100%' }}>
<DataGrid <DataGrid
rows={logs} rows={logs}
@@ -81,8 +84,10 @@ const LogsViewer: React.FC<LogsViewerProps> = ({ onAuthError }) => {
sortModel: [{ field: 'fecha', sort: 'desc' }], sortModel: [{ field: 'fecha', sort: 'desc' }],
}, },
}} }}
sx={{ border: 'none' }}
/> />
</Box> </Box>
</Card>
</Box> </Box>
); );
}; };

View File

@@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import axios from 'axios'; import axios from 'axios';
import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid'; import { DataGrid, GridActionsCellItem } from '@mui/x-data-grid';
import type { GridColDef } from '@mui/x-data-grid'; import type { GridColDef } from '@mui/x-data-grid';
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Alert, TextField, Chip, Switch, FormControlLabel } from '@mui/material'; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Alert, TextField, Chip, Switch, FormControlLabel, Card, Typography } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
@@ -109,7 +109,12 @@ const SourceManager: React.FC<SourceManagerProps> = ({ onAuthError }) => {
headerName: 'Activo', headerName: 'Activo',
width: 100, width: 100,
renderCell: (params) => ( renderCell: (params) => (
<Chip label={params.value ? 'Sí' : 'No'} color={params.value ? 'success' : 'default'} /> <Chip
label={params.value ? 'Sí' : 'No'}
color={params.value ? 'success' : 'default'}
variant={params.value ? 'filled' : 'outlined'}
size="small"
/>
), ),
}, },
{ {
@@ -121,25 +126,45 @@ const SourceManager: React.FC<SourceManagerProps> = ({ onAuthError }) => {
icon={<EditIcon />} icon={<EditIcon />}
label="Editar" label="Editar"
onClick={() => handleOpen(params.row as FuenteContexto)} onClick={() => handleOpen(params.row as FuenteContexto)}
color="inherit"
/>, />,
<GridActionsCellItem <GridActionsCellItem
icon={<DeleteIcon />} icon={<DeleteIcon />}
label="Eliminar" label="Eliminar"
onClick={() => handleDeleteClick(params.id as number)} onClick={() => handleDeleteClick(params.id as number)}
color="inherit" // Keeping it generic to avoid type errors
/>, />,
], ],
}, },
]; ];
return ( return (
<Box sx={{ p: 4 }}> <Box>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>} <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 2 }}> <Typography variant="h4">Gestor de Fuentes</Typography>
Añadir Nueva Fuente <Button
startIcon={<AddIcon />}
variant="contained"
color="secondary"
onClick={() => handleOpen()}
>
Nueva Fuente
</Button> </Button>
<Box sx={{ height: 600, width: '100%' }}>
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50]} />
</Box> </Box>
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
<Card>
<Box sx={{ height: 600, width: '100%' }}>
<DataGrid
rows={rows}
columns={columns}
pageSizeOptions={[10, 25, 50]}
sx={{ border: 'none' }}
/>
</Box>
</Card>
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="md"> <Dialog open={open} onClose={handleClose} fullWidth maxWidth="md">
<DialogTitle>{isEdit ? 'Editar Fuente' : 'Añadir Nueva Fuente'}</DialogTitle> <DialogTitle>{isEdit ? 'Editar Fuente' : 'Añadir Nueva Fuente'}</DialogTitle>
<DialogContent> <DialogContent>
@@ -186,6 +211,7 @@ const SourceManager: React.FC<SourceManagerProps> = ({ onAuthError }) => {
/> />
} }
label="Fuente activa" label="Fuente activa"
sx={{ mt: 2 }}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>