Files
hotel-hacienda/frontend/Frontend-Hotel/src/pages/Inventory/HousekeeperOutcomes.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

315 lines
11 KiB
JavaScript

import React, { useState, useEffect, useContext } from 'react';
import '../../components/Filters/Filters.css';
import './Outcomes.css';
import { langContext } from '../../context/LenguageContext';
import ConfirmationOutcome from '../../components/Modals/ConfirmationOutcome';
import axios from 'axios';
const IMAGE_BASE_URL = import.meta.env.VITE_API_BASE_URL + '/products';
export default function HousekeeperOutcomes() {
const { lang } = useContext(langContext);
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
const [categories, setCategories] = useState([]);
const [nameFilter, setNameFilter] = useState('');
const [categoryFilter, setCategoryFilter] = useState('');
const [stockFilter, setStockFilter] = useState('');
const [formHousekepeer, setFormHousekepeer] = useState(null)
const [modalOpen, setModalOpen] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const [selectedProdName, setSelectedProdName] = useState(null);
const [selectedProductStock, setSelectedProductStock] = useState(null);
const handleOpenModal = (id, product, productStock) => {
setSelectedId(id);
setSelectedProdName(product);
setSelectedProductStock(productStock);
//setSelectedStatus(statusType);
//setSelectedStatusId(statusId);
setModalOpen(true);
};
const handleConfirm = (PCO, UCO, HCO) => {
if (!PCO || !UCO || !HCO) return;
const currentdate = new Date();
axios.post(`${import.meta.env.VITE_API_BASE_URL}/products/newconsumptionstock`, {
"product_id": PCO,
"quantity_consumption": UCO,
"date_consumption": currentdate.getFullYear() + "-" + (currentdate.getMonth() + 1) + "-" + currentdate.getDate(),
"rfc_emp": HCO
})
.then((res) => {
console.log(res || 'Outcome realizado correctamente');
// Cerrar el modal y limpiar selección
setModalOpen(false);
setSelectedId(null);
setSelectedProdName(null);
setSelectedProductStock(null);
async function loadProducts() {
try {
const response = await fetch(import.meta.env.VITE_API_BASE_URL + '/products');
const data = await response.json();
const productsList = Array.isArray(data.data) ? data.data : [];
const productsWithDetails = await Promise.all(
productsList.map(async (prod) => {
try {
const detailResponse = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/products/product/${prod.id_product}`,
{ method: "PUT" }
);
const detailData = await detailResponse.json();
if (detailData.data && detailData.data.length > 0) {
const detail = detailData.data[0];
return {
...prod,
category: detail.id_category_pro || null,
};
}
return {
...prod,
category: null,
};
} catch (err) {
console.error(`Error fetching product ${prod.id_product}:`, err);
return {
...prod,
category: null,
};
}
})
);
setProducts(productsWithDetails);
setFilteredProducts(productsWithDetails);
} catch (err) {
console.error('Error fetching products', err);
setProducts([]);
setFilteredProducts([]);
}
}
loadProducts();
})
.catch((err) => {
console.error(`Error al actualizar el outcome con el producto ${selectedId}:`, err);
alert('❌ Error al actualizar el outcome.');
});
};
const handleCancel = () => {
setModalOpen(false);
};
useEffect(() => {
async function loadProducts() {
try {
const response = await fetch(import.meta.env.VITE_API_BASE_URL + '/products');
const data = await response.json();
const productsList = Array.isArray(data.data) ? data.data : [];
const productsWithDetails = await Promise.all(
productsList.map(async (prod) => {
try {
const detailResponse = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/products/product/${prod.id_product}`,
{ method: "PUT" }
);
const detailData = await detailResponse.json();
if (detailData.data && detailData.data.length > 0) {
const detail = detailData.data[0];
return {
...prod,
category: detail.id_category_pro || null,
};
}
return {
...prod,
category: null,
};
} catch (err) {
console.error(`Error fetching product ${prod.id_product}:`, err);
return {
...prod,
category: null,
};
}
})
);
setProducts(productsWithDetails);
setFilteredProducts(productsWithDetails);
} catch (err) {
console.error('Error fetching products', err);
setProducts([]);
setFilteredProducts([]);
}
}
async function fetchSelectData() {
try {
const res = await fetch(import.meta.env.VITE_API_BASE_URL + '/products/gethousekeeper', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error(`Error fetching info: ${res.status}`);
const data = await res.json();
setFormHousekepeer(data.houseKeeper || []);
} catch (err) {
console.error('Error cargando metadata (getinfo):', err);
}
}
fetchSelectData();
loadProducts();
}, []);
useEffect(() => {
async function loadFilterOptions() {
try {
const categoriesRes = await fetch(
import.meta.env.VITE_API_BASE_URL + "/products/productcategory"
);
const categoriesData = await categoriesRes.json();
if (categoriesData.data) {
setCategories(
categoriesData.data
.filter((d) => d.id_prod_category)
.map((d) => ({
id: d.id_prod_category,
name: d.name_prod_category,
spanish_name: d.spanish_name || d.name_prod_category,
}))
);
}
} catch (err) {
console.error("Error loading filter options", err);
}
}
loadFilterOptions();
}, []);
useEffect(() => {
let filtered = [...products];
if (nameFilter) {
filtered = filtered.filter(prod =>
prod.name_product?.toLowerCase().includes(nameFilter.toLowerCase())
);
}
if (categoryFilter) {
filtered = filtered.filter(
(prod) => prod.category && prod.category.toString() === categoryFilter
);
}
if (stockFilter) {
switch (stockFilter) {
case 'out':
filtered = filtered.filter(prod => (parseInt(prod.units) || 0) === 0);
break;
case 'low':
filtered = filtered.filter(prod => {
const units = parseInt(prod.units) || 0;
return units > 0 && units < 10;
});
break;
case 'in':
filtered = filtered.filter(prod => (parseInt(prod.units) || 0) >= 10);
break;
default:
break;
}
}
setFilteredProducts(filtered);
}, [nameFilter, categoryFilter, stockFilter, products]);
return (
<div className="outcomes-page">
<div className="page-header">
<h2 className="page-title">{lang === "es" ? "Productos" : "Products"}</h2>
</div>
<div className="filters-section">
<input
type="text"
className="filter-search"
placeholder={lang === 'es' ? 'Search by name...' : 'Search by name...'}
value={nameFilter}
onChange={(e) => setNameFilter(e.target.value)}
/>
<select
value={categoryFilter}
onChange={(e) => setCategoryFilter(e.target.value)}
className="filter-select"
>
<option value="">
{lang === "es" ? "Todas las categorías" : "All Categories"}
</option>
{categories
.sort((a, b) => {
const nameA = lang === "en" ? a.name : a.spanish_name;
const nameB = lang === "en" ? b.name : b.spanish_name;
return nameA.localeCompare(nameB);
})
.map((cat, index) => (
<option key={index} value={cat.id}>
{lang === "en" ? cat.name : cat.spanish_name}
</option>
))}
</select>
<select
value={stockFilter}
onChange={(e) => setStockFilter(e.target.value)}
className="filter-select"
>
<option value="">{lang === "es" ? "Todo el stock" : "All Stock"}</option>
<option value="out">{lang === "es" ? "Sin stock" : "Out of Stock"}</option>
<option value="low">{lang === "es" ? "Stock bajo (< 10)" : "Low Stock (< 10)"}</option>
<option value="in">{lang === "es" ? "En stock (≥ 10)" : "In Stock (≥ 10)"}</option>
</select>
</div>
<div className="products-grid">
{Array.isArray(filteredProducts) &&
filteredProducts.map((prod, index) => (
<div key={prod.id_product || index} className="product-card" onClick={() => handleOpenModal(prod.id_product, prod.name_product, prod.units)}>
{prod.image_product && (
<img
src={
prod.image_product.startsWith('http')
? prod.image_product
: prod.image_product.length > 200
? `data:image/jpeg;base64,${prod.image_product}`
: `${IMAGE_BASE_URL}/${prod.image_product}`
}
alt={prod.name_product}
className="product-image"
/>
)}
<div className="product-info">
<div className="product-name">{prod.name_product}</div>
<div className="product-price">{lang === "es" ? "Precio: $" : "Price: $"}{prod.price_product}</div>
<div className="product-units">{lang === "es" ? "Unidades: " : "Units: "}{prod.units}</div>
</div>
</div>
))}
</div>
<ConfirmationOutcome
isOpen={modalOpen}
onConfirm={handleConfirm}
onCancel={handleCancel}
formHousekepeer={formHousekepeer}
idproduct={selectedId}
nameProduct={selectedProdName}
productStock={selectedProductStock}
/>
</div>
);
}