diff --git a/Proyecto_Final_MemberFlow_v1.pdf b/Proyecto_Final_MemberFlow_v1.pdf new file mode 100644 index 0000000..6e45cbf Binary files /dev/null and b/Proyecto_Final_MemberFlow_v1.pdf differ diff --git a/memberflow-api/libs/memberflow-data-1.0-SNAPSHOT.jar b/memberflow-api/libs/memberflow-data-1.0-SNAPSHOT.jar index c9650ee..dfe6bb1 100644 Binary files a/memberflow-api/libs/memberflow-data-1.0-SNAPSHOT.jar and b/memberflow-api/libs/memberflow-data-1.0-SNAPSHOT.jar differ diff --git a/memberflow-api/src/main/java/com/denniseckerskorn/controllers/finance_management/PaymentController.java b/memberflow-api/src/main/java/com/denniseckerskorn/controllers/finance_management/PaymentController.java index a53b52f..ff95ced 100644 --- a/memberflow-api/src/main/java/com/denniseckerskorn/controllers/finance_management/PaymentController.java +++ b/memberflow-api/src/main/java/com/denniseckerskorn/controllers/finance_management/PaymentController.java @@ -34,8 +34,15 @@ public class PaymentController { throws DuplicateEntityException, EntityNotFoundException { Invoice invoice = paymentService.getInvoiceById(dto.getInvoiceId()); + if (invoice == null) { + throw new EntityNotFoundException("Invoice not found with ID: " + dto.getInvoiceId()); + } + Payment saved = paymentService.save(dto.toEntityWithInvoice(invoice)); + if (saved == null) { + throw new DuplicateEntityException("Payment already exists for invoice ID: " + dto.getInvoiceId()); + } return new ResponseEntity<>(new PaymentDTO(saved), HttpStatus.CREATED); } @@ -46,8 +53,15 @@ public class PaymentController { throws EntityNotFoundException, InvalidDataException { Invoice invoice = paymentService.getInvoiceById(dto.getInvoiceId()); + if (invoice == null) { + throw new EntityNotFoundException("Invoice not found with ID: " + dto.getInvoiceId()); + } Payment updated = paymentService.update(dto.toEntityWithInvoice(invoice)); + if (updated == null) { + throw new InvalidDataException("Payment update failed for invoice ID: " + dto.getInvoiceId()); + } + return new ResponseEntity<>(new PaymentDTO(updated), HttpStatus.OK); } diff --git a/memberflow-data/src/main/java/com/denniseckerskorn/services/finance_services/PaymentService.java b/memberflow-data/src/main/java/com/denniseckerskorn/services/finance_services/PaymentService.java index 4fb78fe..83b99ca 100644 --- a/memberflow-data/src/main/java/com/denniseckerskorn/services/finance_services/PaymentService.java +++ b/memberflow-data/src/main/java/com/denniseckerskorn/services/finance_services/PaymentService.java @@ -50,7 +50,7 @@ public class PaymentService extends AbstractService { Payment savedPayment = super.save(payment); - invoice.setPayment(savedPayment); + //invoice.setPayment(savedPayment); invoice.setStatus(StatusValues.PAID); invoiceService.update(invoice); @@ -58,14 +58,28 @@ public class PaymentService extends AbstractService { } - @Override @Transactional - public Payment update(Payment entity) throws EntityNotFoundException, InvalidDataException { - logger.info("Updating payment: {}", entity); - validate(entity); - updateInvoiceStatus(entity); - return super.update(entity); + public Payment update(Payment payment) throws EntityNotFoundException, InvalidDataException { + logger.info("Updating payment: {}", payment); + validate(payment); + + Payment existingPayment = findById(payment.getId()); + + if (existingPayment == null) { + throw new EntityNotFoundException("Payment not found"); + } + + Invoice invoice = payment.getInvoice(); + + if (invoice == null || invoiceService.findById(invoice.getId()) == null) { + throw new EntityNotFoundException("Invoice not found for the payment"); + } + + invoice.setStatus(StatusValues.PAID); + invoiceService.update(invoice); + + return super.update(payment); } @Override @@ -102,18 +116,17 @@ public class PaymentService extends AbstractService { } } - @Transactional - private void updateInvoiceStatus(Payment payment) { - Invoice invoice = payment.getInvoice(); - invoice.setStatus(StatusValues.PAID); - invoiceService.update(invoice); - } - @Transactional public void removePayment(Integer paymentId) { Payment payment = findById(paymentId); + if (payment == null) { + throw new EntityNotFoundException("The payment doesn't exist."); + } + Invoice invoice = payment.getInvoice(); + super.deleteById(paymentId); + invoice.setStatus(StatusValues.NOT_PAID); invoiceService.update(invoice); } diff --git a/memberflow-frontend.zip b/memberflow-frontend.zip deleted file mode 100644 index 0a01b46..0000000 Binary files a/memberflow-frontend.zip and /dev/null differ diff --git a/memberflow-frontend/src/components/forms/IVATypeManager.jsx b/memberflow-frontend/src/components/forms/IVATypeManager.jsx index fd06df9..6067bcc 100644 --- a/memberflow-frontend/src/components/forms/IVATypeManager.jsx +++ b/memberflow-frontend/src/components/forms/IVATypeManager.jsx @@ -1,35 +1,39 @@ import React, { useEffect, useState } from "react"; import api from "../../api/axiosConfig"; - +import ErrorMessage from "../common/ErrorMessage"; +import "../styles/ContentArea.css"; const IVATypeManager = () => { const [ivaTypes, setIvaTypes] = useState([]); const [newIva, setNewIva] = useState({ percentage: "", description: "" }); - const [error, setError] = useState(""); + const [errorMsg, setErrorMsg] = useState(""); + const [successMsg, setSuccessMsg] = useState(""); + + useEffect(() => { + fetchIvaTypes(); + }, []); const fetchIvaTypes = async () => { try { const res = await api.get("/iva-types/getAll"); setIvaTypes(res.data); + setErrorMsg(""); } catch (err) { console.error(err); - setError("Error al obtener los tipos de IVA."); + setErrorMsg("❌ Error al obtener los tipos de IVA."); } }; - useEffect(() => { - fetchIvaTypes(); - }, []); - const handleChange = (e) => { const { name, value } = e.target; setNewIva((prev) => ({ ...prev, [name]: value })); }; const handleAddIva = async () => { - setError(""); + setErrorMsg(""); + setSuccessMsg(""); if (!newIva.percentage || isNaN(newIva.percentage)) { - setError("Introduce un porcentaje válido."); + setErrorMsg("❌ Introduce un porcentaje válido."); return; } @@ -41,10 +45,11 @@ const IVATypeManager = () => { await api.post("/iva-types/create", payload); setNewIva({ percentage: "", description: "" }); + setSuccessMsg("✅ Tipo de IVA añadido correctamente."); fetchIvaTypes(); } catch (err) { console.error(err); - setError("No se pudo crear el tipo de IVA."); + setErrorMsg("❌ No se pudo crear el tipo de IVA."); } }; @@ -52,66 +57,70 @@ const IVATypeManager = () => { if (!window.confirm("¿Seguro que deseas eliminar este tipo de IVA?")) return; try { await api.delete(`/iva-types/deleteById/${id}`); + setSuccessMsg("✅ Tipo de IVA eliminado correctamente."); fetchIvaTypes(); } catch (err) { console.error(err); - setError("No se pudo eliminar el tipo de IVA."); + setErrorMsg("❌ No se pudo eliminar el tipo de IVA."); } }; return ( -
+

Gestión de Tipos de IVA

- {error &&

{error}

} + + -
- +
- - -
- - - - - - - - - - - {ivaTypes.map((iva) => ( - - - - - +
+
IDPorcentajeDescripciónAcciones
{iva.id}{iva.percentage}%{iva.description} - -
+ + + + + + - ))} - -
IDPorcentajeDescripciónAcciones
+ + + {ivaTypes.map((iva) => ( + + {iva.id} + {iva.percentage}% + {iva.description} + + + + + ))} + + +
); }; diff --git a/memberflow-frontend/src/components/forms/InvoiceForm.jsx b/memberflow-frontend/src/components/forms/InvoiceForm.jsx index 9f47838..ad27428 100644 --- a/memberflow-frontend/src/components/forms/InvoiceForm.jsx +++ b/memberflow-frontend/src/components/forms/InvoiceForm.jsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react"; import InvoiceLineItem from "./InvoiceLineItem"; import ErrorMessage from "../common/ErrorMessage"; import api from "../../api/axiosConfig"; +import "../styles/ContentArea.css"; const InvoiceForm = () => { const [students, setStudents] = useState([]); @@ -17,9 +18,7 @@ const InvoiceForm = () => { api.get("/products-services/getAll").then((res) => setProducts(res.data)); }, []); - const addLine = () => { - setLines([...lines, { productServiceId: "", quantity: 1 }]); - }; + const addLine = () => setLines([...lines, { productServiceId: "", quantity: 1 }]); const updateLine = (index, updatedLine) => { const newLines = [...lines]; @@ -31,27 +30,21 @@ const InvoiceForm = () => { setLines(lines.filter((_, i) => i !== index)); }; - const calculateSubtotal = () => { - return lines.reduce((acc, line) => { + const calculateSubtotal = () => + lines.reduce((acc, line) => { const product = products.find((p) => p.id === parseInt(line.productServiceId)); - if (!product) return acc; - return acc + product.price * (parseInt(line.quantity) || 1); + return product ? acc + product.price * (parseInt(line.quantity) || 1) : acc; }, 0); - }; - const calculateIVA = () => { - return lines.reduce((acc, line) => { + const calculateIVA = () => + lines.reduce((acc, line) => { const product = products.find((p) => p.id === parseInt(line.productServiceId)); if (!product || !product.ivaType) return acc; const quantity = parseInt(line.quantity) || 1; - const iva = product.ivaType.percentage || 0; - return acc + (product.price * quantity * (iva / 100)); + return acc + (product.price * quantity * (product.ivaType.percentage / 100)); }, 0); - }; - const calculateTotal = () => { - return calculateSubtotal() + calculateIVA(); - }; + const calculateTotal = () => calculateSubtotal() + calculateIVA(); const createInvoice = async () => { setError(""); @@ -84,7 +77,6 @@ const InvoiceForm = () => { setCreatedInvoice(res.data); alert("✅ Factura creada correctamente."); } catch (err) { - console.error(err); setError("❌ Error al crear la factura: " + (err.response?.data?.message || err.message)); } }; @@ -102,7 +94,6 @@ const InvoiceForm = () => { document.body.appendChild(link); link.click(); } catch (err) { - console.error(err); setError("❌ Error al descargar el PDF."); } }; @@ -112,32 +103,30 @@ const InvoiceForm = () => {

Crear Factura

- - +
+ + - - setDate(e.target.value)} - /> + + setDate(e.target.value)} + required + /> +

