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:
89
frontend/Frontend-Hotel/src/components/Buttons/Button.css
Normal file
89
frontend/Frontend-Hotel/src/components/Buttons/Button.css
Normal file
@@ -0,0 +1,89 @@
|
||||
.btn {
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
border-radius: 999px; /* Súper redondo */
|
||||
background-color: #521414;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
cursor: pointer;
|
||||
color: #ffffff;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.2);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background-color: #4a0d0d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0px 12px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn.secundary {
|
||||
border: none;
|
||||
padding: 20px 40px;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
border-radius: 999px;
|
||||
background-color: #eeeeee;
|
||||
cursor: pointer;
|
||||
color: #555555;
|
||||
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.15);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn.secundary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0px 12px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
background-color: #eeeeee;
|
||||
color: #555;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
border-radius: 999px;
|
||||
padding: 20px 40px;
|
||||
box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.15);
|
||||
border: none;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn.gray {
|
||||
background-color: #ccc;
|
||||
color: #333;
|
||||
}
|
||||
*/
|
||||
18
frontend/Frontend-Hotel/src/components/Buttons/Button.jsx
Normal file
18
frontend/Frontend-Hotel/src/components/Buttons/Button.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
// src/components/Button.jsx
|
||||
import React from "react";
|
||||
import "./Button.css"; // estilos separados
|
||||
|
||||
function Button({ label, onClick, variant = "primary" | "secundary"}) {
|
||||
return (
|
||||
<div>
|
||||
<h1>Login</h1>
|
||||
<Button variant="primary" onClick={() => alert("Ingresando...")}>
|
||||
Iniciar sesión
|
||||
</Button>
|
||||
<Button variant="secundary" onClick={() => alert("Registro")}>
|
||||
Registrarse
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Button;
|
||||
118
frontend/Frontend-Hotel/src/components/ExcelExportButton.jsx
Normal file
118
frontend/Frontend-Hotel/src/components/ExcelExportButton.jsx
Normal file
@@ -0,0 +1,118 @@
|
||||
import React, { useContext } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import * as XLSX from "xlsx";
|
||||
import { FiDownload } from "react-icons/fi";
|
||||
import { langContext } from "../context/LenguageContext";
|
||||
|
||||
export default function ExcelExportButton({
|
||||
data,
|
||||
columns,
|
||||
filenamePrefix,
|
||||
sheetName,
|
||||
dataTransform = null,
|
||||
className = "",
|
||||
style = {},
|
||||
}) {
|
||||
const { lang } = useContext(langContext);
|
||||
|
||||
const handleExportToExcel = () => {
|
||||
if (!data || data.length === 0) {
|
||||
alert(lang === "es" ? "No hay datos para exportar" : "No data to export");
|
||||
return;
|
||||
}
|
||||
|
||||
let dataToExport = dataTransform ? dataTransform(data) : data;
|
||||
|
||||
const excelData = dataToExport.map((row) => {
|
||||
const excelRow = {};
|
||||
columns.forEach((column) => {
|
||||
if (column.key) {
|
||||
let value = row[column.key];
|
||||
|
||||
// Handle render functions (extract text value)
|
||||
if (column.render && typeof column.render === "function") {
|
||||
// Try to get the raw value if render is used
|
||||
value = row[column.key];
|
||||
}
|
||||
|
||||
if (value instanceof Date) {
|
||||
value = isNaN(value.getTime())
|
||||
? ""
|
||||
: value.toLocaleDateString(lang === "es" ? "es-MX" : "en-US");
|
||||
}
|
||||
|
||||
const header =
|
||||
typeof column.header === "string"
|
||||
? column.header
|
||||
: (lang === "es" ? column.header?.es : column.header?.en) ||
|
||||
column.key;
|
||||
|
||||
excelRow[header] = value || "";
|
||||
}
|
||||
});
|
||||
return excelRow;
|
||||
});
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(excelData);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
|
||||
const finalSheetName =
|
||||
typeof sheetName === "string"
|
||||
? sheetName
|
||||
: (lang === "es" ? sheetName?.es : sheetName?.en) || "Sheet1";
|
||||
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, finalSheetName);
|
||||
|
||||
const today = new Date();
|
||||
const dateStr = today.toISOString().split("T")[0];
|
||||
const filename = `${filenamePrefix}-${dateStr}.xlsx`;
|
||||
|
||||
XLSX.writeFile(workbook, filename);
|
||||
};
|
||||
|
||||
const defaultStyle = {
|
||||
backgroundColor: "#ffcb05",
|
||||
color: "#fff",
|
||||
padding: "10px 20px",
|
||||
fontWeight: "bold",
|
||||
border: "none",
|
||||
borderRadius: "8px",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
fontSize: "14px",
|
||||
transition: "background-color 0.3s ease",
|
||||
...style,
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleExportToExcel}
|
||||
className={className}
|
||||
style={defaultStyle}
|
||||
onMouseEnter={(e) => (e.target.style.backgroundColor = "#f4b400")}
|
||||
onMouseLeave={(e) => (e.target.style.backgroundColor = "#ffcb05")}
|
||||
>
|
||||
<FiDownload />
|
||||
{lang === "es" ? "Exportar a Excel" : "Export to Excel"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
ExcelExportButton.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
header: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
key: PropTypes.string.isRequired,
|
||||
render: PropTypes.func,
|
||||
})
|
||||
).isRequired,
|
||||
filenamePrefix: PropTypes.string.isRequired,
|
||||
sheetName: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
|
||||
.isRequired,
|
||||
dataTransform: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
.date-range-filter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.date-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 0 2px #f4f4f4;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
min-width: 150px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
height: 42px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.date-input:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fcd200;
|
||||
}
|
||||
|
||||
.date-input:hover {
|
||||
box-shadow: 0 0 0 2px #e6e6e6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.date-range-filter {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.date-input-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import './DateRangeFilter.css';
|
||||
|
||||
const DateRangeFilter = ({
|
||||
dateRange,
|
||||
onDateChange,
|
||||
labels = { from: 'From:', to: 'To:' },
|
||||
lang = 'en'
|
||||
}) => {
|
||||
const fromLabel = lang === 'en' ? labels.from : 'Desde:';
|
||||
const toLabel = lang === 'en' ? labels.to : 'Hasta:';
|
||||
|
||||
return (
|
||||
<div className="date-range-filter">
|
||||
<div className="date-input-group">
|
||||
<label htmlFor="date-from" className="date-label">
|
||||
{fromLabel}
|
||||
</label>
|
||||
<input
|
||||
id="date-from"
|
||||
type="date"
|
||||
className="date-input"
|
||||
value={dateRange.from}
|
||||
onChange={(e) => onDateChange({ ...dateRange, from: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="date-input-group">
|
||||
<label htmlFor="date-to" className="date-label">
|
||||
{toLabel}
|
||||
</label>
|
||||
<input
|
||||
id="date-to"
|
||||
type="date"
|
||||
className="date-input"
|
||||
value={dateRange.to}
|
||||
onChange={(e) => onDateChange({ ...dateRange, to: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateRangeFilter;
|
||||
|
||||
265
frontend/Frontend-Hotel/src/components/Filters/Filters.css
Normal file
265
frontend/Frontend-Hotel/src/components/Filters/Filters.css
Normal file
@@ -0,0 +1,265 @@
|
||||
/* src/styles/Filters.css */
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: #1a1a1a;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.new-payment-btn {
|
||||
background-color: #FFD700;
|
||||
color: #800000;
|
||||
font-weight: bold;
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.new-payment-btn:hover {
|
||||
background-color: #fcd200;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.new-payment-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.report-page {
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
select,
|
||||
input[type="date"] {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 0 2px #f4f4f4;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
max-width: 250px;
|
||||
min-width: 150px;
|
||||
width: auto;
|
||||
flex: 0 1 auto;
|
||||
/* appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none; */
|
||||
/* background-image: url("data:image/svg+xml,%3Csvg fill='gold' viewBox='0 0 24 24' width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 2px center;
|
||||
background-size: 18px 18px; */
|
||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
/* display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px; */
|
||||
|
||||
}
|
||||
|
||||
select:focus,
|
||||
input[type="date"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #fcd200;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.filter-select, .filter-date {
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1rem;
|
||||
color: #333;
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.1);
|
||||
appearance: none;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg fill="gold" ...>...</svg>'); /* usa ícono amarillo aquí */
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.75rem center;
|
||||
background-size: 1rem;
|
||||
}
|
||||
|
||||
.filter-select:focus, .filter-date:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px #FFD700;
|
||||
}
|
||||
|
||||
.status-button {
|
||||
border: 2px solid;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.status-button.approve {
|
||||
color: #28a745;
|
||||
border-color: #28a745;
|
||||
background-color: #e6fff0;
|
||||
}
|
||||
|
||||
.status-button.approved {
|
||||
color: #28a745;
|
||||
border-color: #28a745;
|
||||
background-color: #e6fff0;
|
||||
}
|
||||
|
||||
.status-button.reject {
|
||||
color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
background-color: #ffe6e6;
|
||||
}
|
||||
.status-button.rejected {
|
||||
color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
background-color: #ffe6e6;
|
||||
}
|
||||
|
||||
.status-button.pending {
|
||||
color: #f0ad4e;
|
||||
border-color: #f0ad4e;
|
||||
background-color: #fff7e6;
|
||||
}
|
||||
|
||||
.status-button.paid {
|
||||
color: #28a745;
|
||||
border-color: #28a745;
|
||||
background-color: #e6fff0;
|
||||
}
|
||||
|
||||
|
||||
.income-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
padding: 15px 20px;
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-container {
|
||||
display: flex;
|
||||
gap: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* .summary-card {
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 10px 20px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
font-size: 1rem;
|
||||
min-width: 160px;
|
||||
} */
|
||||
|
||||
|
||||
.summary-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 15px 25px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
min-width: 140px;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
align-items: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*Date*/
|
||||
.date-filter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.date-filter-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding-left: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
/*
|
||||
.status-button.approve {
|
||||
background-color: #8bed92;
|
||||
border: 1.5px solid #33a544;
|
||||
color: #33a544;
|
||||
border-radius: 20px;
|
||||
padding: 5px 15px;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
} */
|
||||
|
||||
|
||||
.page-filters {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.approved-page h2 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.status-button {
|
||||
border: 2px solid #28a745;
|
||||
color: #28a745;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: bold;
|
||||
background-color: #e6fff0;
|
||||
}
|
||||
|
||||
.status-button.inactive {
|
||||
border-color: #dc3545;
|
||||
color: #dc3545;
|
||||
background-color: #ffe6e6;
|
||||
}
|
||||
*/
|
||||
99
frontend/Frontend-Hotel/src/components/Filters/Filters.jsx
Normal file
99
frontend/Frontend-Hotel/src/components/Filters/Filters.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
// src/components/Filters/Filters.jsx
|
||||
import React from 'react';
|
||||
import './Filters.css';
|
||||
|
||||
const Filters = ({
|
||||
areaOptions = [],
|
||||
statusOptions = [],
|
||||
selectedArea,
|
||||
selectedStatus,
|
||||
onAreaChange,
|
||||
onStatusChange,
|
||||
startDate,
|
||||
endDate,
|
||||
onStartDateChange,
|
||||
onEndDateChange,
|
||||
}) => {
|
||||
return (
|
||||
<div className="filters-container">
|
||||
{areaOptions.length > 0 && (
|
||||
<div className="filter-select-wrapper">
|
||||
<select className="filter-select" value={selectedArea} onChange={onAreaChange}>
|
||||
{areaOptions.map((area, index) => (
|
||||
<option key={index} value={area}>
|
||||
{area}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{statusOptions.length > 0 && (
|
||||
<div className="filter-select-wrapper">
|
||||
<select className="filter-select" value={selectedStatus} onChange={onStatusChange}>
|
||||
{statusOptions.map((status, index) => (
|
||||
<option key={index} value={status}>
|
||||
{status}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{onStartDateChange && (
|
||||
<input
|
||||
type="date"
|
||||
className="filter-date"
|
||||
value={startDate}
|
||||
onChange={onStartDateChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{onEndDateChange && (
|
||||
<input
|
||||
type="date"
|
||||
className="filter-date"
|
||||
value={endDate}
|
||||
onChange={onEndDateChange}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Filters;
|
||||
|
||||
|
||||
|
||||
// src/components/Filters.jsx
|
||||
// import React from 'react';
|
||||
// import '../styles/Filters.css';
|
||||
|
||||
// export default function Filters({ area, status, startDate, endDate, onChange }) {
|
||||
// return (
|
||||
// <div className="filters">
|
||||
// <select value={area} onChange={(e) => onChange('area', e.target.value)}>
|
||||
// <option value="">Area: Hotel, Restaurant</option>
|
||||
// <option value="Hotel">Hotel</option>
|
||||
// <option value="Restaurant">Restaurant</option>
|
||||
// </select>
|
||||
|
||||
// <select value={status} onChange={(e) => onChange('status', e.target.value)}>
|
||||
// <option value="">Status: Active, Inactive</option>
|
||||
// <option value="Active">Active</option>
|
||||
// <option value="Inactive">Inactive</option>
|
||||
// </select>
|
||||
|
||||
// <input
|
||||
// type="date"
|
||||
// value={startDate}
|
||||
// onChange={(e) => onChange('startDate', e.target.value)}
|
||||
// />
|
||||
// <input
|
||||
// type="date"
|
||||
// value={endDate}
|
||||
// onChange={(e) => onChange('endDate', e.target.value)}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
32
frontend/Frontend-Hotel/src/components/FormInput.jsx
Normal file
32
frontend/Frontend-Hotel/src/components/FormInput.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
// export default function FormInput({ label, name, value, onChange, ...rest }) {
|
||||
// return (
|
||||
// <div className="form-input">
|
||||
// {label && <label>{label}</label>}
|
||||
// <input
|
||||
// name={name}
|
||||
// value={value}
|
||||
// onChange={onChange}
|
||||
// {...rest}
|
||||
// />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
export default function FormInput({ label, name, value, onChange, placeholder, type = "text", ...props }) {
|
||||
return (
|
||||
<div>
|
||||
<label>{label}</label>
|
||||
<input
|
||||
type={type}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
frontend/Frontend-Hotel/src/components/FormSelect.jsx
Normal file
13
frontend/Frontend-Hotel/src/components/FormSelect.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
export default function FormSelect({ label, name, value, onChange, options }) {
|
||||
return (
|
||||
<div>
|
||||
<label>{label}</label>
|
||||
<select name={name} value={value} onChange={onChange}>
|
||||
<option value="">Select</option>
|
||||
{options.map(opt => (
|
||||
<option key={opt} value={opt}>{opt}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
frontend/Frontend-Hotel/src/components/Inputs/Input.css
Normal file
8
frontend/Frontend-Hotel/src/components/Inputs/Input.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 1rem;
|
||||
}
|
||||
17
frontend/Frontend-Hotel/src/components/Inputs/Input.jsx
Normal file
17
frontend/Frontend-Hotel/src/components/Inputs/Input.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
// src/components/Input.jsx
|
||||
import React from "react";
|
||||
import "./Input.css";
|
||||
|
||||
function Input({ type = "text", placeholder, value, onChange }) {
|
||||
return (
|
||||
<input
|
||||
className="input"
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Input;
|
||||
357
frontend/Frontend-Hotel/src/components/Layout.jsx
Normal file
357
frontend/Frontend-Hotel/src/components/Layout.jsx
Normal file
@@ -0,0 +1,357 @@
|
||||
// Layout.jsx
|
||||
// import React, { 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";
|
||||
|
||||
// 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" },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// 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" },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: "Expenses",
|
||||
// basePath: "/app/report",
|
||||
// submenu: [
|
||||
// { label: "Report", route: "/app/report" },
|
||||
// { label: "New Expense", route: "/app/new-expense" },
|
||||
// { label: "Payments", route: "/app/payments" },
|
||||
// { label: "Monthly Payments", route: "/app/monthly-payments" },
|
||||
// { label: "Id", route: "/app/expense-id" },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: "Inventory",
|
||||
// basePath: "/app/products",
|
||||
// submenu: [
|
||||
// { label: "Products", route: "/app/products" },
|
||||
// { label: "New Product", route: "/app/new-product" },
|
||||
// { label: "Report", route: "/app/inventory-report" },
|
||||
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: "Payroll",
|
||||
// basePath: "/app/payroll",
|
||||
// submenu: [
|
||||
// { label: "Report", route: "/app/payroll" },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// label: "Hotel",
|
||||
// basePath: "/app/properties",
|
||||
// submenu: [
|
||||
// { label: "Properties", route: "/app/properties" },
|
||||
// // { label: "New Property", route: "/app/properties/:id"},
|
||||
// ],
|
||||
// },
|
||||
|
||||
// ];
|
||||
|
||||
// export default function Layout() {
|
||||
// const navigate = useNavigate();
|
||||
// const location = useLocation();
|
||||
// const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
// const toggleSidebar = () => setSidebarOpen(!isSidebarOpen);
|
||||
// const isDetailPage = /^\/app\/properties\/\d+$/.test(location.pathname);
|
||||
// const isSettingsPage = location.pathname === "/app/settings";
|
||||
// // Detectar qué menú está activo según la ruta actual
|
||||
// // const activeSection = menuConfig.find(section =>
|
||||
// // location.pathname.startsWith(section.basePath)
|
||||
// // );
|
||||
|
||||
// // const activeSection = menuConfig.find(section =>
|
||||
// // section.submenu.some(item => location.pathname.startsWith(item.route))
|
||||
// // );
|
||||
|
||||
// // Encuentra la sección activa o ignórala si es una ruta especial como "/app/properties/:id"
|
||||
// const activeSection = menuConfig.find(section =>
|
||||
// section.submenu.some(item => location.pathname.startsWith(item.route))
|
||||
// );
|
||||
|
||||
// // Si no hay sección activa, es una página sin menú (como detalles)
|
||||
// const activeSubmenu = activeSection?.submenu || [];
|
||||
|
||||
// return (
|
||||
// <div className="dashboard-layout">
|
||||
// {/* Sidebar */}
|
||||
// {isSidebarOpen && (
|
||||
// <aside className="sidebar">
|
||||
// <nav>
|
||||
// <NavLink to="/app/income">Dashboards</NavLink>
|
||||
// <NavLink to="/app/pending-approval">Expenses to be approved</NavLink>
|
||||
// <NavLink to="/app/report">Expenses</NavLink>
|
||||
// <NavLink to="/app/products">Inventory</NavLink>
|
||||
// <NavLink to="/app/payroll">Payroll</NavLink>
|
||||
// <NavLink to="/app/properties">Hotel</NavLink>
|
||||
// </nav>
|
||||
// </aside>
|
||||
// )}
|
||||
|
||||
// {/* Main content */}
|
||||
// <div className="main-content">
|
||||
// {/* Topbar */}
|
||||
// <div className="topbar">
|
||||
// <div className="topbar-header">
|
||||
// {/* Oculta título si estamos en /app/settings */}
|
||||
// {!isSettingsPage && (
|
||||
// <div className="topbar-title">{activeSection?.label}</div>
|
||||
// )}
|
||||
|
||||
// <div className="topbar-icons">
|
||||
// <FaBell className="topbar-icon" />
|
||||
// <FaCog
|
||||
// className="topbar-icon cursor-pointer"
|
||||
// onClick={() => navigate("/app/settings")}
|
||||
// />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/*Oculta submenú si es página de detalles o settings */}
|
||||
// {!isDetailPage && !isSettingsPage && (
|
||||
// <div className="topbar-submenu">
|
||||
// {activeSubmenu.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>
|
||||
// );
|
||||
|
||||
// return (
|
||||
// <div className="dashboard-layout">
|
||||
// {/* Sidebar */}
|
||||
// {isSidebarOpen && (
|
||||
// <aside className="sidebar">
|
||||
// <nav>
|
||||
// <NavLink to="/app/income">Dashboards</NavLink>
|
||||
// <NavLink to="/app/pending-approval">Expenses to be approved</NavLink>
|
||||
// <NavLink to="/app/report">Expenses</NavLink>
|
||||
// <NavLink to="/app/products">Inventory</NavLink>
|
||||
// <NavLink to="/app/payroll">Payroll</NavLink>
|
||||
// <NavLink to="/app/properties">Hotel</NavLink>
|
||||
// </nav>
|
||||
// </aside>
|
||||
// )}
|
||||
|
||||
// {/* Contenido principal */}
|
||||
// <div className="main-content">
|
||||
// {/* ÚNICO Topbar */}
|
||||
// <div className="topbar">
|
||||
// {/* Línea superior: título + iconos */}
|
||||
// <div className="topbar-header">
|
||||
// <div className="topbar-title">{activeSection?.label}</div>
|
||||
// <div className="topbar-icons">
|
||||
// <FaBell className="topbar-icon" />
|
||||
// <FaCog className="topbar-icon cursor-pointer" onClick={() => navigate("/app/settings")} />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// {/* Línea inferior: submenú dinámico */}
|
||||
|
||||
// {/* Línea inferior: submenú dinámico */}
|
||||
// {!isDetailPage && (
|
||||
// <div className="topbar-submenu">
|
||||
// {activeSubmenu.map((item, index) => (
|
||||
// <NavLink
|
||||
// key={index}
|
||||
// to={item.route}
|
||||
// className={({ isActive }) =>
|
||||
// isActive ? "submenu-link active" : "submenu-link"
|
||||
// }
|
||||
// >
|
||||
// {item.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </div>
|
||||
// )}
|
||||
|
||||
// {/* <div className="topbar-submenu">
|
||||
// {activeSubmenu.map((item, index) => (
|
||||
// <NavLink
|
||||
// key={index}
|
||||
// to={item.route}
|
||||
// className={({ isActive }) =>
|
||||
// isActive ? "submenu-link active" : "submenu-link"
|
||||
// }
|
||||
// >
|
||||
// {item.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </div> */}
|
||||
// </div>
|
||||
|
||||
// {/* Aquí va el contenido de la página */}
|
||||
// <div className="content">
|
||||
// <Outlet />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
//}
|
||||
|
||||
|
||||
|
||||
//{ label: "Property", route: "/app/properties/:id" },
|
||||
|
||||
// import React from "react";
|
||||
// import { Outlet, useLocation, NavLink } from "react-router-dom";
|
||||
// import { menuConfig } from "../constants/menuConfig";
|
||||
// import { FaBell, FaCog } from "react-icons/fa";
|
||||
// import "../styles/Dashboard.css";
|
||||
|
||||
// export default function Layout() {
|
||||
// const location = useLocation();
|
||||
// const pathname = location.pathname;
|
||||
|
||||
// // Encuentra la sección activa
|
||||
// const activeSectionKey = Object.keys(menuConfig).find((key) =>
|
||||
// pathname.startsWith(menuConfig[key].baseRoute)
|
||||
// );
|
||||
// const activeSection = menuConfig[activeSectionKey];
|
||||
// const activeSubmenu = activeSection?.submenu || [];
|
||||
|
||||
// return (
|
||||
// <div className="dashboard-layout">
|
||||
// {/* SIDEBAR */}
|
||||
// <aside className="sidebar">
|
||||
// <nav>
|
||||
// {Object.entries(menuConfig).map(([key, section]) => (
|
||||
// <NavLink
|
||||
// key={key}
|
||||
// to={section.baseRoute}
|
||||
// className={({ isActive }) =>
|
||||
// isActive ? "menu-items a active" : "menu-items a"
|
||||
// }
|
||||
// >
|
||||
// {section.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </nav>
|
||||
// </aside>
|
||||
|
||||
// {/* CONTENIDO PRINCIPAL */}
|
||||
// <div className="main-content">
|
||||
// {/* TOPBAR */}
|
||||
// <div className="topbar">
|
||||
// <div className="topbar-header">
|
||||
// <div className="topbar-title">{activeSection?.label}</div>
|
||||
// <div className="topbar-icons">
|
||||
// <FaBell className="topbar-icon" />
|
||||
// <FaCog className="topbar-icon" />
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// <div className="topbar-submenu">
|
||||
// {activeSubmenu.map((item, index) => (
|
||||
// <NavLink
|
||||
// key={index}
|
||||
// to={item.route}
|
||||
// className={({ isActive }) =>
|
||||
// isActive ? "submenu-link active" : "submenu-link"
|
||||
// }
|
||||
// >
|
||||
// {item.label}
|
||||
// </NavLink>
|
||||
// ))}
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
|
||||
// {/* CONTENIDO */}
|
||||
// <div className="content">
|
||||
// <Outlet />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // src/components/Layout.jsx
|
||||
// import React, { useState } from "react";
|
||||
// import { Outlet, NavLink } from "react-router-dom";
|
||||
// import "../styles/Dashboard.css";
|
||||
|
||||
// export default function Layout() {
|
||||
// const [isSidebarOpen, setSidebarOpen] = useState(true);
|
||||
|
||||
// const toggleSidebar = () => {
|
||||
// setSidebarOpen(!isSidebarOpen);
|
||||
// };
|
||||
|
||||
// return (
|
||||
// <div className="dashboard-layout">
|
||||
|
||||
// {/* Sidebar */}
|
||||
// {isSidebarOpen && (
|
||||
// <aside className="sidebar">
|
||||
// <nav>
|
||||
// <h1></h1>
|
||||
// <NavLink to="/app/dashboard">Dashboards</NavLink>
|
||||
// <NavLink to="/app/expenses-to-approve">Expenses to be approved</NavLink>
|
||||
// <NavLink to="/app/expenses">Expenses</NavLink>
|
||||
// <NavLink to="/app/inventory">Inventory</NavLink>
|
||||
// <NavLink to="/app/payroll">Payroll</NavLink>
|
||||
// <NavLink to="/app/hotel">Hotel</NavLink>
|
||||
// <NavLink to="/app/income">Income</NavLink>
|
||||
// <NavLink to="/app/employees">Employees</NavLink>
|
||||
// <NavLink to="/app/contracts">Contracts</NavLink>
|
||||
// <NavLink to="/app/payments">Payments</NavLink>
|
||||
// <NavLink to="/app/pending-approval">PendingApproval</NavLink>
|
||||
// </nav>
|
||||
// </aside>
|
||||
// )}
|
||||
|
||||
// {/* Contenedor principal */}
|
||||
// <div className="main-content">
|
||||
// {/* Topbar */}
|
||||
// <div className="topbar">
|
||||
// <button onClick={toggleSidebar} style={{ fontSize: "1.2rem", marginRight: "auto", background: "none", border: "none", color: "white", cursor: "pointer" }}>
|
||||
// ☰
|
||||
// </button>
|
||||
// <span >Dashboard</span> {/* Cambia esto dinámicamente si deseas */}
|
||||
// </div>
|
||||
|
||||
// {/* Contenido de cada página */}
|
||||
// <div className="content">
|
||||
// <Outlet />
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// <NavLink to="/app/users">Users</NavLink>
|
||||
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>
|
||||
// );
|
||||
// }
|
||||
@@ -0,0 +1,74 @@
|
||||
/* components/Modals/ConfirmationModal.css */
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(122, 0, 41, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 6px;
|
||||
width: 450px;
|
||||
max-width: 95%;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
|
||||
border: 5px solid #7a0029;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background-color: #7a0029;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
padding: 10px 25px;
|
||||
font-size: 18px;
|
||||
border-radius: 30px;
|
||||
border: 2px solid #7a0029;
|
||||
cursor: pointer;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.modal-button.yes {
|
||||
color: green;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.modal-button.no {
|
||||
color: #7a0029;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// components/Modals/ConfirmationModal.jsx
|
||||
import React from 'react';
|
||||
import './ConfirmationModal.css'; // Estilos separados
|
||||
import { useContext } from 'react';
|
||||
import { langContext } from '../../context/LenguageContext';
|
||||
|
||||
export default function ConfirmationModal({ isOpen, statusType, onConfirm, onCancel }) {
|
||||
const { lang } = useContext(langContext);
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h3>{lang === "en" ? "Confirm the status change" : "Confirmar el cambio de estado"}</h3>
|
||||
<button className="close-button" onClick={onCancel}>×</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{lang === "en" ? "Are you sure you received" : "¿Estás seguro de que recibiste"} "{statusType}"?</p>
|
||||
<div className="modal-buttons">
|
||||
<button className="modal-button yes" onClick={onConfirm}>{lang === "en" ? "YES" : "SÍ"}</button>
|
||||
<button className="modal-button no" onClick={onCancel}>{lang === "en" ? "NO" : "NO"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// components/Modals/ConfirmationModal.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './ConfirmationModal.css'; // Estilos separados
|
||||
import { useContext } from 'react';
|
||||
import { langContext } from '../../context/LenguageContext';
|
||||
|
||||
|
||||
export default function ConfirmationOutcome({ isOpen, onConfirm, onCancel, description, taxes, initialAmount, initialTaxId, isFixedPayment }) {
|
||||
/*const [formHousekepeer, setFormHousekepeer] = useState(null)/*/
|
||||
const { lang } = useContext(langContext);
|
||||
const [form, setForm] = useState({
|
||||
tax: '',
|
||||
amount: ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setForm({
|
||||
tax: initialTaxId || '',
|
||||
amount: initialAmount || ''
|
||||
});
|
||||
}
|
||||
}, [isOpen, initialAmount, initialTaxId]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
//console.log(name, value);
|
||||
setForm((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
// Envía los valores al padre (Outcomes.jsx)
|
||||
onConfirm(form.tax, form.amount);
|
||||
};
|
||||
return (
|
||||
<div className="modal-overlay">
|
||||
<form onSubmit={handleConfirmClick}>
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h3>{lang === "en" ? "Confirm the status change" : "Confirmar el cambio de estado"}</h3>
|
||||
<button className="close-button" onClick={onCancel}>×</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div>
|
||||
<p>{lang === "en" ? "Payment" : "Pago"}</p>
|
||||
<input name = "PCO" value={description} onChange={handleChange} disabled={true}></input>
|
||||
</div>
|
||||
<div>
|
||||
<p>{lang === "en" ? "Amount" : "Subtotal"}</p>
|
||||
<input type='number' required name = "amount" value={form.amount} onChange={handleChange} disabled={isFixedPayment}></input>
|
||||
</div>
|
||||
<div>
|
||||
<p>{lang === "en" ? "Tax" : "Impuesto"}</p>
|
||||
<select name = "tax" required value={form.tax} onChange={handleChange} disabled={isFixedPayment}>
|
||||
<option value="">{lang === "en" ? "Select a tax" : "Selecciona un impuesto"}</option>
|
||||
{taxes?.map(tax => (
|
||||
<option key={tax.id} value={tax.id}>{tax.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="modal-buttons">
|
||||
<button className="modal-button yes" type='submit'>{lang === "en" ? "PAID" : "PAGAR"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// components/Modals/ConfirmationModal.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './ConfirmationModal.css'; // Estilos separados
|
||||
import { useContext } from 'react';
|
||||
import { langContext } from '../../context/LenguageContext';
|
||||
|
||||
|
||||
export default function ConfirmationOutcome({ isOpen, onConfirm, onCancel, formHousekepeer, idproduct, nameProduct, productStock }) {
|
||||
/*const [formHousekepeer, setFormHousekepeer] = useState(null)/*/
|
||||
const { lang } = useContext(langContext);
|
||||
if (!isOpen) return null;
|
||||
const [PCO, setProduct] = useState(idproduct);
|
||||
const [UCO, setUnits] = useState(null);
|
||||
const [HCO, setHousekeeper] = useState(null);
|
||||
const [form, setForm] = useState({
|
||||
PCO: idproduct || '',
|
||||
UCO: '',
|
||||
HCO: ''
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
if (name === 'UCO') {
|
||||
const numericValue = Number(value);
|
||||
const maxStock = Number(productStock) || 0;
|
||||
if (value === '') {
|
||||
setForm((prev) => ({ ...prev, [name]: '' }));
|
||||
} else if (!isNaN(numericValue) && numericValue >= 0) {
|
||||
const cappedValue = numericValue > maxStock ? maxStock : numericValue;
|
||||
setForm((prev) => ({ ...prev, [name]: cappedValue.toString() }));
|
||||
}
|
||||
} else {
|
||||
setForm((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
// Envía los valores al padre (Outcomes.jsx)
|
||||
onConfirm(form.PCO, form.UCO, form.HCO);
|
||||
};
|
||||
return (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal">
|
||||
<div className="modal-header">
|
||||
<h3>{lang === "en" ? "Confirm the status change" : "Confirmar el cambio de estado"}</h3>
|
||||
<button className="close-button" onClick={onCancel}>×</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div>
|
||||
<p>{lang === "en" ? "Product" : "Producto"}</p>
|
||||
<input
|
||||
name="PCO"
|
||||
value={nameProduct}
|
||||
onChange={handleChange}
|
||||
disabled={true}
|
||||
style={{ color: '#000', width: '100%', padding: '8px' }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p>{lang === "en" ? "Units" : "Unidades"}</p>
|
||||
<input
|
||||
type='number'
|
||||
name="UCO"
|
||||
value={form.UCO}
|
||||
min={0}
|
||||
max={productStock ? Number(productStock) : undefined}
|
||||
disabled={!productStock || Number(productStock) <= 0}
|
||||
title={(!productStock || Number(productStock) <= 0) ? (lang === "es" ? "No se puede consumir cuando el stock es 0" : "Cannot consume when stock is 0") : (lang === "es" ? `Máximo disponible: ${productStock}` : `Maximum available: ${productStock}`)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === '-' || e.key === 'e' || e.key === 'E' || e.key === '+') {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onChange={handleChange}
|
||||
style={{
|
||||
color: '#000',
|
||||
width: '100%',
|
||||
padding: '8px',
|
||||
backgroundColor: (!productStock || Number(productStock) <= 0) ? '#f0f0f0' : 'white',
|
||||
cursor: (!productStock || Number(productStock) <= 0) ? 'not-allowed' : 'text',
|
||||
opacity: (!productStock || Number(productStock) <= 0) ? 0.6 : 1
|
||||
}}
|
||||
/>
|
||||
{(!productStock || Number(productStock) <= 0) && (
|
||||
<p style={{ color: 'red', fontSize: '12px', marginTop: '5px', marginBottom: 0 }}>
|
||||
{lang === "es" ? "No hay stock disponible" : "There is no stock"}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p>{lang === "en" ? "Housekeeper" : "Camarista"}</p>
|
||||
<select
|
||||
name="HCO"
|
||||
value={form.HCO}
|
||||
onChange={handleChange}
|
||||
style={{ color: '#000', width: '100%', padding: '8px' }}
|
||||
>
|
||||
<option value="">{lang === "en" ? "Select a Housekepeer" : "Selecciona una ama de llaves"}</option>
|
||||
{formHousekepeer && formHousekepeer.map(HK => (
|
||||
<option key={HK.rfc_employee} value={HK.rfc_employee}>{HK.name_emp}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="modal-buttons">
|
||||
<button
|
||||
className="modal-button yes"
|
||||
onClick={handleConfirmClick}
|
||||
disabled={!productStock || Number(productStock) <= 0}
|
||||
style={{
|
||||
opacity: (!productStock || Number(productStock) <= 0) ? 0.5 : 1,
|
||||
cursor: (!productStock || Number(productStock) <= 0) ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{lang === "en" ? "SAVE" : "GUARDAR"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
.discard-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
animation: fadeIn 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.discard-modal-box {
|
||||
background: white;
|
||||
padding: 0;
|
||||
border-radius: 12px;
|
||||
width: 450px;
|
||||
max-width: 90%;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
animation: slideUp 0.3s ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.discard-modal-header {
|
||||
background-color: #7a0029;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.discard-modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.discard-modal-body {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
color: #213547;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.discard-modal-body p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.discard-modal-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.discard-modal-button {
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.discard-modal-button-cancel {
|
||||
background-color: #e2e8f0;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
.discard-modal-button-cancel:hover {
|
||||
background-color: #cbd5e0;
|
||||
}
|
||||
|
||||
.discard-modal-button-confirm {
|
||||
background-color: #7a0029;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.discard-modal-button-confirm:hover {
|
||||
background-color: #5a001f;
|
||||
}
|
||||
|
||||
.discard-modal-button:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { langContext } from '../../context/LenguageContext';
|
||||
import './DiscardConfirmModal.css';
|
||||
|
||||
export default function DiscardConfirmModal({ isOpen, message, onConfirm, onCancel }) {
|
||||
const { lang } = useContext(langContext);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="discard-modal-overlay" onClick={onCancel}>
|
||||
<div className="discard-modal-box" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="discard-modal-header">
|
||||
<h3>{lang === "es" ? "Confirmar descarte" : "Confirm Discard"}</h3>
|
||||
</div>
|
||||
<div className="discard-modal-body">
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
<div className="discard-modal-actions">
|
||||
<button className="discard-modal-button discard-modal-button-cancel" onClick={onCancel}>
|
||||
{lang === "es" ? "Cancelar" : "Cancel"}
|
||||
</button>
|
||||
<button className="discard-modal-button discard-modal-button-confirm" onClick={onConfirm}>
|
||||
{lang === "es" ? "Confirmar" : "Confirm"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
57
frontend/Frontend-Hotel/src/components/Modals/Modal.css
Normal file
57
frontend/Frontend-Hotel/src/components/Modals/Modal.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-box {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
width: 350px;
|
||||
max-width: 90%;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.modal-box h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #111;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: #f1f1f1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background-color: #8b0000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn--primary:hover {
|
||||
background-color: #a00000;
|
||||
}
|
||||
22
frontend/Frontend-Hotel/src/components/Modals/Modal.jsx
Normal file
22
frontend/Frontend-Hotel/src/components/Modals/Modal.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React, { useState } from 'react';
|
||||
import './Modal.css'; // Asegúrate de tener el estilo del modal
|
||||
|
||||
export default function Modal({ isOpen, closeModal }) {
|
||||
return (
|
||||
isOpen && (
|
||||
<div className="modal-overlay">
|
||||
<div className="modal-box">
|
||||
<h3>Enter your email address and we'll send a new password to your email.</h3>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
className="modal-input"
|
||||
/>
|
||||
<button onClick={closeModal} className="btn btn--primary">
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
53
frontend/Frontend-Hotel/src/components/Navbar/Navbar.css
Normal file
53
frontend/Frontend-Hotel/src/components/Navbar/Navbar.css
Normal file
@@ -0,0 +1,53 @@
|
||||
z/* src/styles/Navbar.css */
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #4a0d0d; /* Marrón oscuro */
|
||||
padding: 12px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar__nav {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.nav__link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.nav__link.active {
|
||||
border-bottom: 2px solid #f8d47b; /* Amarillo suave */
|
||||
}
|
||||
|
||||
.navbar__actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background-color: #f8d47b; /* Amarillo */
|
||||
color: #4a0d0d;
|
||||
}
|
||||
|
||||
.btn--secondary {
|
||||
background-color: #ddd;
|
||||
color: #333;
|
||||
}
|
||||
33
frontend/Frontend-Hotel/src/components/Navbar/Navbar.jsx
Normal file
33
frontend/Frontend-Hotel/src/components/Navbar/Navbar.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// src/components/Navbar.jsx
|
||||
import { NavLink, useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "../../context/AuthContext.jsx";
|
||||
import "./Navbar.css"; // estilos separados
|
||||
|
||||
export default function Navbar() {
|
||||
const { isAuthenticated, logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
navigate("/"); // 👈 al cerrar sesión vuelve al Login
|
||||
};
|
||||
|
||||
return (
|
||||
<header className="navbar">
|
||||
<div className="navbar__brand"></div>
|
||||
|
||||
<div className="navbar__actions">
|
||||
{isAuthenticated ? (
|
||||
<button className="btn btn--secondary" onClick={handleLogout}>
|
||||
Logout
|
||||
</button>
|
||||
) : (
|
||||
<NavLink to="/" className="btn btn--primary">
|
||||
Login
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
53
frontend/Frontend-Hotel/src/components/Sidebar.jsx
Normal file
53
frontend/Frontend-Hotel/src/components/Sidebar.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// Sidebar.jsx
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { menuConfig } from "../constants/menuConfig";
|
||||
|
||||
const Sidebar = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<aside className="sidebar">
|
||||
<nav>
|
||||
{Object.entries(menuConfig).map(([key, section]) => (
|
||||
<button
|
||||
key={key}
|
||||
className="sidebar-link"
|
||||
onClick={() => navigate(section.basePath)}
|
||||
>
|
||||
{section.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
||||
// import React, { useState } from "react";
|
||||
// import { NavLink } from "react-router-dom";
|
||||
// import "./..styles/Sidebar.css";
|
||||
|
||||
// export default function Sidebar() {
|
||||
// const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
// return (
|
||||
// <div className={`sidebar ${collapsed ? "collapsed" : ""}`}>
|
||||
// <div className="sidebar-header">
|
||||
// <button className="toggle-btn" onClick={() => setCollapsed(!collapsed)}>
|
||||
// ☰
|
||||
// </button>
|
||||
// {!collapsed && <span className="title">Dashboard</span>}
|
||||
// </div>
|
||||
|
||||
// <nav className="sidebar-nav">
|
||||
// <NavLink to="/app/dashboard">Dashboards</NavLink>
|
||||
// <NavLink to="/app/expenses-to-approve">Expenses to be approved</NavLink>
|
||||
// <NavLink to="/app/expenses">Expenses</NavLink>
|
||||
// <NavLink to="/app/inventory">Inventory</NavLink>
|
||||
// <NavLink to="/app/payroll">Payroll</NavLink>
|
||||
// <NavLink to="/app/hotel">Hotel</NavLink>
|
||||
// </nav>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
100
frontend/Frontend-Hotel/src/components/SummaryCard.css
Normal file
100
frontend/Frontend-Hotel/src/components/SummaryCard.css
Normal file
@@ -0,0 +1,100 @@
|
||||
.summary-card-enhanced {
|
||||
background: linear-gradient(145deg, #ffffff, #f8f9fa);
|
||||
border-radius: 16px;
|
||||
padding: 20px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
min-width: 180px;
|
||||
flex: 1;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.summary-card-enhanced:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.summary-card-enhanced.primary {
|
||||
background: linear-gradient(145deg, #ffffff, #f8f9fa);
|
||||
color: #333;
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card-amount {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin: 8px 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.card-percentage {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #28a745;
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(0, 0, 0, 0.1);
|
||||
border-top-color: #fcd200;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.summary-card-enhanced {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.card-amount {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.summary-card-enhanced {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.card-amount {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
31
frontend/Frontend-Hotel/src/components/SummaryCard.jsx
Normal file
31
frontend/Frontend-Hotel/src/components/SummaryCard.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import './SummaryCard.css';
|
||||
|
||||
const SummaryCard = ({
|
||||
title,
|
||||
amount,
|
||||
isLoading = false,
|
||||
isPrimary = false
|
||||
}) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={`summary-card-enhanced ${isPrimary ? 'primary' : ''}`}>
|
||||
<div className="card-loading">
|
||||
<div className="loading-spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`summary-card-enhanced ${isPrimary ? 'primary' : ''}`}>
|
||||
<div className="card-header">
|
||||
<h3 className="card-title">{title}</h3>
|
||||
</div>
|
||||
<div className="card-amount">${amount || 0}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryCard;
|
||||
|
||||
28
frontend/Frontend-Hotel/src/components/Switch.jsx
Normal file
28
frontend/Frontend-Hotel/src/components/Switch.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
const Switch = ({ checked, onChange, disabled = false }) => {
|
||||
return (
|
||||
<label className="inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<div
|
||||
className={`relative inline-block w-10 h-6 transition duration-200 ease-in-out rounded-full ${
|
||||
checked ? 'bg-green-500' : 'bg-gray-300'
|
||||
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform duration-200 ease-in-out ${
|
||||
checked ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default Switch;
|
||||
152
frontend/Frontend-Hotel/src/components/Table/HotelTable copy.jsx
Normal file
152
frontend/Frontend-Hotel/src/components/Table/HotelTable copy.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import './Table.css';
|
||||
|
||||
export default function Table({ columns, data }) {
|
||||
return (
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th key={col.key}>{col.header}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{columns.map((col, colIndex) => (
|
||||
<td key={colIndex}>
|
||||
{col.render
|
||||
? col.render(row[col.key], row, rowIndex)
|
||||
: col.key === 'propertyId'
|
||||
? (
|
||||
<Link
|
||||
to={`/app/properties/${row[col.key]}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
textDecoration: 'none',
|
||||
color: '#003366',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
{row[col.key]}
|
||||
<FaExternalLinkAlt size={12} />
|
||||
</Link>
|
||||
)
|
||||
: row[col.key]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
// import React from 'react';
|
||||
// import { Link } from 'react-router-dom';
|
||||
// import { FaArrowRight } from 'react-icons/fa';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row)
|
||||
// : col.key === 'propertyId'
|
||||
// ? (
|
||||
// <Link to={`/app/properties/${row[col.key]}`} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
// {row[col.key]}
|
||||
// <FaArrowRight size={12} />
|
||||
// </Link>
|
||||
// )
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
|
||||
// import React from 'react';
|
||||
// import { Link } from 'react-router-dom';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row) // usar render si está definido
|
||||
// : col.key === 'propertyId'
|
||||
// ? <Link to={`/app/properties/${row[col.key]}`}>{row[col.key]}</Link>
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
// import React from 'react';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row) // usar render si está definido
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
152
frontend/Frontend-Hotel/src/components/Table/HotelTable.jsx
Normal file
152
frontend/Frontend-Hotel/src/components/Table/HotelTable.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import './Table.css';
|
||||
|
||||
export default function Table({ columns, data }) {
|
||||
return (
|
||||
<table className="custom-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th key={col.key} style={col.headerStyle || {}}>{col.header}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.map((row, rowIndex) => (
|
||||
<tr key={rowIndex}>
|
||||
{columns.map((col, colIndex) => (
|
||||
<td key={colIndex}>
|
||||
{col.render
|
||||
? col.render(row[col.key], row)
|
||||
: col.key === 'propertyId'
|
||||
? (
|
||||
<Link
|
||||
to={`/app/properties/${row[col.key]}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
textDecoration: 'none',
|
||||
color: '#003366',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
{row[col.key]}
|
||||
<FaExternalLinkAlt size={12} />
|
||||
</Link>
|
||||
)
|
||||
: row[col.key]}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
// import React from 'react';
|
||||
// import { Link } from 'react-router-dom';
|
||||
// import { FaArrowRight } from 'react-icons/fa';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row)
|
||||
// : col.key === 'propertyId'
|
||||
// ? (
|
||||
// <Link to={`/app/properties/${row[col.key]}`} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
|
||||
// {row[col.key]}
|
||||
// <FaArrowRight size={12} />
|
||||
// </Link>
|
||||
// )
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
|
||||
// import React from 'react';
|
||||
// import { Link } from 'react-router-dom';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row) // usar render si está definido
|
||||
// : col.key === 'propertyId'
|
||||
// ? <Link to={`/app/properties/${row[col.key]}`}>{row[col.key]}</Link>
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
// import React from 'react';
|
||||
// import './Table.css';
|
||||
|
||||
// export default function Table({ columns, data }) {
|
||||
// return (
|
||||
// <table className="custom-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// {columns.map((col) => (
|
||||
// <th key={col.key}>{col.header}</th>
|
||||
// ))}
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// {data.map((row, rowIndex) => (
|
||||
// <tr key={rowIndex}>
|
||||
// {columns.map((col, colIndex) => (
|
||||
// <td key={colIndex}>
|
||||
// {col.render
|
||||
// ? col.render(row[col.key], row) // usar render si está definido
|
||||
// : row[col.key]}
|
||||
// </td>
|
||||
// ))}
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
|
||||
// </table>
|
||||
// );
|
||||
// }
|
||||
184
frontend/Frontend-Hotel/src/components/Table/Table.css
Normal file
184
frontend/Frontend-Hotel/src/components/Table/Table.css
Normal file
@@ -0,0 +1,184 @@
|
||||
/* .custom-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.custom-table thead tr {
|
||||
background-color: #7b001a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-table th,
|
||||
.custom-table td {
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #ffcc00;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.custom-table tbody tr {
|
||||
background-color: #ffffff;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 14px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-badge.active {
|
||||
color: green;
|
||||
border: 1px solid green;
|
||||
background-color: rgba(0, 128, 0, 0.192);
|
||||
}
|
||||
|
||||
.status-badge.reject {
|
||||
color: red;
|
||||
border: 1px solid rgb(190, 4, 4);
|
||||
background-color: rgba(255, 0, 0, 0.171);
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
color: rgb(128, 83, 0);
|
||||
border: 1px solid rgb(235, 158, 15);
|
||||
background-color: rgba(248, 176, 42, 0.76);
|
||||
}
|
||||
|
||||
.status-button.approve {
|
||||
background-color: #009e2db0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-button.reject {
|
||||
background-color: #d80404b0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
} */
|
||||
|
||||
.custom-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
/* font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; */
|
||||
font-family: 'Franklin Gothic', 'Arial Narrow', Monserrat, sans-serif
|
||||
}
|
||||
|
||||
.custom-table thead tr {
|
||||
background-color: #7b001a;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*Bordes de línea*/
|
||||
.custom-table th,
|
||||
.custom-table td {
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #ffcc00;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.custom-table tbody tr {
|
||||
font-family: montserrat;
|
||||
background-color: #ffffff;
|
||||
color: #515151;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 14px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-badge.active {
|
||||
color: green;
|
||||
border: 1px solid green;
|
||||
background-color: rgba(0, 128, 0, 0.192);
|
||||
}
|
||||
|
||||
.status-badge.reject {
|
||||
color: red;
|
||||
border: 1px solid rgb(190, 4, 4);
|
||||
background-color: rgba(255, 0, 0, 0.171);
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
color: rgb(128, 83, 0);
|
||||
border: 1px solid rgb(235, 158, 15);
|
||||
background-color: rgba(248, 176, 42, 0.76);
|
||||
}
|
||||
|
||||
.status-badge.paid {
|
||||
color: green;
|
||||
border: 1px solid green;
|
||||
background-color: rgba(0, 128, 0, 0.192);
|
||||
}
|
||||
|
||||
.status-button.approve {
|
||||
background-color: #009e2db0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-button.reject {
|
||||
background-color: #d80404b0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-button.pending {
|
||||
background-color: #e2c000;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-button.paid {
|
||||
background-color: #009e2db0;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 12px;
|
||||
border-radius: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
background-color: transparent;
|
||||
color: #7b001a;
|
||||
font-weight: bold;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*Icono de flecha*/
|
||||
|
||||
.custom-table a:hover {
|
||||
color: #0055aa;
|
||||
}
|
||||
|
||||
.custom-table a:hover svg {
|
||||
transform: translateX(2px);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
8
frontend/Frontend-Hotel/src/components/topbar/Topbar.css
Normal file
8
frontend/Frontend-Hotel/src/components/topbar/Topbar.css
Normal file
@@ -0,0 +1,8 @@
|
||||
/* .topbar {
|
||||
background-color: #e0b200;
|
||||
padding: 1rem;
|
||||
color: #0f0d75;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
} */
|
||||
13
frontend/Frontend-Hotel/src/components/topbar/Topbar.jsx
Normal file
13
frontend/Frontend-Hotel/src/components/topbar/Topbar.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// src/components/Topbar.jsx
|
||||
import React from "react";
|
||||
import "./Topbar.css";
|
||||
|
||||
const Topbar = () => {
|
||||
return (
|
||||
<header className="topbar">
|
||||
<h1 className="logo">Hacienda San Ángel</h1>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Topbar;
|
||||
Reference in New Issue
Block a user