Fase 5: Implementada la configuración dinámica. Implementado el scraping web.
This commit is contained in:
@@ -1,31 +1,82 @@
|
||||
// frontend/src/components/FormularioConfiguracion.tsx
|
||||
|
||||
import { Box, TextField, Button, Paper, Typography } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Box, TextField, Button, Paper, Typography, CircularProgress } from '@mui/material';
|
||||
import type { Configuracion } from '../types';
|
||||
import * as api from '../services/apiService';
|
||||
|
||||
const FormularioConfiguracion = () => {
|
||||
const [config, setConfig] = useState<Configuracion | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
api.obtenerConfiguracion()
|
||||
.then(data => {
|
||||
setConfig(data);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => console.error("Error al cargar configuración", err));
|
||||
}, []);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!config) return;
|
||||
const { name, value } = event.target;
|
||||
setConfig({
|
||||
...config,
|
||||
[name]: name === 'rutaCsv' ? value : Number(value) // Convertir a número si no es la ruta
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (!config) return;
|
||||
|
||||
setSaving(true);
|
||||
setSuccess(false);
|
||||
try {
|
||||
await api.guardarConfiguracion(config);
|
||||
setSuccess(true);
|
||||
setTimeout(() => setSuccess(false), 2000); // El mensaje de éxito desaparece después de 2s
|
||||
} catch (err) {
|
||||
console.error("Error al guardar configuración", err);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
return <Typography color="error">No se pudo cargar la configuración.</Typography>
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper elevation={3} sx={{ padding: 2, marginBottom: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Configuración
|
||||
</Typography>
|
||||
<Box component="form" noValidate autoComplete="off">
|
||||
<Typography variant="h6" gutterBottom>Configuración</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Ruta del archivo CSV"
|
||||
defaultValue="/var/data/headlines.csv"
|
||||
variant="outlined"
|
||||
sx={{ marginBottom: 2 }}
|
||||
fullWidth name="rutaCsv" label="Ruta del archivo CSV"
|
||||
value={config.rutaCsv} onChange={handleChange}
|
||||
variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Intervalo de Actualización (minutos)"
|
||||
defaultValue={5}
|
||||
type="number"
|
||||
variant="outlined"
|
||||
sx={{ marginBottom: 2 }}
|
||||
fullWidth name="intervaloMinutos" label="Intervalo de Actualización (minutos)"
|
||||
value={config.intervaloMinutos} onChange={handleChange}
|
||||
type="number" variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button variant="contained">Guardar Cambios</Button>
|
||||
<TextField
|
||||
fullWidth name="cantidadTitularesAScrapear" label="Titulares a Capturar por Ciclo"
|
||||
value={config.cantidadTitularesAScrapear} onChange={handleChange}
|
||||
type="number" variant="outlined" sx={{ marginBottom: 2 }} disabled={saving}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}>
|
||||
{success && <Typography color="success.main" sx={{ mr: 2 }}>¡Guardado!</Typography>}
|
||||
<Button type="submit" variant="contained" disabled={saving}>
|
||||
{saving ? <CircularProgress size={24} /> : 'Guardar Cambios'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// frontend/src/services/apiService.ts
|
||||
|
||||
import axios from 'axios';
|
||||
import type { Titular } from '../types';
|
||||
import type { Configuracion, Titular } from '../types';
|
||||
|
||||
const API_URL = 'http://localhost:5174/api';
|
||||
|
||||
@@ -30,4 +30,13 @@ interface ReordenarPayload {
|
||||
|
||||
export const actualizarOrdenTitulares = async (payload: ReordenarPayload[]): Promise<void> => {
|
||||
await apiClient.put('/titulares/reordenar', payload);
|
||||
};
|
||||
|
||||
export const obtenerConfiguracion = async (): Promise<Configuracion> => {
|
||||
const response = await apiClient.get('/configuracion');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const guardarConfiguracion = async (config: Configuracion): Promise<void> => {
|
||||
await apiClient.post('/configuracion', config);
|
||||
};
|
||||
@@ -10,4 +10,11 @@ export interface Titular {
|
||||
fechaCreacion: string;
|
||||
tipo: 'Scraped' | 'Edited' | 'Manual' | null;
|
||||
fuente: string | null;
|
||||
}
|
||||
|
||||
export interface Configuracion {
|
||||
rutaCsv: string;
|
||||
intervaloMinutos: number;
|
||||
cantidadTitularesAScrapear: number;
|
||||
limiteTotalEnDb: number;
|
||||
}
|
||||
Reference in New Issue
Block a user