119 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			119 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | // frontend/src/components/TablaTitulares.tsx
 | ||
|  | 
 | ||
|  | import { useEffect, useState } from 'react'; | ||
|  | import { | ||
|  |   Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Chip, IconButton | ||
|  | } from '@mui/material'; | ||
|  | import DeleteIcon from '@mui/icons-material/Delete'; | ||
|  | import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; | ||
|  | import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; | ||
|  | import { CSS } from '@dnd-kit/utilities'; | ||
|  | 
 | ||
|  | import type { Titular } from '../types'; | ||
|  | import * as api from '../services/apiService'; | ||
|  | 
 | ||
|  | // Componente para una fila de tabla "arrastrable"
 | ||
|  | const SortableRow = ({ titular }: { titular: Titular }) => { | ||
|  |   const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: titular.id }); | ||
|  | 
 | ||
|  |   const style = { | ||
|  |     transform: CSS.Transform.toString(transform), | ||
|  |     transition, | ||
|  |   }; | ||
|  | 
 | ||
|  |   const getChipColor = (tipo: Titular['tipo']) => { | ||
|  |     if (tipo === 'Edited') return 'warning'; | ||
|  |     if (tipo === 'Manual') return 'info'; | ||
|  |     return 'success'; | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <TableRow ref={setNodeRef} style={style} {...attributes} {...listeners}> | ||
|  |       <TableCell>...</TableCell> {/* Handle para arrastrar */} | ||
|  |       <TableCell>{titular.texto}</TableCell> | ||
|  |       <TableCell> | ||
|  |         <Chip label={titular.tipo || 'Scraped'} color={getChipColor(titular.tipo)} size="small" /> | ||
|  |       </TableCell> | ||
|  |       <TableCell>{titular.fuente}</TableCell> | ||
|  |       <TableCell> | ||
|  |         <IconButton size="small" onClick={() => console.log('Eliminar:', titular.id)}> | ||
|  |           <DeleteIcon /> | ||
|  |         </IconButton> | ||
|  |       </TableCell> | ||
|  |     </TableRow> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | // Componente principal de la tabla
 | ||
|  | const TablaTitulares = () => { | ||
|  |   const [titulares, setTitulares] = useState<Titular[]>([]); | ||
|  | 
 | ||
|  |   // Sensores para dnd-kit: reaccionar a clics de puntero
 | ||
|  |   const sensors = useSensors(useSensor(PointerSensor)); | ||
|  | 
 | ||
|  |   const cargarTitulares = async () => { | ||
|  |     try { | ||
|  |       const data = await api.obtenerTitulares(); | ||
|  |       setTitulares(data); | ||
|  |     } catch (error) { | ||
|  |       console.error("Error al cargar titulares:", error); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   useEffect(() => { | ||
|  |     cargarTitulares(); | ||
|  |   }, []); | ||
|  | 
 | ||
|  |   const handleDragEnd = (event: any) => { | ||
|  |     const { active, over } = event; | ||
|  |     if (active.id !== over.id) { | ||
|  |       setTitulares((items) => { | ||
|  |         const oldIndex = items.findIndex((item) => item.id === active.id); | ||
|  |         const newIndex = items.findIndex((item) => item.id === over.id); | ||
|  |         const newArray = arrayMove(items, oldIndex, newIndex); | ||
|  | 
 | ||
|  |         // Creamos el payload para la API
 | ||
|  |         const payload = newArray.map((item, index) => ({ | ||
|  |           id: item.id, | ||
|  |           nuevoOrden: index | ||
|  |         })); | ||
|  | 
 | ||
|  |         // Llamada a la API en segundo plano
 | ||
|  |         api.actualizarOrdenTitulares(payload).catch(err => { | ||
|  |           console.error("Error al reordenar:", err); | ||
|  |           // Opcional: revertir el estado si la API falla
 | ||
|  |         }); | ||
|  | 
 | ||
|  |         return newArray; | ||
|  |       }); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <TableContainer component={Paper}> | ||
|  |       <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | ||
|  |         <SortableContext items={titulares.map(t => t.id)} strategy={verticalListSortingStrategy}> | ||
|  |           <Table> | ||
|  |             <TableHead> | ||
|  |               <TableRow> | ||
|  |                 <TableCell style={{ width: 50 }}></TableCell> {/* Celda para el drag handle */} | ||
|  |                 <TableCell>Texto del Titular</TableCell> | ||
|  |                 <TableCell>Tipo</TableCell> | ||
|  |                 <TableCell>Fuente</TableCell> | ||
|  |                 <TableCell>Acciones</TableCell> | ||
|  |               </TableRow> | ||
|  |             </TableHead> | ||
|  |             <TableBody> | ||
|  |               {titulares.map((titular) => ( | ||
|  |                 <SortableRow key={titular.id} titular={titular} /> | ||
|  |               ))} | ||
|  |             </TableBody> | ||
|  |           </Table> | ||
|  |         </SortableContext> | ||
|  |       </DndContext> | ||
|  |     </TableContainer> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | export default TablaTitulares; |