Productos / Servicios

- {lines.map((line, i) => ( { /> ))} -
-

Subtotal: {calculateSubtotal().toFixed(2)} €

-

IVA: {calculateIVA().toFixed(2)} €

-

Total estimado con IVA: {calculateTotal().toFixed(2)} €

+
+
+

Subtotal:

+

{calculateSubtotal().toFixed(2)} €

+
+
+

IVA:

+

{calculateIVA().toFixed(2)} €

+
+
+

Total con IVA:

+

{calculateTotal().toFixed(2)} €

+
-
- - +
+ +
{createdInvoice && (
-

Factura creada: #{createdInvoice.id}

-

Total con IVA: {createdInvoice.total.toFixed(2)} €

- +

Factura #{createdInvoice.id} creada correctamente

+

Total: {createdInvoice.total.toFixed(2)} €

+
)}
diff --git a/memberflow-frontend/src/components/forms/InvoiceLineItem.jsx b/memberflow-frontend/src/components/forms/InvoiceLineItem.jsx index c71ab8d..926a097 100644 --- a/memberflow-frontend/src/components/forms/InvoiceLineItem.jsx +++ b/memberflow-frontend/src/components/forms/InvoiceLineItem.jsx @@ -8,47 +8,51 @@ const InvoiceLineItem = ({ index, line, products, onUpdate, onRemove }) => { const selectedProduct = products.find(p => p.id === parseInt(line.productServiceId)); const quantity = parseInt(line.quantity) || 1; const price = selectedProduct?.price || 0; - const ivaPercentage = selectedProduct?.ivaType?.percentage || 0; - const totalConIVA = price * quantity * (1 + ivaPercentage / 100); + const iva = selectedProduct?.ivaType?.percentage || 0; + const totalConIVA = price * quantity * (1 + iva / 100); return ( -
- +
+
+ + - + + - {selectedProduct && ( - <> -
- {selectedProduct.name}: {selectedProduct.description} + {selectedProduct && ( +
+

{selectedProduct.name}: {selectedProduct.description}

+

IVA: {iva}%

+

Total con IVA: {totalConIVA.toFixed(2)} €

-
- Total con IVA: {totalConIVA.toFixed(2)} € -
- - )} + )} - + +
); }; diff --git a/memberflow-frontend/src/components/forms/PaymentForm.jsx b/memberflow-frontend/src/components/forms/PaymentForm.jsx index 3b463d7..c0283a7 100644 --- a/memberflow-frontend/src/components/forms/PaymentForm.jsx +++ b/memberflow-frontend/src/components/forms/PaymentForm.jsx @@ -88,81 +88,82 @@ const PaymentForm = () => {

Registrar Pago de una Factura

- - - - {invoices.length > 0 && ( - <> - - + + {students.map((s) => + s.user ? ( + - ))} - - - )} + ) : null + )} + - {invoices.length === 0 && selectedUserId && ( -

- No hay facturas pendientes de pago para este estudiante. -

- )} + {invoices.length > 0 && ( + <> + + + + )} - {selectedInvoiceId && ( - <> - - setAmount(e.target.value)} - /> + {invoices.length === 0 && selectedUserId && ( +

+ No hay facturas pendientes de pago para este estudiante. +

+ )} - - + {selectedInvoiceId && ( + <> + + setAmount(e.target.value)} + required + /> - - + + - - - )} + + + + + + )} +
); diff --git a/memberflow-frontend/src/components/layout/MainLayout.jsx b/memberflow-frontend/src/components/layout/MainLayout.jsx index 9cde849..3b125ad 100644 --- a/memberflow-frontend/src/components/layout/MainLayout.jsx +++ b/memberflow-frontend/src/components/layout/MainLayout.jsx @@ -24,7 +24,6 @@ import MembershipList from '../lists/MembershipList'; import InvoiceForm from '../forms/InvoiceForm'; import InvoiceList from '../lists/InvoiceList'; import PaymentForm from '../forms/PaymentForm'; -import PaymentList from '../lists/PaymentList'; import ProductForm from '../forms/ProductForm'; import ProductList from '../lists/ProductList'; import IVATypeManager from '../forms/IVATypeManager'; @@ -67,7 +66,6 @@ const MainLayout = () => { } /> } /> } /> - } /> } /> } /> } /> diff --git a/memberflow-frontend/src/components/layout/SidebarAdmin.jsx b/memberflow-frontend/src/components/layout/SidebarAdmin.jsx index 288f6de..9727f08 100644 --- a/memberflow-frontend/src/components/layout/SidebarAdmin.jsx +++ b/memberflow-frontend/src/components/layout/SidebarAdmin.jsx @@ -40,7 +40,6 @@ const SidebarAdmin = () => { - diff --git a/memberflow-frontend/src/components/lists/ProductList.jsx b/memberflow-frontend/src/components/lists/ProductList.jsx index cbb8ce3..c437baf 100644 --- a/memberflow-frontend/src/components/lists/ProductList.jsx +++ b/memberflow-frontend/src/components/lists/ProductList.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import api from "../../api/axiosConfig"; +import ErrorMessage from "../common/ErrorMessage"; import "../styles/ContentArea.css"; const ProductList = () => { @@ -7,29 +8,35 @@ const ProductList = () => { const [ivaTypes, setIvaTypes] = useState([]); const [editIndex, setEditIndex] = useState(null); const [editableProduct, setEditableProduct] = useState(null); - - const fetchProducts = async () => { - const res = await api.get("/products-services/getAll"); - setProducts(res.data); - }; - - const fetchIvaTypes = async () => { - const res = await api.get("/iva-types/getAll"); - setIvaTypes(res.data); - }; + const [errorMsg, setErrorMsg] = useState(""); + const [successMsg, setSuccessMsg] = useState(""); useEffect(() => { fetchProducts(); fetchIvaTypes(); }, []); - useEffect(() => { - console.log("IVA Types cargados:", ivaTypes); -}, [ivaTypes]); + const fetchProducts = async () => { + try { + const res = await api.get("/products-services/getAll"); + setProducts(res.data); + } catch (err) { + setErrorMsg("❌ Error al cargar los productos."); + } + }; + + const fetchIvaTypes = async () => { + try { + const res = await api.get("/iva-types/getAll"); + setIvaTypes(res.data); + } catch (err) { + setErrorMsg("❌ Error al cargar tipos de IVA."); + } + }; const startEdit = (index) => { setEditIndex(index); - setEditableProduct({ ...products[index] }); // contiene ivaTypeId directamente + setEditableProduct({ ...products[index] }); }; const cancelEdit = () => { @@ -47,152 +54,145 @@ const ProductList = () => { const handleSave = async () => { try { const dto = { - id: editableProduct.id, - name: editableProduct.name, - price: parseFloat(editableProduct.price), - type: editableProduct.type, - status: editableProduct.status, - ivaTypeId: editableProduct.ivaTypeId, + ...editableProduct, + price: parseFloat(editableProduct.price) }; - await api.put("/products-services/update", dto); - alert("✅ Producto actualizado correctamente."); - setEditIndex(null); - setEditableProduct(null); + setSuccessMsg("✅ Producto actualizado correctamente."); + setErrorMsg(""); + cancelEdit(); fetchProducts(); } catch (err) { - console.error(err); - alert("❌ Error al actualizar el producto."); + setErrorMsg("❌ Error al actualizar el producto."); } }; const handleDelete = async (id) => { - if (window.confirm("¿Estás seguro de que deseas eliminar este producto?")) { - await api.delete(`/products-services/deleteById/${id}`); - fetchProducts(); + if (window.confirm("¿Seguro que quieres eliminar este producto?")) { + try { + await api.delete(`/products-services/deleteById/${id}`); + fetchProducts(); + setSuccessMsg("✅ Producto eliminado correctamente."); + setErrorMsg(""); + } catch { + setErrorMsg("❌ Error al eliminar el producto."); + } } }; - const getIVADisplay = (ivaTypeId) => { - const iva = ivaTypes.find((i) => i.id === Number(ivaTypeId)); - return iva ? `${iva.percentage}%` : "Sin IVA"; -}; - + const getIVADisplay = (ivaTypeId) => { + const iva = ivaTypes.find((i) => i.id === Number(ivaTypeId)); + return iva?.percentage ? `${iva.percentage}%` : ""; + }; return ( -
+

Lista de Productos y Servicios

+ + - - - - - - - - - - - - - - {products.map((p, index) => { - const isEditing = index === editIndex; - return ( - - - - - - - - - - ); - })} - -
IDNombrePrecioIVATipoEstadoAcciones
{p.id} - {isEditing ? ( - handleChange("name", e.target.value)} - /> - ) : ( - p.name - )} - - {isEditing ? ( - handleChange("price", e.target.value)} - /> - ) : ( - `${p.price.toFixed(2)} €` - )} - - {isEditing ? ( - - ) : ( - getIVADisplay(p.ivaTypeId) - )} - - {isEditing ? ( - - ) : ( - p.type - )} - - {isEditing ? ( - - ) : ( - p.status - )} - - {isEditing ? ( - <> - - - - ) : ( - <> - - - - )} -
+
+ + + + + + + + + + + + + {products.map((p, index) => { + const isEditing = index === editIndex; + return ( + + + + + + + + + ); + })} + +
NombrePrecio (€)IVATipoEstadoAcciones
+ {isEditing ? ( + handleChange("name", e.target.value)} + /> + ) : ( + p.name + )} + + {isEditing ? ( + handleChange("price", e.target.value)} + /> + ) : ( + `${p.price.toFixed(2)} €` + )} + + {isEditing ? ( + + ) : ( + getIVADisplay(p.ivaTypeId) + )} + + {isEditing ? ( + + ) : ( + p.type + )} + + {isEditing ? ( + + ) : ( + p.status + )} + + {isEditing ? ( + <> + + + + ) : ( + <> + + + + )} +
+
); }; diff --git a/memberflow-frontend/src/components/styles/ContentArea.css b/memberflow-frontend/src/components/styles/ContentArea.css index 2c0bb3d..7ab2780 100644 --- a/memberflow-frontend/src/components/styles/ContentArea.css +++ b/memberflow-frontend/src/components/styles/ContentArea.css @@ -202,20 +202,6 @@ ul li button:hover { background-color: #c0392b; } -.add-button { - padding: 6px 12px; - border: none; - border-radius: 8px; - background-color: #2ecc71; - color: white; - cursor: pointer; - transition: background-color 0.3s; -} - -.add-button:hover { - background-color: #27ae60; -} - .form-column { display: flex; flex-direction: column; @@ -308,6 +294,83 @@ form textarea:focus { color: #7f8c8d; } +.invoice-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-top: 20px; + padding: 20px; + background-color: #f0f4f8; + border-radius: 12px; +} + +.invoice-line-summary { + background-color: #ecf0f1; + padding: 12px; + border-radius: 10px; + margin-top: 10px; + margin-bottom: 10px; + font-size: 15px; + color: #2d3436; +} + +/* Botones generales */ + +.add-button { + padding: 6px 12px; + border: none; + border-radius: 8px; + background-color: #2ecc71; + color: white; + cursor: pointer; + transition: background-color 0.3s; +} + +.add-button:hover { + background-color: #27ae60; +} + +.btn { + padding: 6px 12px; + border: none; + border-radius: 8px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s, transform 0.2s; +} + +/* Botón primario (azul) */ +.btn-primary { + background-color: #0984e3; + color: white; +} + +.btn-primary:hover { + background-color: #74b9ff; + transform: translateY(-2px); +} + +/* Botón de peligro (rojo) */ +.btn-danger { + background-color: #e74c3c; + color: white; +} + +.btn-danger:hover { + background-color: #c0392b; + transform: translateY(-2px); +} + +/* Botón de éxito (verde) */ +.btn-success { + background-color: #2ecc71; + color: white; +} + +.btn-success:hover { + background-color: #27ae60; + transform: translateY(-2px); +} diff --git a/memberflow-frontend/src/components/styles/Sidebar.css b/memberflow-frontend/src/components/styles/Sidebar.css index 9ad3338..4909083 100644 --- a/memberflow-frontend/src/components/styles/Sidebar.css +++ b/memberflow-frontend/src/components/styles/Sidebar.css @@ -7,6 +7,7 @@ height: 100vh; display: flex; flex-direction: column; + overflow: hidden; /* Evita overflow exterior */ } @@ -15,6 +16,7 @@ font-size: 1.1rem; font-weight: 600; color: #0984e3; + margin: 0; } .sidebar button { @@ -40,9 +42,18 @@ .sidebar-scroll { flex: 1; overflow-y: auto; - padding: 0 20px 20px; + padding: 0 20px 80px; /* MÁS espacio inferior */ display: flex; flex-direction: column; gap: 10px; min-height: 0; +} + +/* Opcional: para asegurar buen scroll en todos los navegadores */ +.sidebar-scroll::-webkit-scrollbar { + width: 6px; +} +.sidebar-scroll::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; } \ No newline at end of file diff --git a/memberflow-frontend/src/pages/admin/AdminDashboard.jsx b/memberflow-frontend/src/pages/admin/AdminDashboard.jsx index f7568f8..1f3ce9e 100644 --- a/memberflow-frontend/src/pages/admin/AdminDashboard.jsx +++ b/memberflow-frontend/src/pages/admin/AdminDashboard.jsx @@ -5,29 +5,60 @@ import '../../components/styles/DashboardCards.css'; const AdminDashboard = () => { const navigate = useNavigate(); - const options = [ - { title: '➕ Crear Usuario', route: '/admin/user-management/users/create' }, - { title: '📜 Listar Usuarios', route: '/admin/user-management/users/list' }, - { title: '🔔 Crear Notificación', route: '/admin/user-management/notifications/create' }, - { title: '🕓 Crear Historial', route: '/admin/user-management/student-history/create' }, - { title: '📜 Ver Historial', route: '/admin/user-management/student-history/list' }, - { title: '🎓 Gestión Estudiantes', route: '/admin/user-management/students' }, - { title: '👨‍🏫 Gestión Profesores', route: '/admin/user-management/teachers' }, - { title: '🛡️ Gestión Admins', route: '/admin/user-management/admins' }, - { title: '📚 Gestión de Clases', route: '/admin/class-management' }, - { title: '💰 Finanzas', route: '/admin/finance' } + const sections = [ + { + title: '👥 Administración de Usuarios', + options: [ + { title: '➕ Crear Usuario', route: '/admin/user-management/users/create' }, + { title: '📋 Ver Usuarios', route: '/admin/user-management/users/list' }, + { title: '🔔 Crear Notificación', route: '/admin/user-management/notifications/create' }, + { title: '📨 Ver Notificaciones', route: '/admin/user-management/notifications/list' }, + { title: '🕒 Crear Historial', route: '/admin/user-management/student-history/create' }, + { title: '📜 Ver Historial', route: '/admin/user-management/student-history/list' }, + ] + }, + { + title: '📚 Administración de Clases', + options: [ + { title: '➕ Crear Grupo', route: '/admin/class-management/training-groups/create' }, + { title: '👥 Ver Grupos', route: '/admin/class-management/training-groups/list' }, + { title: '🧑‍🏫 Administrar Grupos', route: '/admin/class-management/training-groups/manage-students' }, + { title: '🗓️ Ver Horario', route: '/admin/class-management/training-groups/view-timetable' }, + { title: '📆 Ver Sesiones', route: '/admin/class-management/training-session/list' }, + { title: '📝 Registrar Asistencia', route: '/admin/class-management/assistance/create' }, + { title: '📋 Ver Asistencias', route: '/admin/class-management/assistance/list' }, + { title: '➕ Detalles de las Membresías', route: '/admin/class-management/memberships/details' }, + { title: '🏷️ Asignar Membresías', route: '/admin/class-management/memberships/list' }, + ] + }, + { + title: '💵 Finanzas', + options: [ + { title: '🧾 Crear Factura', route: '/admin/finance/invoices/create' }, + { title: '📄 Ver Facturas', route: '/admin/finance/invoices/list' }, + { title: '📄 Nuevo Pago', route: '/admin/finance/payments/create' }, + { title: '🛒 Añadir Productos', route: '/admin/finance/products/create' }, + { title: '📦 Ver Productos', route: '/admin/finance/products/list' }, + { title: '💱 Añadir Tipo de IVA', route: '/admin/finance/ivatypes/create' }, + ] + }, ]; return (

Panel de Control del Administrador

-
- {options.map((opt, index) => ( -
navigate(opt.route)}> - {opt.title} + {sections.map((section, index) => ( +
+

{section.title}

+
+ {section.options.map((opt, idx) => ( +
navigate(opt.route)}> + {opt.title} +
+ ))}
- ))} -
+
+ ))}
); };