| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | import { useEffect, useState, useCallback } from 'react'; | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  | import { | 
					
						
							|  |  |  |   Box, Button, Stack, Chip, CircularProgress, | 
					
						
							|  |  |  |   Accordion, AccordionSummary, AccordionDetails, Typography | 
					
						
							|  |  |  | } from '@mui/material'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import AddIcon from '@mui/icons-material/Add'; | 
					
						
							|  |  |  | import SyncIcon from '@mui/icons-material/Sync'; | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  | import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  | import type { Titular, Configuracion } from '../types'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import * as api from '../services/apiService'; | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | import { useSignalR } from '../hooks/useSignalR'; | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  | import { useNotification } from '../hooks/useNotification'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import FormularioConfiguracion from './FormularioConfiguracion'; | 
					
						
							|  |  |  | import TablaTitulares from './TablaTitulares'; | 
					
						
							|  |  |  | import AddTitularModal from './AddTitularModal'; | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  | import EditarTitularModal from './EditarTitularModal'; | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  | import { PowerSwitch } from './PowerSwitch'; | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  | import ConfirmationModal from './ConfirmationModal'; | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  | import type { ActualizarTitularPayload } from '../services/apiService'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  | const Dashboard = () => { | 
					
						
							|  |  |  |   const [titulares, setTitulares] = useState<Titular[]>([]); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   const [config, setConfig] = useState<Configuracion | null>(null); | 
					
						
							|  |  |  |   const [addModalOpen, setAddModalOpen] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |   const [isGeneratingCsv, setIsGeneratingCsv] = useState(false); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |   const [confirmState, setConfirmState] = useState<{ open: boolean; onConfirm: (() => void) | null }>({ open: false, onConfirm: null }); | 
					
						
							|  |  |  |   const { showNotification } = useNotification(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  |   const [titularAEditar, setTitularAEditar] = useState<Titular | null>(null); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |   const onTitularesActualizados = useCallback((titularesActualizados: Titular[]) => { | 
					
						
							|  |  |  |     setTitulares(titularesActualizados); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   }, []); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const { connectionStatus } = useSignalR([ | 
					
						
							|  |  |  |     { eventName: 'TitularesActualizados', callback: onTitularesActualizados } | 
					
						
							|  |  |  |   ]); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |     const fetchConfig = api.obtenerConfiguracion(); | 
					
						
							|  |  |  |     const fetchEstado = api.getEstadoProceso(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Promise.all([fetchConfig, fetchEstado]) | 
					
						
							|  |  |  |       .then(([configData, estadoData]) => { | 
					
						
							|  |  |  |         setConfig({ | 
					
						
							|  |  |  |           ...configData, | 
					
						
							|  |  |  |           scrapingActivo: estadoData.activo | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .catch(error => console.error("Error al cargar datos iniciales:", error)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     api.obtenerTitulares().then(setTitulares); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  |   const handleDelete = (id: number) => { | 
					
						
							|  |  |  |     const onConfirm = async () => { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await api.eliminarTitular(id); | 
					
						
							|  |  |  |         showNotification('Titular eliminado correctamente', 'success'); | 
					
						
							|  |  |  |       } catch (err) { | 
					
						
							|  |  |  |         showNotification('Error al eliminar el titular', 'error'); | 
					
						
							|  |  |  |         console.error("Error al eliminar:", err); | 
					
						
							|  |  |  |       } finally { | 
					
						
							|  |  |  |         setConfirmState({ open: false, onConfirm: null }); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     setConfirmState({ open: true, onConfirm }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleSaveEdit = async (id: number, payload: ActualizarTitularPayload) => { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.actualizarTitular(id, payload); | 
					
						
							|  |  |  |       showNotification('Titular actualizado', 'success'); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       showNotification('Error al guardar los cambios', 'error'); | 
					
						
							|  |  |  |       console.error("Error al guardar cambios:", err); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   const handleSwitchChange = async (event: React.ChangeEvent<HTMLInputElement>) => { | 
					
						
							|  |  |  |     if (!config) return; | 
					
						
							|  |  |  |     const isChecked = event.target.checked; | 
					
						
							|  |  |  |     setConfig({ ...config, scrapingActivo: isChecked }); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.setEstadoProceso(isChecked); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       console.error("Error al cambiar estado del proceso", err); | 
					
						
							|  |  |  |       setConfig({ ...config, scrapingActivo: !isChecked }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   const handleReorder = async (titularesReordenados: Titular[]) => { | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |     setTitulares(titularesReordenados); | 
					
						
							|  |  |  |     const payload = titularesReordenados.map((item, index) => ({ id: item.id, nuevoOrden: index })); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     try { | 
					
						
							|  |  |  |       await api.actualizarOrdenTitulares(payload); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       console.error("Error al reordenar:", err); | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |       api.obtenerTitulares().then(setTitulares); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const handleAdd = async (texto: string) => { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.crearTitularManual(texto); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |       showNotification('Titular manual añadido', 'success'); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     } catch (err) { | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |       showNotification('Error al añadir el titular', 'error'); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |       console.error("Error al añadir titular:", err); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |   const getStatusChip = () => { | 
					
						
							|  |  |  |     switch (connectionStatus) { | 
					
						
							|  |  |  |       case 'Connected': | 
					
						
							|  |  |  |         return <Chip label="Conectado" color="success" size="small" />; | 
					
						
							|  |  |  |       case 'Reconnecting': | 
					
						
							|  |  |  |       case 'Connecting': | 
					
						
							|  |  |  |         return <Chip label="Conectando..." color="warning" size="small" />; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         return <Chip label="Desconectado" color="error" size="small" />; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |   const handleGenerateCsv = async () => { | 
					
						
							|  |  |  |     setIsGeneratingCsv(true); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await api.generarCsvManual(); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |       showNotification('CSV generado manualmente', 'success'); | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |     } catch (error) { | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |       showNotification('Error al generar el CSV', 'error'); | 
					
						
							|  |  |  |       console.error("Error al generar CSV manualmente", error); | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |     } finally { | 
					
						
							|  |  |  |       setIsGeneratingCsv(false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   return ( | 
					
						
							|  |  |  |     <> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       <Box | 
					
						
							|  |  |  |         sx={{ | 
					
						
							|  |  |  |           display: 'flex', | 
					
						
							|  |  |  |           flexDirection: { xs: 'column', sm: 'row' }, | 
					
						
							|  |  |  |           justifyContent: 'space-between', | 
					
						
							|  |  |  |           alignItems: 'center', | 
					
						
							|  |  |  |           gap: 2, | 
					
						
							|  |  |  |           mb: 3, | 
					
						
							|  |  |  |         }} | 
					
						
							|  |  |  |       > | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |         <Stack direction="row" spacing={2} alignItems="center"> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |           <Typography variant="h5" component="h2" sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}> | 
					
						
							|  |  |  |             Estado del Servidor {getStatusChip()} | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |           </Typography> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |           {config ? ( | 
					
						
							|  |  |  |             <PowerSwitch | 
					
						
							|  |  |  |               checked={config.scrapingActivo} | 
					
						
							|  |  |  |               onChange={handleSwitchChange} | 
					
						
							|  |  |  |               label={config.scrapingActivo ? "Proceso ON" : "Proceso OFF"} | 
					
						
							|  |  |  |             /> | 
					
						
							|  |  |  |           ) : <CircularProgress size={24} />} | 
					
						
							| 
									
										
										
										
											2025-10-28 12:26:49 -03:00
										 |  |  |         </Stack> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         <Stack direction="row" spacing={2} sx={{ width: { xs: '100%', sm: 'auto' } }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |           <Button | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |             variant="contained" color="success" | 
					
						
							|  |  |  |             startIcon={isGeneratingCsv ? <CircularProgress size={20} color="inherit" /> : <SyncIcon />} | 
					
						
							|  |  |  |             onClick={handleGenerateCsv} disabled={isGeneratingCsv} | 
					
						
							| 
									
										
										
										
											2025-10-28 13:19:24 -03:00
										 |  |  |           > | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |             Regenerar CSV | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |           </Button> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |           <Button | 
					
						
							|  |  |  |             variant="contained" color="primary" | 
					
						
							|  |  |  |             startIcon={<AddIcon />} | 
					
						
							|  |  |  |             onClick={() => setAddModalOpen(true)} | 
					
						
							|  |  |  |           > | 
					
						
							|  |  |  |             Titular Manual | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |           </Button> | 
					
						
							|  |  |  |         </Stack> | 
					
						
							|  |  |  |       </Box> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       <Accordion defaultExpanded={false} sx={{ mb: 3 }}> | 
					
						
							|  |  |  |         <AccordionSummary expandIcon={<ExpandMoreIcon />}> | 
					
						
							|  |  |  |           <Typography variant="h6">Configuración</Typography> | 
					
						
							|  |  |  |         </AccordionSummary> | 
					
						
							|  |  |  |         <AccordionDetails> | 
					
						
							|  |  |  |           <FormularioConfiguracion config={config} setConfig={setConfig} /> | 
					
						
							|  |  |  |         </AccordionDetails> | 
					
						
							|  |  |  |       </Accordion> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |       <TablaTitulares | 
					
						
							|  |  |  |         titulares={titulares} | 
					
						
							|  |  |  |         onReorder={handleReorder} | 
					
						
							|  |  |  |         onDelete={handleDelete} | 
					
						
							|  |  |  |         onEdit={(titular) => setTitularAEditar(titular)} | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  |         onSave={handleSaveEdit} | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |       /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <AddTitularModal | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |         open={addModalOpen} | 
					
						
							|  |  |  |         onClose={() => setAddModalOpen(false)} | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |         onAdd={handleAdd} | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:47:47 -03:00
										 |  |  |       <ConfirmationModal | 
					
						
							|  |  |  |         open={confirmState.open} | 
					
						
							|  |  |  |         onClose={() => setConfirmState({ open: false, onConfirm: null })} | 
					
						
							|  |  |  |         onConfirm={() => confirmState.onConfirm?.()} | 
					
						
							|  |  |  |         title="Confirmar Eliminación" | 
					
						
							|  |  |  |         message="¿Estás seguro de que quieres eliminar este titular? Esta acción no se puede deshacer." | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2025-10-29 12:26:38 -03:00
										 |  |  |       <EditarTitularModal | 
					
						
							|  |  |  |         open={titularAEditar !== null} | 
					
						
							|  |  |  |         onClose={() => setTitularAEditar(null)} | 
					
						
							|  |  |  |         onSave={(id, texto, viñeta) => handleSaveEdit(id, { texto, viñeta: viñeta || null })} | 
					
						
							|  |  |  |         titular={titularAEditar} | 
					
						
							|  |  |  |       /> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     </> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default Dashboard; |