Commit inicial - Sistema de Gestion Hotelera Hacienda San Angel
- Backend Node.js/Express con PostgreSQL - Frontend React 19 con Vite - Docker Compose para orquestacion - Documentacion completa en README.md - Scripts SQL para base de datos - Configuracion de ejemplo (.env.example)
This commit is contained in:
426
frontend/Frontend-Hotel/src/components/Layout2.jsx
Normal file
426
frontend/Frontend-Hotel/src/components/Layout2.jsx
Normal file
@@ -0,0 +1,426 @@
|
||||
// Layout.jsx
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Outlet, NavLink, useLocation } from "react-router-dom";
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import "../styles/Dashboard.css";
|
||||
import { AuthContext } from "../context/AuthContext";
|
||||
import { langContext } from "../context/LenguageContext";
|
||||
import { FaSignOutAlt } from "react-icons/fa";
|
||||
import { menuConfig } from "../constants/menuconfig";
|
||||
|
||||
export default function Layout() {
|
||||
const { toggleLang, lang } = useContext(langContext);
|
||||
const { user, logout } = useContext(AuthContext);
|
||||
console.log('user', user);
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
navigate("/");
|
||||
};
|
||||
const menuConfigWithPermissions = Object.values(menuConfig).map(section => ({
|
||||
...section,
|
||||
hidden:
|
||||
section.label === "Dashboards" ? (user >= 1 && user <= 2 ? false : true) :
|
||||
section.label === "Expenses to be approved" ? (user === 1 || user === 2 ? false : true) :
|
||||
section.label === "Expenses" ? (user >= 1 && user <= 5 ? false : true) :
|
||||
section.label === "Inventory" ? (user >= 1 && user <= 5 ? false : true) :
|
||||
section.label === "Payroll" ? (user >= 1 && user <= 4 ? false : true) :
|
||||
section.label === "Hotel" ? (user === 1 ? false : true) :
|
||||
section.label === "Income" ? (user >= 1 && user <= 4 ? false : true) :
|
||||
section.label === "Housekeeper" ? (user === 6 ? false : true) :
|
||||
false,
|
||||
submenu: section.submenu?.map(item => ({
|
||||
...item,
|
||||
hidden: item.hidden ||
|
||||
(section.label === "Expenses" && user === 2 && item.label !== "Report" && item.label !== "Monthly Report" ? true : false) ||
|
||||
(section.label === "Payroll" && user === 2 && !["Report", "Attendance", "Employees", "Contracts"].includes(item.label) ? true : false) ||
|
||||
(section.label === "Expenses" && user === 5 && !["New Expense", "Purchase Entries", "New Suppliers"].includes(item.label) ? true : false)
|
||||
}))
|
||||
}));
|
||||
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen);
|
||||
// const isDetailPage = /^\/app\/properties\/\d+$/.test(location.pathname);
|
||||
// Detectar páginas de detalle (para ocultar el submenú)
|
||||
const isDetailPage =
|
||||
/^\/app\/properties\/\d+$/.test(location.pathname) || // Propiedades (ya existente)
|
||||
/^\/app\/payroll\/contracts-detail(\/.*)?$/.test(location.pathname) || // Contract Detail
|
||||
/^\/app\/expenses\/detail(\/.*)?$/.test(location.pathname); // Otros detalles si los tienes
|
||||
|
||||
|
||||
const activeSection = menuConfigWithPermissions.find(section => {
|
||||
if (section.hidden) return false;
|
||||
|
||||
const matchesSubmenu = section.submenu.some(item => location.pathname.startsWith(item.route));
|
||||
const matchesBasePath = location.pathname.startsWith(section.basePath);
|
||||
|
||||
if (matchesSubmenu || matchesBasePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.label === "Income" && location.pathname.startsWith("/app/edit-income-form")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.label === "Expenses" && (location.pathname.startsWith("/app/expenses/edit/") || location.pathname.startsWith("/app/expenses/") || location.pathname === "/app/new-monthly")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.label === "Inventory" && location.pathname.startsWith("/app/alter-product/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.label === "Payroll" && (location.pathname.startsWith("/app/payroll/employee/") || location.pathname.startsWith("/app/payroll/contract/") || location.pathname.startsWith("/app/payroll/edit/") || location.pathname.startsWith("/app/payroll/contracts-detail/"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (section.label === "Hotel" && location.pathname.startsWith("/app/properties/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const activeSubmenu = activeSection?.label === "Housekeeper"
|
||||
? [{ label: "Outcomes", spanish_label: "Salidas", route: "/app/housekeeper/outcomes" }]
|
||||
: activeSection?.submenu || [];
|
||||
|
||||
const isLandingPage = location.pathname === '/app' || !activeSection;
|
||||
|
||||
|
||||
return (
|
||||
<div className="dashboard-layout">
|
||||
{/* Sidebar */}
|
||||
{isSidebarOpen && (
|
||||
<aside className="sidebar">
|
||||
<nav>
|
||||
{/*sSolo se muestran secciones que no están ocultas */}
|
||||
{menuConfigWithPermissions
|
||||
.filter(section => !section.hidden)
|
||||
.map((section, index) => (
|
||||
<NavLink key={index} to={section.basePath}>
|
||||
{lang === "en" ? section.label : section.spanish_label}
|
||||
</NavLink>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<div className="main-content">
|
||||
{/* Topbar */}
|
||||
<div className="topbar">
|
||||
<div className="topbar-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
|
||||
{/* Botón + Título (alineados a la izquierda) */}
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
<button onClick={toggleSidebar} className="sidebar-toggle-button">
|
||||
☰
|
||||
</button>
|
||||
|
||||
<div className="topbar-title">
|
||||
{lang === "en" ? activeSection?.label : activeSection?.spanish_label}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Iconos a la derecha */}
|
||||
<div className="topbar-icons" style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
<select
|
||||
|
||||
|
||||
className="language-select"
|
||||
onChange={toggleLang}
|
||||
>
|
||||
<option value="en">EN</option>
|
||||
<option value="es">ES</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
background: "transparent",
|
||||
color: "#ffffffff",
|
||||
border: "none",
|
||||
fontWeight: "bold",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
>
|
||||
<FaSignOutAlt className="topbar-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submenú (solo si no es página de detalle) */}
|
||||
{!isDetailPage && (
|
||||
<div className="topbar-submenu">
|
||||
{activeSubmenu.filter(section => !section.hidden).map((item, index) => (
|
||||
<NavLink
|
||||
key={index}
|
||||
to={item.route}
|
||||
className={({ isActive }) =>
|
||||
isActive ? "submenu-link active" : "submenu-link"
|
||||
}
|
||||
>
|
||||
{lang === "en" ? item.label : item.spanish_label}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Página actual */}
|
||||
<div className="content">
|
||||
{isLandingPage ? (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
gap: '2rem'
|
||||
}}>
|
||||
<img
|
||||
src="/logoHotel.png"
|
||||
alt="Hotel Logo"
|
||||
style={{
|
||||
maxWidth: '300px',
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
}}
|
||||
/>
|
||||
<p style={{
|
||||
fontSize: '1.25rem',
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
maxWidth: '600px',
|
||||
padding: '0 1rem'
|
||||
}}>
|
||||
{lang === "en"
|
||||
? "To get started, select an option from the menu on the left."
|
||||
: "Para comenzar, selecciona una opción del menú de la izquierda."}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<Outlet />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// // Layout.jsx
|
||||
// import React, { use, useContext, useState } from "react";
|
||||
// import { Outlet, NavLink, useLocation } from "react-router-dom";
|
||||
// import { useNavigate } from 'react-router-dom';
|
||||
// import { FaBell, FaCog } from "react-icons/fa";
|
||||
// import "../styles/Dashboard.css";
|
||||
// import { AuthContext } from "../context/AuthContext";
|
||||
// import { langContext } from "../context/LenguageContext";
|
||||
// import { FaSignOutAlt } from "react-icons/fa";
|
||||
|
||||
// export default function Layout() {
|
||||
// const { toggleLang } = useContext(langContext);
|
||||
// const { user, logout } = useContext(AuthContext);
|
||||
// const handleLogout = () => {
|
||||
// logout();
|
||||
// navigate("/");
|
||||
// };
|
||||
// // const {lang, setLang} = useContext(AuthContext);
|
||||
// // console.log(lang);
|
||||
// const menuConfig = [
|
||||
// {
|
||||
// label: "Dashboards",
|
||||
// basePath: "/app/income",
|
||||
// submenu: [
|
||||
// { label: "Income", route: "/app/income" },
|
||||
// // { label: "Expenses", route: "/app/expenses" },
|
||||
// // { label: "Cost per room", route: "/app/cost-per-room" },
|
||||
// // { label: "Budget", route: "/app/budget" },
|
||||
// ],
|
||||
// hidden: user === 1 ? false : true //Solo admin puede ver dashboards,
|
||||
// },
|
||||
// {
|
||||
// label: "Expenses to be approved",
|
||||
// basePath: "/app/pending-approval",
|
||||
// submenu: [
|
||||
// { label: "Pending approval", route: "/app/pending-approval" },
|
||||
// { label: "Approved", route: "/app/approved" },
|
||||
// { label: "Rejected", route: "/app/rejected" },
|
||||
// ],
|
||||
// hidden: user === 1 ? false : true
|
||||
// },
|
||||
// {
|
||||
// label: "Expenses",
|
||||
// basePath: "/app/report-expense",
|
||||
// submenu: [
|
||||
// { label: "Report", route: "/app/report-expense" },
|
||||
// { label: "New Expense", route: "/app/new-expense" },
|
||||
// { label: "Payments", route: "/app/payments" },
|
||||
// { label: "Monthly Payments", route: "/app/monthly-payments" },
|
||||
// { label: "New Monthly Payments", route: "/app/new-monthly" },
|
||||
// { label: "Purchase Entries", route: "/app/purchase-entries" },
|
||||
// ],
|
||||
// hidden: user >= 1 && user <= 3 ? false : true
|
||||
// },
|
||||
// {
|
||||
// label: "Inventory",
|
||||
// basePath: "/app/products",
|
||||
// submenu: [
|
||||
// { label: "Products", route: "/app/products", hidden: user === 5 ? true : false },
|
||||
// { label: "New Product", route: "/app/new-product", hidden: user === 5 ? true : false },
|
||||
// { label: "Report", route: "/app/inventory-report", hidden: user === 5 ? true : false },
|
||||
// { label: "Discard Product", route: "/app/discard-product", hidden: user === 5 ? true : false },
|
||||
// { label: "Adjustments", route: "/app/product-adjustments", hidden: user === 5 ? true : false }
|
||||
// ],
|
||||
// hidden: user >= 1 && user <= 4 ? false : true
|
||||
// },
|
||||
// {
|
||||
// label: "Payroll",
|
||||
// basePath: "/app/payroll",
|
||||
// submenu: [
|
||||
// { label: "Report", route: "/app/payroll" },
|
||||
// { label: "New Contract", route: "/app/payroll/NewPayRoll"},
|
||||
// { label: "Attendance", route: "/app/payroll/attendance" },
|
||||
// { label: "Employees", route: "/app/payroll/employees" },
|
||||
// { label: "New Employee", route: "/app/payroll/newemployee" },
|
||||
// ],
|
||||
// hidden: user >= 1 && user <= 3 ? false : true
|
||||
// },
|
||||
// {
|
||||
// label: "Hotel",
|
||||
// basePath: "/app/properties",
|
||||
// submenu: [
|
||||
// { label: "Properties", route: "/app/properties" },
|
||||
// ],
|
||||
// hidden: user === 1 ? false : true
|
||||
// },
|
||||
// //SECCIÓN "OCULTA" PARA SETTINGS
|
||||
// {
|
||||
// label: "Settings",
|
||||
// basePath: "/app/settings",
|
||||
// submenu: [
|
||||
// { label: "General", route: "/app/settings" },
|
||||
// { label: "Users", route: "/app/settings/users" },
|
||||
// { label: "Units", route: "/app/settings/roles" },
|
||||
// { label: "Room management", route: "/app/settings/room-management" },
|
||||
// ],
|
||||
// hidden: true, //etiqueta para ignorar en sidebar
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const navigate = useNavigate();
|
||||
// const location = useLocation();
|
||||
// const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
// const toggleSidebar = () => setSidebarOpen(!isSidebarOpen);
|
||||
// const isDetailPage = /^\/app\/properties\/\d+$/.test(location.pathname);
|
||||
|
||||
// //Identificar la sección activa, incluyendo settings
|
||||
// const activeSection = menuConfig.find(section =>
|
||||
// section.submenu.some(item => location.pathname.startsWith(item.route))
|
||||
// );
|
||||
|
||||
// const activeSubmenu = activeSection?.submenu || [];
|
||||
|
||||
|
||||
// return (
|
||||
// <div className="dashboard-layout">
|
||||
// {/* Sidebar */}
|
||||
// {isSidebarOpen && (
|
||||
// <aside className="sidebar">
|
||||
// <nav>
|
||||
// {/*sSolo se muestran secciones que no están ocultas */}
|
||||
// {menuConfig
|
||||
// .filter(section => !section.hidden)
|
||||
// .map((section, index) => (
|
||||
// <NavLink key={index} to={section.basePath}>
|
||||
// {section.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </nav>
|
||||
// </aside>
|
||||
// )}
|
||||
|
||||
// {/* Main content */}
|
||||
// <div className="main-content">
|
||||
// {/* Topbar */}
|
||||
// <div className="topbar">
|
||||
// <div className="topbar-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||
|
||||
// {/* Botón + Título (alineados a la izquierda) */}
|
||||
// <div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
// <button onClick={toggleSidebar} className="sidebar-toggle-button">
|
||||
// ☰
|
||||
// </button>
|
||||
|
||||
// <div className="topbar-title">{activeSection?.label}</div>
|
||||
// </div>
|
||||
|
||||
|
||||
// {/* Iconos a la derecha */}
|
||||
// <div className="topbar-icons" style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
// <FaBell className="topbar-icon" />
|
||||
// <FaCog
|
||||
// className="topbar-icon cursor-pointer"
|
||||
// onClick={() => navigate("/app/settings")}
|
||||
// />
|
||||
// <select
|
||||
|
||||
|
||||
// className="language-select"
|
||||
// onChange={toggleLang}
|
||||
// >
|
||||
// <option value="en">EN</option>
|
||||
// <option value="es">ES</option>
|
||||
// </select>
|
||||
// <button
|
||||
// onClick={handleLogout}
|
||||
// style={{
|
||||
// display: "flex",
|
||||
// alignItems: "center",
|
||||
// gap: "6px",
|
||||
// background: "transparent",
|
||||
// color: "#ffffffff",
|
||||
// border: "none",
|
||||
// fontWeight: "bold",
|
||||
// cursor: "pointer"
|
||||
// }}
|
||||
// >
|
||||
// <FaSignOutAlt className="topbar-icon" />
|
||||
// </button>
|
||||
|
||||
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Submenú (solo si no es página de detalle) */}
|
||||
// {!isDetailPage && (
|
||||
// <div className="topbar-submenu">
|
||||
// {activeSubmenu.filter(section => !section.hidden).map((item, index) => (
|
||||
// <NavLink
|
||||
// key={index}
|
||||
// to={item.route}
|
||||
// className={({ isActive }) =>
|
||||
// isActive ? "submenu-link active" : "submenu-link"
|
||||
// }
|
||||
// >
|
||||
// {item.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
|
||||
|
||||
// {/* Página actual */}
|
||||
// <div className="content">
|
||||
// <Outlet />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
Reference in New Issue
Block a user