From fec840b3bed2982922bbbc2095190bc8d6e2b189 Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 7 Apr 2026 09:08:51 +0200 Subject: [PATCH] =?UTF-8?q?add:=20a=C3=B1adir=20funcionalidad=20de=20guard?= =?UTF-8?q?ar=20y=20ver=20historial=20si=20est=C3=A1s=20logeado?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.jsx | 2 +- frontend/src/components/layout/MainLayout.jsx | 137 +++++++++++++----- frontend/src/pages/DocEditor.jsx | 43 +++++- frontend/src/pages/History.jsx | 130 +++++++++++++++++ frontend/src/routers/AppRouter.jsx | 26 ++-- frontend/src/services/docService.js | 30 ++++ 6 files changed, 317 insertions(+), 51 deletions(-) create mode 100644 frontend/src/pages/History.jsx 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