Feat: Estetico
- Se aplican estilos al resto de controles por uniformidad.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
// EN: src/components/AdminPanel.tsx
|
||||
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 SmartToyIcon from '@mui/icons-material/SmartToy';
|
||||
|
||||
import ContextManager from './ContextManager';
|
||||
import LogsViewer from './LogsViewer';
|
||||
import SourceManager from './SourceManager';
|
||||
|
||||
import SystemPromptManager from './SystemPromptManager';
|
||||
|
||||
interface AdminPanelProps {
|
||||
@@ -21,28 +21,57 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ onLogout }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
||||
Panel de Administración del Chatbot
|
||||
</Typography>
|
||||
<IconButton color="inherit" onClick={onLogout} aria-label="Cerrar sesión">
|
||||
<LogoutIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
<Tabs value={currentTab} onChange={handleTabChange} textColor="inherit" indicatorColor="secondary">
|
||||
<Tab label="Gestor de Contexto" />
|
||||
<Tab label="Historial de Conversaciones" />
|
||||
<Tab label="Gestor de Fuentes" />
|
||||
<Tab label="Gestor de Prompts" />
|
||||
</Tabs>
|
||||
<Box sx={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<AppBar
|
||||
position="sticky"
|
||||
elevation={0}
|
||||
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>
|
||||
<IconButton onClick={onLogout} color="error" title="Cerrar sesión">
|
||||
<LogoutIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={handleTabChange}
|
||||
textColor="inherit"
|
||||
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>
|
||||
</Container>
|
||||
</AppBar>
|
||||
|
||||
{currentTab === 0 && <ContextManager onAuthError={onLogout} />}
|
||||
{currentTab === 1 && <LogsViewer onAuthError={onLogout} />}
|
||||
{currentTab === 2 && <SourceManager onAuthError={onLogout} />}
|
||||
{currentTab === 3 && <SystemPromptManager onAuthError={onLogout} />}
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Container maxWidth="xl">
|
||||
{currentTab === 0 && <ContextManager onAuthError={onLogout} />}
|
||||
{currentTab === 1 && <LogsViewer onAuthError={onLogout} />}
|
||||
{currentTab === 2 && <SourceManager onAuthError={onLogout} />}
|
||||
{currentTab === 3 && <SystemPromptManager onAuthError={onLogout} />}
|
||||
</Container>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// src/components/AdminPanel.tsx
|
||||
// src/components/ContextManager.tsx
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
import { DataGrid, GridActionsCellItem } 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 DeleteIcon from '@mui/icons-material/Delete';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@@ -122,90 +122,99 @@ const ContextManager: React.FC<ContextManagerProps> = ({ onAuthError }) => {
|
||||
icon={<EditIcon />}
|
||||
label="Editar"
|
||||
onClick={() => handleOpen(params.row as ContextoItem)}
|
||||
color="inherit"
|
||||
/>,
|
||||
<GridActionsCellItem
|
||||
icon={<DeleteIcon />}
|
||||
label="Eliminar"
|
||||
// Llama a la función que abre el diálogo
|
||||
onClick={() => handleDeleteClick(params.id as number)}
|
||||
color="inherit"
|
||||
/>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 4 }}>
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 1 }}>
|
||||
Añadir Nuevo Item
|
||||
</Button>
|
||||
<Box sx={{ height: 600, width: '100%' }}>
|
||||
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50, 100]} />
|
||||
</Box>
|
||||
<Box sx={{ p: 4 }}>
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 1 }}>
|
||||
Añadir Nuevo Item
|
||||
</Button>
|
||||
<Box sx={{ height: 600, width: '100%' }}>
|
||||
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50, 100]} />
|
||||
</Box>
|
||||
|
||||
{/* --- DIÁLOGO DE EDICIÓN/CREACIÓN --- */}
|
||||
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>{isEdit ? 'Editar Item' : 'Añadir Nuevo Item'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Clave"
|
||||
fullWidth
|
||||
value={currentRow.clave || ''}
|
||||
disabled={isEdit} // La clave no se puede editar una vez creada
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, clave: e.target.value })}
|
||||
helperText={!isEdit ? "Esta clave no se podrá modificar en el futuro." : ""}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Valor"
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
value={currentRow.valor || ''}
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, valor: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Descripción"
|
||||
fullWidth
|
||||
value={currentRow.descripcion || ''}
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, descripcion: e.target.value })}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancelar</Button>
|
||||
<Button onClick={handleSave} variant="contained">Guardar</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* --- DIÁLOGO DE CONFIRMACIÓN DE BORRADO --- */}
|
||||
<Dialog
|
||||
open={confirmOpen}
|
||||
onClose={handleConfirmClose}
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||
<Typography variant="h4">Gestor de Contexto</Typography>
|
||||
<Button
|
||||
startIcon={<AddIcon />}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => handleOpen()}
|
||||
>
|
||||
<DialogTitle>Confirmar Eliminación</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
¿Estás seguro de que quieres eliminar este item? Esta acción no se puede deshacer.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleConfirmClose}>Cancelar</Button>
|
||||
<Button onClick={handleConfirmDelete} color="error" variant="contained">
|
||||
Eliminar
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
Nuevo Item
|
||||
</Button>
|
||||
</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, 100]}
|
||||
sx={{ border: 'none' }}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{/* --- DIÁLOGO DE EDICIÓN/CREACIÓN --- */}
|
||||
<Dialog open={open} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>{isEdit ? 'Editar Item' : 'Añadir Nuevo Item'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Clave"
|
||||
fullWidth
|
||||
value={currentRow.clave || ''}
|
||||
disabled={isEdit} // La clave no se puede editar una vez creada
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, clave: e.target.value })}
|
||||
helperText={!isEdit ? "Esta clave no se podrá modificar en el futuro." : ""}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Valor"
|
||||
fullWidth
|
||||
multiline
|
||||
rows={4}
|
||||
value={currentRow.valor || ''}
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, valor: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Descripción"
|
||||
fullWidth
|
||||
value={currentRow.descripcion || ''}
|
||||
onChange={(e) => setCurrentRow({ ...currentRow, descripcion: e.target.value })}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancelar</Button>
|
||||
<Button onClick={handleSave} variant="contained">Guardar</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* --- DIÁLOGO DE CONFIRMACIÓN DE BORRADO --- */}
|
||||
<Dialog
|
||||
open={confirmOpen}
|
||||
onClose={handleConfirmClose}
|
||||
>
|
||||
<DialogTitle>Confirmar Eliminación</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
¿Estás seguro de que quieres eliminar este item? Esta acción no se puede deshacer.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleConfirmClose}>Cancelar</Button>
|
||||
<Button onClick={handleConfirmDelete} color="error" variant="contained">
|
||||
Eliminar
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// src/components/Login.tsx
|
||||
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 {
|
||||
onLoginSuccess: (token: string) => void;
|
||||
@@ -10,70 +11,94 @@ const Login: React.FC<LoginProps> = ({ onLoginSuccess }) => {
|
||||
const [username, setUsername] = React.useState('');
|
||||
const [password, setPassword] = React.useState('');
|
||||
const [error, setError] = React.useState('');
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const handleLogin = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
setError('');
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
// Usamos la variable de entorno para la URL de la API
|
||||
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password }),
|
||||
});
|
||||
// Using apiClient ensures consistently handled headers/configs
|
||||
const response = await apiClient.post('/api/auth/login', { username, password });
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
onLoginSuccess(data.token);
|
||||
// Axios returns data directly in response.data
|
||||
if (response.data && response.data.token) {
|
||||
onLoginSuccess(response.data.token);
|
||||
} else {
|
||||
setError('Usuario o contraseña incorrectos.');
|
||||
setError('Respuesta inesperada del servidor.');
|
||||
}
|
||||
} catch (err) {
|
||||
setError('No se pudo conectar con el servidor. Inténtalo de nuevo más tarde.');
|
||||
} catch (err: any) {
|
||||
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 (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
||||
<Paper elevation={6} sx={{ padding: 4, width: '100%', maxWidth: 400 }}>
|
||||
<Typography variant="h4" component="h1" gutterBottom textAlign="center">
|
||||
Iniciar Sesión
|
||||
</Typography>
|
||||
<Typography variant="body2" textAlign="center" sx={{ mb: 3 }}>
|
||||
Gestor de Contexto del Chatbot
|
||||
</Typography>
|
||||
<form onSubmit={handleLogin}>
|
||||
<TextField
|
||||
label="Usuario"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<TextField
|
||||
label="Contraseña"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
{error && <Alert severity="error" sx={{ mt: 2 }}>{error}</Alert>}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="large"
|
||||
sx={{ mt: 3 }}
|
||||
>
|
||||
Entrar
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
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 variant="body2" color="text.secondary">
|
||||
Inicia sesión para gestionar el Chatbot
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
||||
<TextField
|
||||
label="Usuario"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
autoFocus
|
||||
sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}
|
||||
/>
|
||||
<TextField
|
||||
label="Contraseña"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
sx={{ bgcolor: 'rgba(255,255,255,0.05)' }}
|
||||
/>
|
||||
|
||||
{error && <Alert severity="error">{error}</Alert>}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="large"
|
||||
disabled={loading}
|
||||
sx={{
|
||||
mt: 1,
|
||||
py: 1.5,
|
||||
background: 'linear-gradient(45deg, #7c4dff 30%, #ff4081 90%)',
|
||||
fontSize: '1rem'
|
||||
}}
|
||||
>
|
||||
{loading ? 'Entrando...' : 'Iniciar Sesión'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
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';
|
||||
|
||||
interface ConversacionLog {
|
||||
@@ -66,23 +66,28 @@ const LogsViewer: React.FC<LogsViewerProps> = ({ onAuthError }) => {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 4 }}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Historial de Conversaciones
|
||||
</Typography>
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
<Box sx={{ height: 700, width: '100%' }}>
|
||||
<DataGrid
|
||||
rows={logs}
|
||||
columns={columns}
|
||||
pageSizeOptions={[25, 50, 100]}
|
||||
initialState={{
|
||||
sorting: {
|
||||
sortModel: [{ field: 'fecha', sort: 'desc' }],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4">Historial de Conversaciones</Typography>
|
||||
</Box>
|
||||
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
|
||||
<Card>
|
||||
<Box sx={{ height: 700, width: '100%' }}>
|
||||
<DataGrid
|
||||
rows={logs}
|
||||
columns={columns}
|
||||
pageSizeOptions={[25, 50, 100]}
|
||||
initialState={{
|
||||
sorting: {
|
||||
sortModel: [{ field: 'fecha', sort: 'desc' }],
|
||||
},
|
||||
}}
|
||||
sx={{ border: 'none' }}
|
||||
/>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
||||
import axios from 'axios';
|
||||
import { DataGrid, GridActionsCellItem } 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 DeleteIcon from '@mui/icons-material/Delete';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
@@ -109,7 +109,12 @@ const SourceManager: React.FC<SourceManagerProps> = ({ onAuthError }) => {
|
||||
headerName: 'Activo',
|
||||
width: 100,
|
||||
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 />}
|
||||
label="Editar"
|
||||
onClick={() => handleOpen(params.row as FuenteContexto)}
|
||||
color="inherit"
|
||||
/>,
|
||||
<GridActionsCellItem
|
||||
icon={<DeleteIcon />}
|
||||
label="Eliminar"
|
||||
onClick={() => handleDeleteClick(params.id as number)}
|
||||
color="inherit" // Keeping it generic to avoid type errors
|
||||
/>,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 4 }}>
|
||||
{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
|
||||
<Button startIcon={<AddIcon />} variant="contained" onClick={() => handleOpen()} sx={{ mb: 2 }}>
|
||||
Añadir Nueva Fuente
|
||||
</Button>
|
||||
<Box sx={{ height: 600, width: '100%' }}>
|
||||
<DataGrid rows={rows} columns={columns} pageSizeOptions={[10, 25, 50]} />
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||
<Typography variant="h4">Gestor de Fuentes</Typography>
|
||||
<Button
|
||||
startIcon={<AddIcon />}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => handleOpen()}
|
||||
>
|
||||
Nueva Fuente
|
||||
</Button>
|
||||
</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">
|
||||
<DialogTitle>{isEdit ? 'Editar Fuente' : 'Añadir Nueva Fuente'}</DialogTitle>
|
||||
<DialogContent>
|
||||
@@ -186,6 +211,7 @@ const SourceManager: React.FC<SourceManagerProps> = ({ onAuthError }) => {
|
||||
/>
|
||||
}
|
||||
label="Fuente activa"
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
Reference in New Issue
Block a user