diff --git a/frontend/src/components/FormularioConfiguracion.tsx b/frontend/src/components/FormularioConfiguracion.tsx index 92022fa..fc8bcd3 100644 --- a/frontend/src/components/FormularioConfiguracion.tsx +++ b/frontend/src/components/FormularioConfiguracion.tsx @@ -1,7 +1,8 @@ -import { Box, TextField, Button, Paper, CircularProgress, Typography } from '@mui/material'; +import { Box, TextField, Paper, CircularProgress, Chip } from '@mui/material'; import type { Configuracion } from '../types'; import * as api from '../services/apiService'; -import { useState } from 'react'; +import { useState, useEffect, useRef } from 'react'; +import { useDebounce } from '../hooks/useDebounce'; interface Props { config: Configuracion | null; @@ -9,61 +10,79 @@ interface Props { } const FormularioConfiguracion = ({ config, setConfig }: Props) => { - const [saving, setSaving] = useState(false); - const [success, setSuccess] = useState(false); + const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved'>('idle'); + const debouncedConfig = useDebounce(config, 750); + + const isInitialLoad = useRef(true); + + useEffect(() => { + // Solo procedemos si tenemos un objeto de configuración "debounced" + if (debouncedConfig) { + // Si la 'ref' es true, significa que esta es la primera vez que recibimos + // un objeto de configuración válido. Lo ignoramos y marcamos la carga inicial como completada. + if (isInitialLoad.current) { + isInitialLoad.current = false; + return; + } + + // Si la 'ref' ya es false, significa que cualquier cambio posterior + // es una modificación real del usuario, por lo que procedemos a guardar. + const saveConfig = async () => { + setSaveStatus('saving'); + try { + await api.guardarConfiguracion(debouncedConfig); + setSaveStatus('saved'); + setTimeout(() => { + setSaveStatus('idle'); + }, 2000); + } catch (err) { + console.error("Error en el auto-guardado:", err); + setSaveStatus('idle'); + } + }; + + saveConfig(); + } + }, [debouncedConfig]); // La dependencia sigue siendo la misma + if (!config) { return ; } const handleChange = (event: React.ChangeEvent) => { + setSaveStatus('idle'); const { name, value } = event.target; const numericFields = ['intervaloMinutos', 'cantidadTitularesAScrapear']; - setConfig(prevConfig => prevConfig ? { ...prevConfig, [name]: numericFields.includes(name) ? Number(value) : value } : null); }; - - 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); - } catch (err) { - console.error("Error al guardar configuración", err); - } finally { - setSaving(false); + + const getSaveStatusIndicator = () => { + if (saveStatus === 'saving') { + return } />; } + if (saveStatus === 'saved') { + return ; + } + return null; }; return ( - - - - + + + + - - {success && ¡Guardado!} - + + {getSaveStatusIndicator()} diff --git a/frontend/src/hooks/useDebounce.ts b/frontend/src/hooks/useDebounce.ts new file mode 100644 index 0000000..5f7f831 --- /dev/null +++ b/frontend/src/hooks/useDebounce.ts @@ -0,0 +1,27 @@ +// frontend/src/hooks/useDebounce.ts + +import { useState, useEffect } from 'react'; + +// Este hook toma un valor y un retardo (delay) en milisegundos. +// Devuelve una nueva versión del valor que solo se actualiza +// después de que el valor original no haya cambiado durante el 'delay' especificado. +export function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + // Configura un temporizador para actualizar el valor "debounced" + // después de que pase el tiempo de 'delay'. + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + // Función de limpieza: Si el 'value' cambia (porque el usuario sigue escribiendo), + // este return se ejecuta primero, limpiando el temporizador anterior. + // Esto previene que el valor se actualice mientras el usuario sigue interactuando. + return () => { + clearTimeout(handler); + }; + }, [value, delay]); // El efecto se vuelve a ejecutar solo si el valor o el retardo cambian. + + return debouncedValue; +} \ No newline at end of file