| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | // frontend/src/components/TablaTitulares.tsx
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import { | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Chip, IconButton, Typography, Link | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | } from '@mui/material'; | 
					
						
							|  |  |  | import DeleteIcon from '@mui/icons-material/Delete'; | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  | import DragHandleIcon from '@mui/icons-material/DragHandle'; | 
					
						
							|  |  |  | import EditIcon from '@mui/icons-material/Edit'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, type DragEndEvent } from '@dnd-kit/core'; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; | 
					
						
							|  |  |  | import { CSS } from '@dnd-kit/utilities'; | 
					
						
							|  |  |  | import type { Titular } from '../types'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | interface SortableRowProps { | 
					
						
							|  |  |  |   titular: Titular; | 
					
						
							|  |  |  |   onDelete: (id: number) => void; | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |   onEdit: (titular: Titular) => void; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  | const SortableRow = ({ titular, onDelete, onEdit }: SortableRowProps) => { | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |   const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: titular.id }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const style = { | 
					
						
							|  |  |  |     transform: CSS.Transform.toString(transform), | 
					
						
							|  |  |  |     transition, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   const getChipColor = (tipo: Titular['tipo']): "success" | "warning" | "info" => { | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |     if (tipo === 'Edited') return 'warning'; | 
					
						
							|  |  |  |     if (tipo === 'Manual') return 'info'; | 
					
						
							|  |  |  |     return 'success'; | 
					
						
							|  |  |  |   }; | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |    | 
					
						
							|  |  |  |   const formatFuente = (fuente: string | null) => { | 
					
						
							|  |  |  |     if (!fuente) return 'N/A'; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const url = new URL(fuente); | 
					
						
							|  |  |  |       return url.hostname.replace('www.', ''); | 
					
						
							|  |  |  |     } catch { | 
					
						
							|  |  |  |       return fuente; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |     <TableRow ref={setNodeRef} style={style} {...attributes} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}> | 
					
						
							|  |  |  |       <TableCell sx={{ cursor: 'grab', verticalAlign: 'middle' }} {...listeners}> | 
					
						
							|  |  |  |         <DragHandleIcon sx={{ color: 'text.secondary' }} /> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |       </TableCell> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       <TableCell sx={{ verticalAlign: 'middle' }}>{titular.texto}</TableCell> | 
					
						
							|  |  |  |       <TableCell sx={{ verticalAlign: 'middle' }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |         <Chip label={titular.tipo || 'Scraped'} color={getChipColor(titular.tipo)} size="small" /> | 
					
						
							|  |  |  |       </TableCell> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       <TableCell sx={{ verticalAlign: 'middle' }}> | 
					
						
							|  |  |  |         {titular.urlFuente ? ( | 
					
						
							|  |  |  |           <Link href={titular.urlFuente} target="_blank" rel="noopener noreferrer" underline="hover" color="primary.light"> | 
					
						
							|  |  |  |             {formatFuente(titular.fuente)} | 
					
						
							|  |  |  |           </Link> | 
					
						
							|  |  |  |         ) : ( | 
					
						
							|  |  |  |           formatFuente(titular.fuente) | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       </TableCell> | 
					
						
							|  |  |  |       <TableCell sx={{ verticalAlign: 'middle', textAlign: 'right' }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |         <IconButton size="small" onClick={(e) => { e.stopPropagation(); onEdit(titular); }}> | 
					
						
							|  |  |  |           <EditIcon fontSize="small" /> | 
					
						
							|  |  |  |         </IconButton> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |         <IconButton size="small" onClick={(e) => { e.stopPropagation(); onDelete(titular.id); }} sx={{ color: '#ef4444' }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |           <DeleteIcon /> | 
					
						
							|  |  |  |         </IconButton> | 
					
						
							|  |  |  |       </TableCell> | 
					
						
							|  |  |  |     </TableRow> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | interface TablaTitularesProps { | 
					
						
							|  |  |  |   titulares: Titular[]; | 
					
						
							|  |  |  |   onReorder: (titulares: Titular[]) => void; | 
					
						
							|  |  |  |   onDelete: (id: number) => void; | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |   onEdit: (titular: Titular) => void; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  | const TablaTitulares = ({ titulares, onReorder, onDelete, onEdit }: TablaTitularesProps) => { | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |   const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } })); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   const handleDragEnd = (event: DragEndEvent) => { | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |     const { active, over } = event; | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |     if (over && active.id !== over.id) { | 
					
						
							|  |  |  |       const oldIndex = titulares.findIndex((item) => item.id === active.id); | 
					
						
							|  |  |  |       const newIndex = titulares.findIndex((item) => item.id === over.id); | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       onReorder(arrayMove(titulares, oldIndex, newIndex)); | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |   if (titulares.length === 0) { | 
					
						
							|  |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |       <Paper elevation={0} sx={{ p: 3, textAlign: 'center' }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |         <Typography>No hay titulares para mostrar.</Typography> | 
					
						
							|  |  |  |       </Paper> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |     <Paper elevation={0} sx={{ overflow: 'hidden' }}> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |       <TableContainer> | 
					
						
							|  |  |  |         <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> | 
					
						
							|  |  |  |           <SortableContext items={titulares.map(t => t.id)} strategy={verticalListSortingStrategy}> | 
					
						
							|  |  |  |             <Table> | 
					
						
							|  |  |  |               <TableHead> | 
					
						
							| 
									
										
										
										
											2025-10-29 11:36:20 -03:00
										 |  |  |                 <TableRow sx={{ '& .MuiTableCell-root': { borderBottom: '1px solid rgba(255, 255, 255, 0.12)' } }}> | 
					
						
							|  |  |  |                   <TableCell sx={{ width: 50 }} /> | 
					
						
							|  |  |  |                   <TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Texto del Titular</TableCell> | 
					
						
							|  |  |  |                   <TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Tipo</TableCell> | 
					
						
							|  |  |  |                   <TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em' }}>Fuente</TableCell> | 
					
						
							|  |  |  |                   <TableCell sx={{ textTransform: 'uppercase', color: 'text.secondary', letterSpacing: '0.05em', textAlign: 'right' }}>Acciones</TableCell> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |                 </TableRow> | 
					
						
							|  |  |  |               </TableHead> | 
					
						
							|  |  |  |               <TableBody> | 
					
						
							|  |  |  |                 {titulares.map((titular) => ( | 
					
						
							| 
									
										
										
										
											2025-10-28 14:12:05 -03:00
										 |  |  |                   <SortableRow key={titular.id} titular={titular} onDelete={onDelete} onEdit={onEdit} /> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:54:36 -03:00
										 |  |  |                 ))} | 
					
						
							|  |  |  |               </TableBody> | 
					
						
							|  |  |  |             </Table> | 
					
						
							|  |  |  |           </SortableContext> | 
					
						
							|  |  |  |         </DndContext> | 
					
						
							|  |  |  |       </TableContainer> | 
					
						
							|  |  |  |     </Paper> | 
					
						
							| 
									
										
										
										
											2025-10-28 11:45:51 -03:00
										 |  |  |   ); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default TablaTitulares; |