Files
Hacienda-San-Angel/frontend/Frontend-Hotel/src/pages/ExpensesToBeApproval/Rejected.jsx
Consultoria AS 0211bea186 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)
2026-01-17 18:52:34 -08:00

465 lines
15 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import DateRangeFilter from '../../components/Filters/DateRangeFilter';
import SummaryCard from '../../components/SummaryCard';
import '../../components/Filters/Filters.css';
import './Rejected.css';
import Table from '../../components/Table/HotelTable';
import axios from 'axios';
import { Link } from 'react-router-dom';
import { useContext } from 'react';
import { langContext } from '../../context/LenguageContext';
//**////** */ */ APROBADOS Y RECHAZADOS
export default function Rejected() {
const { lang } = useContext(langContext);
const [rejectedExpenses, setRejectedExpenses] = useState([]);
const [filteredExpenses, setFilteredExpenses] = useState([]);
const [dateRange, setDateRange] = useState({ from: '', to: '' });
const [totalRejected, setTotalRejected] = useState(0);
const [mainSupplier, setMainSupplier] = useState('N/A');
const [descriptionFilter, setDescriptionFilter] = useState('');
const [areaFilter, setAreaFilter] = useState('');
//Obtener lista de gastos rechazados
useEffect(() => {
axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/rejectedexpenses')
.then((res) => {
const formatted = res.data.data.map((item, index) => ({
id: index + 1,
description: item.expense_description,
requestDate: new Date(item.request_date).toISOString().split('T')[0],
rejectedDate: new Date(item.reject_date).toISOString().split('T')[0],
area: item.area,
requestedBy: item.requested_by,
amount: parseFloat(item.amount),
supplier: 'N/A'
}));
setRejectedExpenses(formatted);
setFilteredExpenses(formatted);
})
.catch((err) => {
console.error('❌ Error fetching rejected expenses:', err.message);
});
}, []);
//Obtener Total Rejected usando método GET correcto
useEffect(() => {
axios.post(import.meta.env.VITE_API_BASE_URL + '/expenses/totalapproved', { option: 2 })
.then((res) => {
const total = parseFloat(res.data.data);
setTotalRejected(isNaN(total) ? 0 : total);
})
.catch((err) => {
if (err.response) {
console.error(`❌ Error ${err.response.status}: ${err.response.data.message}`);
} else {
console.error('❌ Error fetching total rejected amount:', err.message);
}
});
}, []);
//Obtener proveedor principal
useEffect(() => {
axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/mainsupplier')
.then((res) => {
if (res.data.data.length > 0) {
setMainSupplier(res.data.data[0].supplier);
}
})
.catch((err) => {
if (err.response) {
console.error(`❌ Error ${err.response.status}: ${err.response.data.message}`);
} else {
console.error('❌ Error fetching main supplier:', err.message);
}
});
}, []);
useEffect(() => {
let filtered = [...rejectedExpenses];
if (dateRange.from && dateRange.to) {
const from = new Date(dateRange.from);
const to = new Date(dateRange.to);
filtered = filtered.filter((item) => {
const itemDate = new Date(item.rejectedDate);
return itemDate >= from && itemDate <= to;
});
}
if (descriptionFilter) {
filtered = filtered.filter((item) =>
item.description?.toLowerCase().includes(descriptionFilter.toLowerCase())
);
}
if (areaFilter) {
filtered = filtered.filter((item) => item.area === areaFilter);
}
setFilteredExpenses(filtered);
}, [dateRange, rejectedExpenses, descriptionFilter, areaFilter]);
//Columnas de tabla
const columns = [
{
header: lang === "en" ? "EXPENSE DESCRIPTION" : "DESCRIPCIÓN DEL GASTO",
key: 'description',
render: (text, row) => (
<Link
to={`/app/expenses/${row.id}`}
style={{ color: 'blue', textDecoration: 'underline' }}
>
{text}
</Link>
),
},
{ header: lang === "en" ? "REQUEST DATE" : "FECHA DE SOLICITUD", key: 'requestDate' },
{ header: lang === "en" ? "REJECTED DATE" : "FECHA DE RECHAZO", key: 'rejectedDate' },
{ header: lang === "en" ? "AREA" : "ÁREA", key: 'area' },
{ header: lang === "en" ? "REQUESTED BY" : "SOLICITADO POR", key: 'requestedBy' },
{
header: lang === "en" ? "AMOUNT" : "IMPORTE",
key: 'amount',
render: (amount) => `$${amount.toLocaleString()}`
},
{
header: lang === "en" ? "STATUS" : "ESTADO",
key: 'id',
headerStyle: { textAlign: 'center' },
render: () => (
<div style={{ textAlign: 'center' }}>
<button className="status-button reject" style={{ pointerEvents: 'none' }}>
{lang === "en" ? "REJECTED" : "RECHAZADO"}
</button>
</div>
),
},
];
const uniqueAreas = [...new Set(rejectedExpenses.map(item => item.area).filter(Boolean))].sort();
const clearFilters = () => {
setDateRange({ from: '', to: '' });
setDescriptionFilter('');
setAreaFilter('');
};
return (
<div className="rejected-page">
<div className="page-header">
<h2 className="page-title">
{lang === "en" ? "Rejected Expenses" : "Gastos Rechazados"}
</h2>
</div>
<div className="filters-section">
<input
type="text"
placeholder={lang === "en" ? "Search by description..." : "Buscar por descripción..."}
value={descriptionFilter}
onChange={(e) => setDescriptionFilter(e.target.value)}
className="filter-search"
/>
<select
value={areaFilter}
onChange={(e) => setAreaFilter(e.target.value)}
className="filter-select"
>
<option value="">{lang === "en" ? "All Areas" : "Todas las Áreas"}</option>
{uniqueAreas.map((area, index) => (
<option key={index} value={area}>
{area}
</option>
))}
</select>
<DateRangeFilter
dateRange={dateRange}
onDateChange={setDateRange}
lang={lang}
/>
<button
onClick={clearFilters}
className="clear-filters-btn"
>
{lang === 'es' ? 'Limpiar filtros' : 'Clear filters'}
</button>
</div>
<div className="summary-cards-wrapper">
<SummaryCard
title={lang === "en" ? "Total Rejected" : "Total Rechazado"}
amount={totalRejected}
/>
<div className="info-card">
<h3 className="card-title">{lang === "en" ? "Main Supplier" : "Proveedor Principal"}</h3>
<div className="card-value">{mainSupplier}</div>
</div>
</div>
<div className="table-section">
<Table columns={columns} data={filteredExpenses} />
</div>
</div>
);
}
// import React, { useEffect, useState } from 'react';
// import '../../components/Filters/Filters.css';
// import Table from '../../components/Table/HotelTable';
// import axios from 'axios';
// export default function Rejected() {
// const [rejectedExpenses, setRejectedExpenses] = useState([]);
// const [filteredExpenses, setFilteredExpenses] = useState([]);
// const [dateRange, setDateRange] = useState({ from: '', to: '' });
// const [totalRejected, setTotalRejected] = useState(0);
// const [mainSupplier, setMainSupplier] = useState('N/A');
// // ✅ Cargar lista de gastos rechazados
// useEffect(() => {
// axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/rejectedexpenses')
// .then((res) => {
// const formatted = res.data.data.map((item, index) => ({
// id: index + 1,
// description: item.expense_description,
// requestDate: new Date(item.request_date).toISOString().split('T')[0],
// rejectedDate: new Date(item.reject_date).toISOString().split('T')[0],
// area: item.area,
// requestedBy: item.requested_by,
// amount: parseFloat(item.amount),
// supplier: 'N/A'
// }));
// setRejectedExpenses(formatted);
// setFilteredExpenses(formatted);
// })
// .catch((err) => {
// console.error('Error fetching rejected expenses:', err);
// });
// }, []);
// // ✅ CORREGIDO: Método GET con parámetro en URL
// useEffect(() => {
// axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/totalapproved/2')
// .then((res) => {
// setTotalRejected(parseFloat(res.data.data));
// })
// .catch((err) => {
// console.error('Error fetching total rejected amount:', err);
// });
// }, []);
// // ✅ Obtener Main Supplier
// useEffect(() => {
// axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/mainsupplier')
// .then((res) => {
// if (res.data.data.length > 0) {
// setMainSupplier(res.data.data[0].supplier); // "Costco"
// }
// })
// .catch((err) => {
// console.error('Error fetching main supplier:', err);
// });
// }, []);
// // ✅ Filtrar por fechas
// useEffect(() => {
// if (dateRange.from && dateRange.to) {
// const from = new Date(dateRange.from);
// const to = new Date(dateRange.to);
// const filtered = rejectedExpenses.filter((item) => {
// const itemDate = new Date(item.rejectedDate);
// return itemDate >= from && itemDate <= to;
// });
// setFilteredExpenses(filtered);
// } else {
// setFilteredExpenses(rejectedExpenses);
// }
// }, [dateRange, rejectedExpenses]);
// const columns = [
// {
// header: 'EXPENSE DESCRIPTION',
// key: 'description',
// render: (text) => <span>{text}</span>,
// },
// { header: 'REQUEST DATE', key: 'requestDate' },
// { header: 'REJECTED DATE', key: 'rejectedDate' },
// { header: 'AREA', key: 'area' },
// { header: 'REQUESTED BY', key: 'requestedBy' },
// {
// header: 'AMOUNT',
// key: 'amount',
// render: (amount) => `$${amount.toLocaleString()}`
// },
// {
// header: 'STATUS',
// key: 'id',
// render: () => (
// <button className="status-button reject" style={{ pointerEvents: 'none' }}>
// REJECTED
// </button>
// ),
// },
// ];
// return (
// <div className="rejected-page">
// <h2>REJECTED</h2>
// {/* Filtros de fecha */}
// <div className="page-filters">
// <input
// type="date"
// value={dateRange.from}
// onChange={(e) => setDateRange({ ...dateRange, from: e.target.value })}
// />
// <input
// type="date"
// value={dateRange.to}
// onChange={(e) => setDateRange({ ...dateRange, to: e.target.value })}
// />
// </div>
// {/* Summary Cards */}
// <div className="summary-container">
// <div className="summary-card">
// <strong>Total Rejected</strong>
// <div>${totalRejected.toLocaleString()}</div>
// </div>
// <div className="summary-card">
// <strong>Main Supplier</strong>
// <div>{mainSupplier}</div>
// </div>
// </div>
// {/* Tabla */}
// <Table columns={columns} data={filteredExpenses} />
// </div>
// );
// }
// import React, { useEffect, useState } from 'react';
// import '../../components/Filters/Filters.css';
// import Table from '../../components/Table/HotelTable';
// import axios from 'axios';
// export default function Rejected() {
// const [rejectedExpenses, setRejectedExpenses] = useState([]);
// const [filteredExpenses, setFilteredExpenses] = useState([]);
// const [dateRange, setDateRange] = useState({ from: '', to: '' });
// //Obtener datos reales desde el backend
// useEffect(() => {
// axios.get(import.meta.env.VITE_API_BASE_URL + '/expenses/rejectedexpenses')
// .then((res) => {
// const formatted = res.data.data.map((item, index) => ({
// id: index + 1, // temporal, ya que no viene `id_expense`
// description: item.expense_description,
// requestDate: new Date(item.request_date).toISOString().split('T')[0],
// rejectedDate: new Date(item.reject_date).toISOString().split('T')[0],
// area: item.area,
// requestedBy: item.requested_by,
// amount: parseFloat(item.amount),
// supplier: 'N/A' // ❗ backend no envía proveedor
// }));
// setRejectedExpenses(formatted);
// setFilteredExpenses(formatted);
// })
// .catch((err) => {
// console.error('Error fetching rejected expenses:', err);
// });
// }, []);
// //Filtrar por fechas
// useEffect(() => {
// if (dateRange.from && dateRange.to) {
// const from = new Date(dateRange.from);
// const to = new Date(dateRange.to);
// const filtered = rejectedExpenses.filter((item) => {
// const itemDate = new Date(item.rejectedDate);
// return itemDate >= from && itemDate <= to;
// });
// setFilteredExpenses(filtered);
// } else {
// setFilteredExpenses(rejectedExpenses);
// }
// }, [dateRange, rejectedExpenses]);
// //Columnas de la tabla
// const columns = [
// {
// header: 'EXPENSE DESCRIPTION',
// key: 'description',
// render: (text) => <span>{text}</span>, // ❌ sin enlace por falta de ID
// },
// { header: 'REQUEST DATE', key: 'requestDate' },
// { header: 'REJECTED DATE', key: 'rejectedDate' },
// { header: 'AREA', key: 'area' },
// { header: 'REQUESTED BY', key: 'requestedBy' },
// {
// header: 'AMOUNT',
// key: 'amount',
// render: (amount) => `$${amount.toLocaleString()}`
// },
// {
// header: 'STATUS',
// key: 'id',
// render: () => (
// <button className="status-button reject" style={{ pointerEvents: 'none' }}>
// REJECTED
// </button>
// ),
// },
// ];
// //Cálculo de total rechazado
// const totalAmount = filteredExpenses.reduce((sum, item) => sum + item.amount, 0);
// //Proveedor principal (no aplica)
// const mainSupplier = 'N/A';
// return (
// <div className="rejected-page">
// <h2>REJECTED</h2>
// {/* Filtros de fecha */}
// <div className="page-filters">
// <input
// type="date"
// value={dateRange.from}
// onChange={(e) => setDateRange({ ...dateRange, from: e.target.value })}
// />
// <input
// type="date"
// value={dateRange.to}
// onChange={(e) => setDateRange({ ...dateRange, to: e.target.value })}
// />
// </div>
// {/* Summary Cards */}
// <div className="summary-container">
// <div className="summary-card">
// <strong>Total Rejected</strong>
// <div>${totalAmount.toLocaleString()}</div>
// </div>
// <div className="summary-card">
// <strong>Main Supplier</strong>
// <div>{mainSupplier}</div>
// </div>
// </div>
// {/* Tabla */}
// <Table columns={columns} data={filteredExpenses} />
// </div>
// );
// }