diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 8f978f1..e7eaa42 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,14 +1,13 @@
// frontend/src/App.tsx
-import { ThemeProvider, createTheme, CssBaseline, Box } from '@mui/material';
-import TablaTitulares from './components/TablaTitulares'; // Lo crearemos ahora
+import { ThemeProvider, createTheme, CssBaseline, Container } from '@mui/material';
+import Dashboard from './components/Dashboard';
-// Definimos un tema oscuro similar al de la imagen de referencia
const darkTheme = createTheme({
palette: {
mode: 'dark',
primary: {
- main: '#3f51b5',
+ main: '#90caf9', // Un azul más claro para mejor contraste en modo oscuro
},
background: {
default: '#121212',
@@ -20,12 +19,10 @@ const darkTheme = createTheme({
function App() {
return (
- {/* Normaliza el CSS para que se vea bien en todos los navegadores */}
-
-
Titulares Dashboard
- {/* Aquí irán los demás componentes como el formulario de configuración */}
-
-
+
+
+
+
);
}
diff --git a/frontend/src/components/AddTitularModal.tsx b/frontend/src/components/AddTitularModal.tsx
new file mode 100644
index 0000000..db04978
--- /dev/null
+++ b/frontend/src/components/AddTitularModal.tsx
@@ -0,0 +1,60 @@
+// frontend/src/components/AddTitularModal.tsx
+
+import { useState } from 'react';
+import { Modal, Box, Typography, TextField, Button } from '@mui/material';
+
+// Estilo para el modal, centrado en la pantalla
+const style = {
+ position: 'absolute' as 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: 400,
+ bgcolor: 'background.paper',
+ boxShadow: 24,
+ p: 4,
+};
+
+interface Props {
+ open: boolean;
+ onClose: () => void;
+ onAdd: (texto: string) => void;
+}
+
+const AddTitularModal = ({ open, onClose, onAdd }: Props) => {
+ const [texto, setTexto] = useState('');
+
+ const handleSubmit = () => {
+ if (texto.trim()) {
+ onAdd(texto.trim());
+ setTexto('');
+ onClose();
+ }
+ };
+
+ return (
+
+
+
+ Añadir Titular Manual
+
+ setTexto(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && handleSubmit()}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default AddTitularModal;
\ No newline at end of file
diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx
new file mode 100644
index 0000000..3007ee3
--- /dev/null
+++ b/frontend/src/components/Dashboard.tsx
@@ -0,0 +1,88 @@
+// frontend/src/components/Dashboard.tsx
+
+import { useEffect, useState } from 'react';
+import { Box, Button, Typography, Stack } from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+import SyncIcon from '@mui/icons-material/Sync';
+import type { Titular } from '../types';
+import * as api from '../services/apiService';
+import FormularioConfiguracion from './FormularioConfiguracion';
+import TablaTitulares from './TablaTitulares';
+import AddTitularModal from './AddTitularModal';
+
+const Dashboard = () => {
+ const [titulares, setTitulares] = useState([]);
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const cargarTitulares = async () => {
+ try {
+ const data = await api.obtenerTitulares();
+ setTitulares(data);
+ } catch (error) {
+ console.error("Error al cargar titulares:", error);
+ }
+ };
+
+ useEffect(() => {
+ cargarTitulares();
+ }, []);
+
+ const handleReorder = async (titularesReordenados: Titular[]) => {
+ setTitulares(titularesReordenados); // Actualización optimista de la UI
+ const payload = titularesReordenados.map((item, index) => ({
+ id: item.id,
+ nuevoOrden: index
+ }));
+ try {
+ await api.actualizarOrdenTitulares(payload);
+ } catch (err) {
+ console.error("Error al reordenar:", err);
+ cargarTitulares(); // Revertir en caso de error
+ }
+ };
+
+ const handleDelete = async (id: number) => {
+ if (window.confirm('¿Estás seguro de que quieres eliminar este titular?')) {
+ try {
+ await api.eliminarTitular(id);
+ setTitulares(titulares.filter(t => t.id !== id)); // Actualizar UI
+ } catch (err) {
+ console.error("Error al eliminar:", err);
+ }
+ }
+ };
+
+ const handleAdd = async (texto: string) => {
+ try {
+ await api.crearTitularManual(texto);
+ cargarTitulares(); // Recargar la lista para ver el nuevo titular
+ } catch (err) {
+ console.error("Error al añadir titular:", err);
+ }
+ };
+
+ return (
+ <>
+
+
+ Titulares Dashboard
+
+
+ }>
+ Generate CSV
+
+ } onClick={() => setModalOpen(true)}>
+ Add Manual
+
+
+
+
+
+
+
+ setModalOpen(false)} onAdd={handleAdd} />
+ >
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/frontend/src/components/FormularioConfiguracion.tsx b/frontend/src/components/FormularioConfiguracion.tsx
new file mode 100644
index 0000000..a3a3303
--- /dev/null
+++ b/frontend/src/components/FormularioConfiguracion.tsx
@@ -0,0 +1,35 @@
+// frontend/src/components/FormularioConfiguracion.tsx
+
+import { Box, TextField, Button, Paper, Typography } from '@mui/material';
+
+const FormularioConfiguracion = () => {
+ return (
+
+
+ Configuración
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FormularioConfiguracion;
\ No newline at end of file
diff --git a/frontend/src/components/TablaTitulares.tsx b/frontend/src/components/TablaTitulares.tsx
index cb2991c..6ef7619 100644
--- a/frontend/src/components/TablaTitulares.tsx
+++ b/frontend/src/components/TablaTitulares.tsx
@@ -1,19 +1,22 @@
// frontend/src/components/TablaTitulares.tsx
-import { useEffect, useState } from 'react';
import {
- Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Chip, IconButton
+ Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Chip, IconButton, Typography
} from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
-import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
+import DragHandleIcon from '@mui/icons-material/DragHandle'; // Importar el ícono
+import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, type DragEndEvent } 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 }) => {
+// La prop `onDelete` se añade para comunicar el evento al componente padre
+interface SortableRowProps {
+ titular: Titular;
+ onDelete: (id: number) => void;
+}
+
+const SortableRow = ({ titular, onDelete }: SortableRowProps) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: titular.id });
const style = {
@@ -28,15 +31,19 @@ const SortableRow = ({ titular }: { titular: Titular }) => {
};
return (
-
- ... {/* Handle para arrastrar */}
+
+ {/* El handle de arrastre ahora es un ícono */}
+
+
+ {titular.texto}{titular.fuente}
- console.log('Eliminar:', titular.id)}>
+ {/* Usamos un stopPropagation para que el clic no active el arrastre */}
+ { e.stopPropagation(); onDelete(titular.id); }}>
@@ -44,75 +51,58 @@ const SortableRow = ({ titular }: { titular: Titular }) => {
);
};
+interface TablaTitularesProps {
+ titulares: Titular[];
+ onReorder: (titulares: Titular[]) => void;
+ onDelete: (id: number) => void;
+}
-// Componente principal de la tabla
-const TablaTitulares = () => {
- const [titulares, setTitulares] = useState([]);
+const TablaTitulares = ({ titulares, onReorder, onDelete }: TablaTitularesProps) => {
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } })); // Evita activar el drag con un simple clic
- // 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 handleDragEnd = (event: DragEndEvent) => {
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;
- });
+ if (over && active.id !== over.id) {
+ const oldIndex = titulares.findIndex((item) => item.id === active.id);
+ const newIndex = titulares.findIndex((item) => item.id === over.id);
+ const newArray = arrayMove(titulares, oldIndex, newIndex);
+ onReorder(newArray); // Pasamos el nuevo array al padre para que gestione el estado y la llamada a la API
}
};
+ if (titulares.length === 0) {
+ return (
+
+ No hay titulares para mostrar.
+
+ );
+ }
+
return (
-
-
- t.id)} strategy={verticalListSortingStrategy}>
-
-
-
- {/* Celda para el drag handle */}
- Texto del Titular
- Tipo
- Fuente
- Acciones
-
-
-
- {titulares.map((titular) => (
-
- ))}
-
-