2025-11-21 11:20:44 -03:00
// EN: src/components/SourceManager.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 , TextField , Chip , Switch , FormControlLabel } from '@mui/material' ;
import EditIcon from '@mui/icons-material/Edit' ;
import DeleteIcon from '@mui/icons-material/Delete' ;
import AddIcon from '@mui/icons-material/Add' ;
import apiClient from '../api/apiClient' ;
interface FuenteContexto {
id : number ;
nombre : string ;
url : string ;
descripcionParaIA : string ;
activo : boolean ;
}
interface SourceManagerProps {
onAuthError : ( ) = > void ;
}
const SourceManager : React.FC < SourceManagerProps > = ( { onAuthError } ) = > {
const [ rows , setRows ] = useState < FuenteContexto [ ] > ( [ ] ) ;
const [ open , setOpen ] = useState ( false ) ;
const [ isEdit , setIsEdit ] = useState ( false ) ;
const [ currentRow , setCurrentRow ] = useState < Partial < FuenteContexto > > ( { } ) ;
const [ error , setError ] = useState < string | null > ( null ) ;
const [ confirmOpen , setConfirmOpen ] = useState ( false ) ;
const [ itemToDelete , setItemToDelete ] = useState < number | null > ( null ) ;
const fetchData = useCallback ( async ( ) = > {
try {
// --- ENDPOINT ---
const response = await apiClient . get ( '/api/admin/fuentes' ) ;
setRows ( response . data ) ;
} catch ( err ) {
setError ( 'No se pudieron cargar las fuentes de contexto.' ) ;
if ( axios . isAxiosError ( err ) && err . response ? . status === 401 ) {
onAuthError ( ) ;
}
}
} , [ onAuthError ] ) ;
useEffect ( ( ) = > {
fetchData ( ) ;
} , [ fetchData ] ) ;
const handleOpen = ( item? : FuenteContexto ) = > {
if ( item ) {
setIsEdit ( true ) ;
setCurrentRow ( item ) ;
} else {
// --- ESTADO INICIAL ---
setIsEdit ( false ) ;
setCurrentRow ( { nombre : '' , url : '' , descripcionParaIA : '' , activo : true } ) ;
}
setOpen ( true ) ;
} ;
const handleClose = ( ) = > setOpen ( false ) ;
const handleSave = async ( ) = > {
try {
if ( isEdit ) {
await apiClient . put ( ` /api/admin/fuentes/ ${ currentRow . id } ` , currentRow ) ;
} else {
await apiClient . post ( '/api/admin/fuentes' , currentRow ) ;
}
fetchData ( ) ;
handleClose ( ) ;
} catch ( err ) {
setError ( 'Error al guardar la fuente.' ) ;
}
} ;
const handleDeleteClick = ( id : number ) = > {
setItemToDelete ( id ) ;
setConfirmOpen ( true ) ;
} ;
const handleConfirmClose = ( ) = > {
setConfirmOpen ( false ) ;
setItemToDelete ( null ) ;
} ;
const handleConfirmDelete = async ( ) = > {
if ( itemToDelete !== null ) {
try {
await apiClient . delete ( ` /api/admin/fuentes/ ${ itemToDelete } ` ) ;
fetchData ( ) ;
} catch ( err ) {
setError ( 'Error al eliminar la fuente.' ) ;
} finally {
handleConfirmClose ( ) ;
}
}
} ;
// --- DEFINICIÓN DE COLUMNAS ---
const columns : GridColDef [ ] = [
{ field : 'nombre' , headerName : 'Nombre' , width : 200 } ,
{ field : 'url' , headerName : 'URL' , width : 350 } ,
{ field : 'descripcionParaIA' , headerName : 'Descripción para IA' , flex : 1 } ,
{
field : 'activo' ,
headerName : 'Activo' ,
width : 100 ,
renderCell : ( params ) = > (
< Chip label = { params . value ? 'Sí' : 'No' } color = { params . value ? 'success' : 'default' } / >
) ,
} ,
{
field : 'actions' ,
type : 'actions' ,
width : 100 ,
getActions : ( params ) = > [
< GridActionsCellItem
icon = { < EditIcon / > }
label = "Editar"
onClick = { ( ) = > handleOpen ( params . row as FuenteContexto ) }
/ > ,
< GridActionsCellItem
icon = { < DeleteIcon / > }
label = "Eliminar"
onClick = { ( ) = > handleDeleteClick ( params . id as number ) }
/ > ,
] ,
} ,
] ;
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 >
< Dialog open = { open } onClose = { handleClose } fullWidth maxWidth = "md" >
< DialogTitle > { isEdit ? 'Editar Fuente' : 'Añadir Nueva Fuente' } < / DialogTitle >
< DialogContent >
< TextField
autoFocus
margin = "dense"
label = "Nombre"
fullWidth
value = { currentRow . nombre || '' }
onChange = { ( e ) = > setCurrentRow ( { . . . currentRow , nombre : e.target.value } ) }
helperText = "Un nombre corto y descriptivo (ej: FAQs de Suscripción)."
/ >
< TextField
margin = "dense"
label = "URL"
fullWidth
value = { currentRow . url || '' }
onChange = { ( e ) = > setCurrentRow ( { . . . currentRow , url : e.target.value } ) }
helperText = "La URL completa de la página que el bot debe leer."
/ >
< TextField
margin = "dense"
label = "Descripción para la IA"
fullWidth
multiline
rows = { 3 }
value = { currentRow . descripcionParaIA || '' }
onChange = { ( e ) = > setCurrentRow ( { . . . currentRow , descripcionParaIA : e.target.value } ) }
2025-11-21 12:10:45 -03:00
helperText = "¡Crucial! Describe en una frase para qué sirve esta fuente. Ej: 'Usar para responder preguntas sobre cómo registrarse, iniciar sesión o sobre el registro'."
2025-11-21 11:20:44 -03:00
/ >
< FormControlLabel
control = {
< Switch
checked = { currentRow . activo ? ? true }
onChange = { ( e ) = > setCurrentRow ( { . . . currentRow , activo : e.target.checked } ) }
/ >
}
label = "Fuente activa"
/ >
< / DialogContent >
< DialogActions >
< Button onClick = { handleClose } > Cancelar < / Button >
< Button onClick = { handleSave } variant = "contained" > Guardar < / Button >
< / DialogActions >
< / Dialog >
< Dialog
open = { confirmOpen }
onClose = { handleConfirmClose }
>
< DialogTitle > Confirmar Eliminación < / DialogTitle >
< DialogContent >
< DialogContent >
¿ Estás seguro de que quieres eliminar este item ? Esta acción no se puede deshacer .
< / DialogContent >
< / DialogContent >
< DialogActions >
< Button onClick = { handleConfirmClose } > Cancelar < / Button >
< Button onClick = { handleConfirmDelete } color = "error" variant = "contained" >
Eliminar
< / Button >
< / DialogActions >
< / Dialog >
< / Box >
) ;
} ;
export default SourceManager ;