Files
Hacienda-San-Angel/frontend/Frontend-Hotel/src/pages/Income/IncomeReport.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

394 lines
13 KiB
JavaScript

import React, { useEffect, useState, useContext } from "react";
import { Link, useNavigate } from "react-router-dom";
import { langContext } from "../../context/LenguageContext";
import DateRangeFilter from "../../components/Filters/DateRangeFilter";
import SummaryCard from "../../components/SummaryCard";
import ExcelExportButton from "../../components/ExcelExportButton";
import "../../components/Filters/Filters.css";
import "./IncomeReport.css";
import Table from "../../components/Table/HotelTable";
export default function IncomeReport() {
const navigate = useNavigate();
const { lang } = useContext(langContext);
const [incomeData, setIncomeData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [dateRange, setDateRange] = useState({ from: "", to: "" });
const [accountFilter, setAccountFilter] = useState("");
const [statusFilter, setStatusFilter] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [accounts, setAccounts] = useState([]);
useEffect(() => {
async function fetchAccounts() {
try {
await fetch(
`${import.meta.env.VITE_API_BASE_URL}/incomeshrx/stripedata`
);
const response = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/incomeshrx/accountincome`
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
const data = Array.isArray(result) ? result : result.data || [];
setAccounts(data);
} catch (err) {
console.error("Error fetching accounts:", err);
setAccounts([]);
}
}
fetchAccounts();
}, []);
useEffect(() => {
async function fetchIncomeData() {
try {
setLoading(true);
setError("");
const response = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/incomeshrx/incomehorux`
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
const data = Array.isArray(result) ? result : result.data || [];
const mappedData = data.map((item) => ({
id_hrx_income: item.id_hrx_income,
date: item.date_in,
created_at: item.createddate || item.date_in,
account: item.account_name,
amount: item.account_name === "STRIPE" ? parseFloat(item.amountinvoice || 0).toFixed(2) : parseFloat(item.amount || 0).toFixed(2),
invoiceAmount: item.account_name === "STRIPE" ? parseFloat(item.amount || 0).toFixed(2) : parseFloat(item.amountinvoice || 0).toFixed(2),
invoices: item.invoice,
status: item.status_in ? "Distributed" : "Pending",
categories: item.categories || [],
}));
setIncomeData(mappedData);
setFilteredData(mappedData);
} catch (err) {
console.error("Error fetching income data:", err);
setError(
lang === "es"
? "Error al cargar los datos de ingresos. Por favor, intente de nuevo."
: "Failed to load income data. Please try again."
);
setIncomeData([]);
setFilteredData([]);
} finally {
setLoading(false);
}
}
fetchIncomeData();
}, [lang]);
useEffect(() => {
let filtered = [...incomeData];
if (dateRange.from && dateRange.to) {
filtered = filtered.filter((item) => {
const itemDate = item.created_at?.slice(0, 10);
if (!itemDate) return false;
return itemDate >= dateRange.from && itemDate <= dateRange.to;
});
}
if (accountFilter) {
filtered = filtered.filter((item) => item.account === accountFilter);
}
if (statusFilter) {
filtered = filtered.filter((item) => item.status === statusFilter);
}
setFilteredData(filtered);
}, [dateRange, accountFilter, statusFilter, incomeData]);
const clearFilters = () => {
setDateRange({ from: "", to: "" });
setAccountFilter("");
setStatusFilter("");
};
const formatDate = (dateStr) => {
if (!dateStr) return "";
const date = new Date(dateStr);
if (isNaN(date.getTime())) return "";
const day = String(date.getDate()).padStart(2, "0");
const month = String(date.getMonth() + 1).padStart(2, "0");
const year = date.getFullYear();
return `${day}/${month}/${year}`;
};
const handleStatusClick = (label, row) => {
if (label === "Pending") {
navigate(`/app/edit-income-form/${row.id}`, {
state: {
incomeData: {
id: row.id,
date: row.created_at,
account: row.account,
amount: row.amount,
invoiceAmount: row.invoiceAmount,
invoices: row.invoices,
status: row.status,
categories: row.categories
}
}
});
}
};
const totalIncome = filteredData.reduce((sum, item) => {
return sum + parseFloat(item.amount || 0);
}, 0).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const statusOptions = ["Distributed", "Pending"];
const columns = [
{ header: lang === "es" ? "ID" : "ID", key: "id_hrx_income" },
{
header: lang === "es" ? "FECHA" : "DATE",
key: "created_at",
render: (value) => formatDate(value),
},
{
header: lang === "es" ? "CUENTA" : "ACCOUNT",
key: "account",
render: (value, row) => (
<span
onClick={() => navigate(`/app/edit-income-form/${row.id}`, {
state: {
incomeData: {
id: row.id,
date: row.created_at,
account: row.account,
amount: row.amount,
invoiceAmount: row.invoiceAmount,
invoices: row.invoices,
status: row.status,
categories: row.categories
}
}
})}
style={{
color: "#5D1A2A",
textDecoration: "underline",
cursor: "pointer",
fontWeight: "500",
}}
>
{value}
</span>
),
},
{
header: lang === "es" ? "MONTO" : "AMOUNT",
key: "amount",
render: (value) => {
const num = parseFloat(value);
return `$${num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
},
},
{
header: lang === "es" ? "MONTO FACTURA" : "INVOICE AMOUNT",
key: "invoiceAmount",
render: (value) => {
const num = parseFloat(value);
return `$${num.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
},
},
{
header: lang === "es" ? "FACTURA" : "INVOICE",
key: "invoices",
},
{
header: lang === "es" ? "CATEGORÍAS" : "CATEGORIES",
key: "categories",
render: (value, row) => {
if (!row.categories || row.categories.length === 0) {
return "-";
}
return row.categories.map((category, index) => {
const total = parseFloat(category.total || 0);
const formattedTotal = `$${total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
return (
<div key={index} style={{ marginBottom: index < row.categories.length - 1 ? '4px' : '0' }}>
{category.category}: {formattedTotal}
</div>
);
});
},
},
{
header: lang === "es" ? "ESTADO" : "STATUS",
key: "status",
headerStyle: { textAlign: "center" },
render: (value, row) => (
<div style={{ textAlign: "center" }}>
<button
onClick={() => handleStatusClick(value, row)}
style={{
padding: "4px 12px",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontWeight: "bold",
backgroundColor: value === "Distributed" ? "#4CAF50" : "#FFC107",
color: value === "Distributed" ? "white" : "black",
}}
>
{lang === "es"
? value === "Distributed"
? "Distribuido"
: "Pendiente"
: value}
</button>
</div>
),
},
];
const exportColumns = [
{ header: lang === "es" ? "ID" : "ID", key: "id_hrx_income" },
{ header: lang === "es" ? "FECHA" : "DATE", key: "created_at" },
{ header: lang === "es" ? "CUENTA" : "ACCOUNT", key: "account" },
{ header: lang === "es" ? "MONTO" : "AMOUNT", key: "amount" },
{ header: lang === "es" ? "MONTO FACTURA" : "INVOICE AMOUNT", key: "invoiceAmount" },
{ header: lang === "es" ? "FACTURA" : "INVOICE", key: "invoices" },
{ header: lang === "es" ? "CATEGORÍAS" : "CATEGORIES", key: "categories" },
{ header: lang === "es" ? "ESTADO" : "STATUS", key: "status" },
];
const dataTransform = (data) => {
return data.map((row) => ({
...row,
created_at: formatDate(row.created_at),
amount: row.amount ? `$${parseFloat(row.amount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '',
invoiceAmount: row.invoiceAmount ? `$${parseFloat(row.invoiceAmount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : '',
categories: row.categories && row.categories.length > 0
? row.categories.map(cat => {
const total = parseFloat(cat.total || 0);
const formattedTotal = `$${total.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
return `${cat.category}: ${formattedTotal}`;
}).join('; ')
: '',
status: lang === "es"
? (row.status === "Distributed" ? "Distribuido" : "Pendiente")
: row.status,
}));
};
return (
<div className="income-report-page">
<div className="page-header">
<h2 className="page-title">
{lang === "es" ? "Informe de Ingresos" : "Income Report"}
</h2>
</div>
{error && <div className="error-message">{error}</div>}
<div className="summary-actions-section">
<div className="summary-card-wrapper">
<SummaryCard
title={lang === "es" ? "Total de Ingresos" : "Total Income"}
amount={totalIncome}
isLoading={loading}
/>
</div>
<Link to="/app/new-income-form" className="new-income-btn">
{lang === "es" ? "+ Nuevo Ingreso" : "+ New Income"}
</Link>
</div>
<div className="filters-section">
<DateRangeFilter
dateRange={dateRange}
onDateChange={setDateRange}
lang={lang}
/>
<select
value={accountFilter}
onChange={(e) => setAccountFilter(e.target.value)}
className="filter-select"
>
<option value="">
{lang === "es" ? "Todas las cuentas" : "All Accounts"}
</option>
{accounts.map((account) => (
<option key={account.id_acc_income} value={account.name_acc_income}>
{account.name_acc_income}
</option>
))}
</select>
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="filter-select"
>
<option value="">
{lang === "es" ? "Todos los estados" : "All Status"}
</option>
{statusOptions.map((status, index) => (
<option key={index} value={status}>
{lang === "es"
? status === "Distributed"
? "Distribuido"
: "Pendiente"
: status}
</option>
))}
</select>
<button onClick={clearFilters} className="clear-filters-btn">
{lang === "es" ? "Limpiar filtros" : "Clear filters"}
</button>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: '15px' }}>
<ExcelExportButton
data={filteredData}
columns={exportColumns}
filenamePrefix={lang === "es" ? "informe-ingresos" : "income-report"}
sheetName={lang === "es" ? "Informe de Ingresos" : "Income Report"}
dataTransform={dataTransform}
/>
</div>
<div className="table-section">
{loading ? (
<div className="table-loading">
<div className="loading-spinner-large"></div>
<p>{lang === "es" ? "Cargando..." : "Loading..."}</p>
</div>
) : (
<Table columns={columns} data={filteredData} />
)}
</div>
</div>
);
}