diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index df64ca3..e05ea1a 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,4 +1,4 @@
-import { AppRouter } from './routers/AppRouter';
+import AppRouter from './routers/AppRouter';
function App() {
return (
diff --git a/frontend/src/components/layout/MainLayout.jsx b/frontend/src/components/layout/MainLayout.jsx
index cd5bf25..0a127ec 100644
--- a/frontend/src/components/layout/MainLayout.jsx
+++ b/frontend/src/components/layout/MainLayout.jsx
@@ -1,54 +1,123 @@
-import { Outlet, Link } from 'react-router-dom';
-import { useAuth } from '../../context/AuthContext';
+import { useState } from 'react';
+import { Link, useNavigate, useLocation } from 'react-router-dom';
+import { useAuth } from '../../context/AuthContext';
-export default function MainLayout() {
- const { user, isAuthenticated, logout } = useAuth();
+export default function MainLayout({ children }) {
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const { user, logout, isAuthenticated } = useAuth();
+
+ const userInitial = user?.username ? user.username[0].toUpperCase() : "U";
+
+ const handleLogout = () => {
+ logout();
+ setIsDropdownOpen(false);
+ navigate('/login');
+ };
+
+ const isActive = (path) => {
+ return location.pathname === path || (path === '/editor' && location.pathname === '/');
+ };
return (
-
-
-
+
+ {/* HEADER */}
+
-
-
- DoC
-
-
+ {/* Logo / Título */}
+
+
Deck of Cards
-
- {isAuthenticated ? (
-
-
{
- if(window.confirm('¿Deseas cerrar sesión?')) {
- logout();
- }
- }}
+ {/* Navegación y Usuario */}
+
+
+ {/* LINKS PRINCIPALES */}
+
+
+ Editor
+
+
+ {/* Enlace al Historial */}
+ {isAuthenticated && (
+
- {user?.username?.charAt(0).toUpperCase() || 'U'}
-
+ Historial
+
+ )}
+
+
+ {/* SECCIÓN DE USUARIO / LOGIN */}
+ {isAuthenticated ? (
+
+
+
+ {/* Dropdown Menu (Solo Usuario y Cerrar Sesión) */}
+ {isDropdownOpen && (
+ <>
+
setIsDropdownOpen(false)}
+ >
+
+
+
+
Usuario
+
{user?.username}
+
+
+
+
+ >
+ )}
) : (
-
- Iniciar Sesión
-
+ // BOTONES PARA USUARIO NO LOGUEADO
+
+
+ Iniciar sesión
+
+
+ Registrarse
+
+
)}
-
-
-
+ {/* CONTENIDO PRINCIPAL */}
+
+ {children}
);
diff --git a/frontend/src/pages/DocEditor.jsx b/frontend/src/pages/DocEditor.jsx
index 1ebf5f9..dd393b5 100644
--- a/frontend/src/pages/DocEditor.jsx
+++ b/frontend/src/pages/DocEditor.jsx
@@ -2,7 +2,7 @@ import { useState } from 'react';
import Step1BaseScale from '../components/editor/Step1BaseScale';
import Step2FuzzyModeling from '../components/editor/Step2FuzzyModeling';
import SubscaleModal from '../components/editor/SubscaleModal';
-import { calculateValueFunction, buildFuzzyGraph } from '../services/docService';
+import { calculateValueFunction, buildFuzzyGraph, saveToHistory } from '../services/docService';
import Step3FinalGraph from '../components/editor/Step3FinalGraph';
export default function DocEditor() {
@@ -193,6 +193,38 @@ export default function DocEditor() {
}
};
+ // Petición para guardar en el historial
+ const handleSaveToHistory = async () => {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ alert("Para guardar tu modelo debes iniciar sesión primero. Puedes seguir visualizando la gráfica sin problema.");
+ return;
+ }
+
+ const defaultName = criterionName ? `Modelo de ${criterionName}` : "Mi nueva gráfica DoC-IT2MF";
+ const historyName = prompt("Dale un nombre a este modelo para guardarlo en tu historial:", defaultName);
+
+ if (!historyName) return;
+
+ setIsLoading(true);
+ try {
+ const payload = {
+ name: historyName,
+ results: finalResult.levels || finalResult.results
+ };
+
+ await saveToHistory(payload);
+
+ alert("¡Gráfica guardada con éxito en tu historial!");
+
+ } catch (error) {
+ console.error("Error al guardar en el historial:", error);
+ alert("Hubo un problema al guardar el modelo: " + error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
return (
@@ -223,10 +255,13 @@ export default function DocEditor() {
)}
diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx
new file mode 100644
index 0000000..e5df8c4
--- /dev/null
+++ b/frontend/src/pages/History.jsx
@@ -0,0 +1,130 @@
+import React, { useState, useEffect } from 'react';
+import { Link } from 'react-router-dom';
+import { getUserHistory, deleteHistoryItem } from '../services/docService';
+import Step3FinalGraph from '../components/editor/Step3FinalGraph';
+
+export default function History() {
+ const [historyItems, setHistoryItems] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [expandedId, setExpandedId] = useState(null);
+
+ useEffect(() => {
+ fetchHistory();
+ }, []);
+
+ const fetchHistory = async () => {
+ setIsLoading(true);
+ try {
+ const data = await getUserHistory();
+ const items = Array.isArray(data) ? data : data.history || data.items || [];
+
+ setHistoryItems(items.reverse());
+ } catch (error) {
+ console.error("Error fetching history:", error);
+ alert("Hubo un problema al cargar el historial.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleDelete = async (id) => {
+ if (!window.confirm('¿Seguro que quieres borrar este modelo definitivamente?')) return;
+
+ try {
+ await deleteHistoryItem(id);
+ setHistoryItems(prev => prev.filter(item => item._id !== id && item.id !== id));
+ if (expandedId === id) setExpandedId(null);
+ } catch (error) {
+ alert("Error al borrar: " + error);
+ }
+ };
+
+ const toggleExpand = (id) => {
+ setExpandedId(expandedId === id ? null : id);
+ };
+
+ return (
+
+
+ {/* Cabecera */}
+
+
+
Mi Historial
+
+ Aquí están todas las gráficas y modelos que has guardado.
+
+
+
+ + Nuevo Modelo
+
+
+
+ {/* Lista de Historial */}
+ {isLoading ? (
+
+
⏳
+
Cargando tus gráficas...
+
+ ) : historyItems.length === 0 ? (
+
+
📭
+
Aún no has guardado ningún modelo.
+
Ve al editor, crea una gráfica y dale a "Finalizar y Guardar".
+
+ ) : (
+
+ {historyItems.map((item) => {
+ const itemId = item._id || item.id;
+ const isExpanded = expandedId === itemId;
+
+ return (
+
+
+ {/* Cabecera de la Card (Siempre visible) */}
+
+
+
+ 📊
+
+
+
{item.name || 'Modelo sin título'}
+
+ Guardado en el historial
+
+
+
+
+
+
+
+
+
+
+ {/* Contenido Desplegable (La Gráfica) */}
+ {isExpanded && (
+
+
+
+ )}
+
+ );
+ })}
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/routers/AppRouter.jsx b/frontend/src/routers/AppRouter.jsx
index 679ca28..3337675 100644
--- a/frontend/src/routers/AppRouter.jsx
+++ b/frontend/src/routers/AppRouter.jsx
@@ -1,20 +1,22 @@
-import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
+import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import MainLayout from '../components/layout/MainLayout';
import DocEditor from '../pages/DocEditor';
import Login from '../pages/Login';
import Register from '../pages/Register';
+import History from '../pages/History';
-export function AppRouter() {
+export default function AppRouter() {
return (
-
-
- }>
- } />
- } />
- } />
- } />
-
-
-
+
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
);
}
\ No newline at end of file
diff --git a/frontend/src/services/docService.js b/frontend/src/services/docService.js
index 16ea154..ecf3686 100644
--- a/frontend/src/services/docService.js
+++ b/frontend/src/services/docService.js
@@ -18,4 +18,34 @@ export const buildFuzzyGraph = async (payload) => {
console.error('Error building fuzzy graph:', error);
throw error.response?.data?.detail || error.message;
}
+};
+
+export const saveToHistory = async (payload) => {
+ try {
+ const response = await api.post('/history/add', payload);
+ return response.data;
+ } catch (error) {
+ console.error('Error saving to history:', error);
+ throw error.response?.data?.detail || error.message;
+ }
+};
+
+export const getUserHistory = async () => {
+ try {
+ const response = await api.get('/history/user');
+ return response.data;
+ } catch (error) {
+ console.error('Error fetching history:', error);
+ throw error.response?.data?.detail || error.message;
+ }
+};
+
+export const deleteHistoryItem = async (id) => {
+ try {
+ const response = await api.delete(`/history/delete/${id}`);
+ return response.data;
+ } catch (error) {
+ console.error('Error deleting history item:', error);
+ throw error.response?.data?.detail || error.message;
+ }
};
\ No newline at end of file