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:
@@ -0,0 +1,314 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user