478 lines
16 KiB
JavaScript
478 lines
16 KiB
JavaScript
import { useEffect, useRef, useState } from "react";
|
|
import axios from "axios";
|
|
import { Phone, Mail, Globe, MapPin, Facebook, Instagram } from "lucide-react";
|
|
import "./MenuQRCode.scss";
|
|
import { decryptToObject, sessionStore } from "../../../Services/Others";
|
|
import { useDispatch } from "react-redux";
|
|
import {
|
|
PublicQrCodeget,
|
|
} from "../../../Features/ConfigMasterPage/ConfigMasterPage";
|
|
import {
|
|
Comboget,
|
|
getCardDataWithoutSubmodule,
|
|
getLayoutCategories,
|
|
getLayoutSubCategories,
|
|
getLayoutproductCard,
|
|
} from "../../../Features/BookingScreen/BookingData/BookingData";
|
|
import { getBranchDetail } from "../../../Features/BrachLogin/BranchLogin";
|
|
|
|
const url_string = window.location.href;
|
|
const url = new URL(url_string);
|
|
const codesParam = url.searchParams.get("code");
|
|
|
|
const CustomerMenuPage = () => {
|
|
const apiUrlToken = import.meta.env.ENV_API_URL_TOKEN;
|
|
const dispatch = useDispatch();
|
|
const categoriesRef = useRef(null);
|
|
const [activeCategory, setActiveCategory] = useState(null);
|
|
const [activeCategoryType, setActiveCategoryType] = useState(null); // 'combo' or 'regular'
|
|
const [decryptedData, setDecryptedData] = useState(null);
|
|
const [categories, setCategories] = useState([]);
|
|
const [selectedCategoryObj, setSelectedCategoryObj] = useState(null);
|
|
const [subCategories, setSubCategories] = useState([]);
|
|
const [noSubcatCardData, setNoSubcatCardData] = useState([]);
|
|
const [activeProdSubCat, setActiveProdSubCat] = useState(null);
|
|
const [brachData, setBrachData] = useState(null);
|
|
const [comboData, setComboData] = useState([]);
|
|
const [comboItems, setComboItems] = useState([]);
|
|
const [selectedComboId, setSelectedComboId] = useState(null);
|
|
|
|
// Initialize data on mount
|
|
useEffect(() => {
|
|
initializeData();
|
|
}, []);
|
|
|
|
// Fetch combo data after decrypted data is available
|
|
useEffect(() => {
|
|
if (decryptedData) {
|
|
CombogetData();
|
|
}
|
|
}, [decryptedData]);
|
|
|
|
// Fetch products when category/subcategory changes
|
|
useEffect(() => {
|
|
if (activeCategoryType === 'regular' && selectedCategoryObj) {
|
|
fetchProducts();
|
|
} else if (activeCategoryType === 'combo' && selectedComboId) {
|
|
fetchComboItems(selectedComboId);
|
|
}
|
|
}, [selectedCategoryObj, activeProdSubCat, selectedComboId, activeCategoryType]);
|
|
|
|
const initializeData = async () => {
|
|
try {
|
|
await addSession();
|
|
await getQrCode();
|
|
} catch (error) {
|
|
console.error("Initialization error:", error);
|
|
}
|
|
};
|
|
|
|
const getQrCode = async () => {
|
|
try {
|
|
const qrData = await dispatch(PublicQrCodeget(codesParam)).unwrap();
|
|
const oldUrl = qrData?.data?.data?.[0]?.OldUrl;
|
|
const decrypted = decryptToObject(oldUrl);
|
|
if (!decrypted) {
|
|
console.error("Invalid decrypted data");
|
|
return;
|
|
}
|
|
|
|
const { BranchId, CompId, AppId, UserId } = decrypted;
|
|
if (BranchId && CompId && AppId) {
|
|
setDecryptedData({ BranchId, CompId, AppId, UserId });
|
|
await getBranchDetailData(BranchId);
|
|
await fetchCategories({ BranchId, CompId, AppId, UserId });
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching QR code:", error);
|
|
}
|
|
};
|
|
|
|
const getBranchDetailData = async (BranchId) => {
|
|
const response = await dispatch(getBranchDetail({ BrId: BranchId })).unwrap();
|
|
setBrachData(response?.data?.data?.[0]);
|
|
};
|
|
|
|
const addSession = async () => {
|
|
if (!sessionStorage.getItem("auth")) {
|
|
try {
|
|
const { data } = await axios.post(`${apiUrlToken}/jwtTokenGenerator`, {
|
|
username: "1000000001",
|
|
password: "1234",
|
|
});
|
|
sessionStorage.setItem("auth", data.token);
|
|
sessionStore("LoginType");
|
|
} catch (error) {
|
|
console.error("Session Error:", error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const CombogetData = async () => {
|
|
try {
|
|
const data = {
|
|
BranchId: decryptedData.BranchId,
|
|
CompId: decryptedData.CompId,
|
|
AppId: decryptedData.AppId,
|
|
ActiveStatus: "A"
|
|
};
|
|
|
|
const response = await dispatch(Comboget(data)).unwrap();
|
|
const combos = response?.data?.data || [];
|
|
setComboData(combos);
|
|
|
|
// Don't auto-select combo anymore - let user click on "Combo Offer"
|
|
} catch (error) {
|
|
console.error("Error fetching Combo data:", error);
|
|
}
|
|
};
|
|
|
|
const fetchComboItems = (comboId) => {
|
|
try {
|
|
// Find the selected combo and display its items
|
|
const selectedCombo = comboData.find(c => c.ComboId === comboId);
|
|
|
|
if (selectedCombo && selectedCombo.ComboInwardDetails && selectedCombo.ComboInwardDetails.length > 0) {
|
|
// Extract ComboDetails from the first ComboInwardDetails
|
|
const comboDetails = selectedCombo.ComboInwardDetails[0].ComboDetails || [];
|
|
setComboItems(comboDetails);
|
|
} else {
|
|
setComboItems([]);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching combo items:", error);
|
|
setComboItems([]);
|
|
}
|
|
};
|
|
|
|
const fetchCategories = async ({ BranchId, CompId, AppId, UserId }) => {
|
|
try {
|
|
const response = await dispatch(
|
|
getLayoutCategories({ BranchId, CompId, AppId, UserId })
|
|
).unwrap();
|
|
|
|
const categoryData = response?.data?.data || [];
|
|
setCategories(categoryData);
|
|
|
|
// Set first regular category as active by default
|
|
if (categoryData.length > 0) {
|
|
const firstCategory = categoryData[0];
|
|
setActiveCategory(firstCategory.ProdCat);
|
|
setActiveCategoryType('regular');
|
|
setSelectedCategoryObj(firstCategory);
|
|
await fetchSubCategories(firstCategory);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching categories:", error);
|
|
}
|
|
};
|
|
|
|
const fetchSubCategories = async (category) => {
|
|
try {
|
|
const response = await dispatch(
|
|
getLayoutSubCategories({
|
|
BranchId: category.BranchId,
|
|
CompId: category.CompId,
|
|
AppId: category.AppId,
|
|
ProdCat: category.ProdCat,
|
|
})
|
|
).unwrap();
|
|
|
|
const subCatData = response?.data?.data || [];
|
|
setSubCategories(subCatData);
|
|
|
|
if (subCatData.length > 0) {
|
|
setActiveProdSubCat(subCatData[0].ProdSubCat);
|
|
} else {
|
|
setActiveProdSubCat(null);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching subcategories:", error);
|
|
setSubCategories([]);
|
|
setActiveProdSubCat(null);
|
|
}
|
|
};
|
|
|
|
const fetchProducts = async () => {
|
|
if (!selectedCategoryObj) return;
|
|
|
|
try {
|
|
const { CompId, BranchId, AppId, ProdCat } = selectedCategoryObj;
|
|
|
|
if (activeProdSubCat !== null) {
|
|
const response = await dispatch(
|
|
getLayoutproductCard({
|
|
CompId,
|
|
BranchId,
|
|
AppId,
|
|
ProdSubCat: activeProdSubCat,
|
|
})
|
|
).unwrap();
|
|
|
|
if (response?.data?.statusCode === 1) {
|
|
setNoSubcatCardData(response?.data?.data || []);
|
|
}
|
|
} else {
|
|
const response = await dispatch(
|
|
getCardDataWithoutSubmodule({
|
|
compId: CompId,
|
|
branchId: BranchId,
|
|
appId: AppId,
|
|
prodCat: ProdCat,
|
|
})
|
|
).unwrap();
|
|
|
|
if (response?.data?.statusCode === 1) {
|
|
setNoSubcatCardData(response?.data?.data || []);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching products:", error);
|
|
setNoSubcatCardData([]);
|
|
}
|
|
};
|
|
|
|
const handleComboClick = () => {
|
|
setActiveCategory('combo-offer');
|
|
setActiveCategoryType('combo');
|
|
setSelectedCategoryObj(null);
|
|
setSubCategories([]);
|
|
setActiveProdSubCat(null);
|
|
setSelectedComboId(null);
|
|
setComboItems([]);
|
|
};
|
|
|
|
const handleComboSubCategoryClick = (comboId) => {
|
|
setSelectedComboId(comboId);
|
|
};
|
|
|
|
const handleCategoryClick = async (cat) => {
|
|
setActiveCategory(cat.ProdCat);
|
|
setActiveCategoryType('regular');
|
|
setSelectedCategoryObj(cat);
|
|
await fetchSubCategories(cat);
|
|
};
|
|
|
|
const handleSubCategoryClick = (subCat) => {
|
|
setActiveProdSubCat(subCat.ProdSubCat);
|
|
};
|
|
|
|
const scrollCategories = (distance) => {
|
|
categoriesRef.current.scrollBy({
|
|
left: distance,
|
|
behavior: "smooth",
|
|
});
|
|
};
|
|
|
|
const displayItems = activeCategoryType === 'combo' ? comboItems : noSubcatCardData;
|
|
console.log(displayItems, "displayItems")
|
|
// Get combo details for display
|
|
const selectedCombo = activeCategoryType === 'combo' && selectedComboId
|
|
? comboData.find(c => c.ComboId === selectedComboId)
|
|
: null;
|
|
|
|
const displayTitle = activeCategoryType === 'combo'
|
|
? (selectedCombo?.ComboName || "Combo Offers")
|
|
: selectedCategoryObj?.ProdCatName || "Menu";
|
|
|
|
// Calculate total combo price
|
|
const comboTotalPrice = selectedCombo?.ComboInwardDetails?.[0]?.TotalSellPrice || 0;
|
|
const comboOfferPrice = selectedCombo?.ComboInwardDetails?.[0]?.OfferPrice || 0;
|
|
const comboDiscountedPrice = comboOfferPrice
|
|
? comboTotalPrice - (comboTotalPrice * comboOfferPrice / 100)
|
|
: comboTotalPrice;
|
|
|
|
return (
|
|
<div className="menuq-container">
|
|
<header className="menuq-header">
|
|
<div>
|
|
<img src={brachData?.CompLogo} alt="logo" />
|
|
</div>
|
|
<div style={{ textAlign: 'left' }}>
|
|
<h1 className="menuq-header-title">{brachData?.BrName}</h1>
|
|
<p className="menuq-header-address">
|
|
{brachData?.Address1} {brachData?.Address2} {brachData?.City} {brachData?.Dist} {brachData?.State} {brachData?.Zip}
|
|
</p>
|
|
<p className="menuq-header-contact">
|
|
{brachData?.BrMobile}, {brachData?.BrMobile2}
|
|
</p>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="menuq-categories-wrapper">
|
|
<button className="scroll-btn left" onClick={() => scrollCategories(-100)}>
|
|
←
|
|
</button>
|
|
|
|
<nav className="menuq-categories" ref={categoriesRef}>
|
|
{/* Show Static "Combo Offer" Button First (only if combos exist) */}
|
|
{comboData.length > 0 && (
|
|
<button
|
|
onClick={handleComboClick}
|
|
className={`menuq-category ${activeCategory === 'combo-offer' && activeCategoryType === 'combo'
|
|
? "menuq-category--active"
|
|
: ""
|
|
}`}
|
|
>
|
|
Combo Offers
|
|
</button>
|
|
)}
|
|
|
|
{/* Then Show Regular Categories */}
|
|
{categories.map((cat) => (
|
|
<button
|
|
key={`cat-${cat.ProdCat}`}
|
|
onClick={() => handleCategoryClick(cat)}
|
|
className={`menuq-category ${activeCategory === cat.ProdCat && activeCategoryType === 'regular'
|
|
? "menuq-category--active"
|
|
: ""
|
|
}`}
|
|
>
|
|
{cat.ProdCatName}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
|
|
<button className="scroll-btn right" onClick={() => scrollCategories(100)}>
|
|
→
|
|
</button>
|
|
</div>
|
|
|
|
<section className="menuq-content">
|
|
<div className="menuq-card">
|
|
{/* <h2 className="menuq-card-title">{displayTitle}</h2> */}
|
|
|
|
|
|
|
|
|
|
{/* Show subcategory filters only for regular categories */}
|
|
{activeCategoryType === 'regular' && (
|
|
<div className="menuq-filters">
|
|
{subCategories.length > 0 ? (
|
|
subCategories.map((subCat) => {
|
|
const displayName = subCat.ProdSubCatName?.trim() || "General";
|
|
|
|
return (
|
|
<button
|
|
key={subCat.ProdSubCat || "general"}
|
|
onClick={() => handleSubCategoryClick(subCat)}
|
|
className={`menuq-filter ${activeProdSubCat === subCat.ProdSubCat
|
|
? "menuq-filter--active"
|
|
: ""
|
|
}`}
|
|
>
|
|
{displayName}
|
|
</button>
|
|
);
|
|
})
|
|
) : (
|
|
<button
|
|
className="menuq-filter menuq-filter--active"
|
|
onClick={() => setActiveProdSubCat(null)}
|
|
>
|
|
General
|
|
</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="menuq-items">
|
|
{activeCategoryType === 'combo' && activeCategory === 'combo-offer' ? (
|
|
comboData.map((combo, i) => {
|
|
const comboDetail = combo.ComboInwardDetails?.[0];
|
|
const totalPrice = comboDetail?.TotalSellPrice || 0;
|
|
const offerPrice = comboDetail?.OfferPrice || 0;
|
|
const discountedPrice = offerPrice
|
|
? totalPrice - (totalPrice * offerPrice / 100)
|
|
: totalPrice;
|
|
|
|
const comboImage = combo.ComboImage;
|
|
|
|
return (
|
|
<div key={i} className="menuq-item">
|
|
<div className="menuq-image-item-price">
|
|
{comboImage && (
|
|
<img
|
|
src={comboImage}
|
|
alt={combo.ComboName}
|
|
className="menuq-item-image"
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className="menuq-item-info">
|
|
<h3 className="menuq-item-name">{combo.ComboName}</h3>
|
|
<p className="menuq-item-cuisine">{combo.ComboDescription || 'Combo Offer'}</p>
|
|
<div className="menuq-item-price">
|
|
{offerPrice > 0 && (
|
|
<span style={{ textDecoration: 'line-through', color: '#999', marginRight: '8px' }}>
|
|
₹{totalPrice.toFixed(2)}
|
|
</span>
|
|
)}
|
|
₹{discountedPrice.toFixed(2)}/-
|
|
{offerPrice > 0 && (
|
|
<span style={{ color: '#28a745', marginLeft: '8px', fontSize: '0.9em' }}>
|
|
({offerPrice}% OFF)
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})
|
|
) : displayItems.length > 0 ? (
|
|
displayItems.map((item, i) => {
|
|
// For combo items, get price from StockDetails
|
|
const stockDetail = item.StockDetails?.[0];
|
|
const itemPrice = activeCategoryType === 'combo'
|
|
? (stockDetail?.SellPrice || stockDetail?.MRP || 0)
|
|
: (item.ProductDetail?.[0]?.ProdVariantDetails?.[0]?.StockDetails?.[0]?.SellPrice || 0);
|
|
|
|
// For combo items, use ComboQty or ProductQty
|
|
const quantity = activeCategoryType === 'combo'
|
|
? (item.ComboQty || item.ProductQty || 1)
|
|
: 1;
|
|
|
|
const stockAvailable = (item.ProductDetail?.[0]?.StockAvailable === 'Y' && item.ProductDetail?.[0]?.ProdVariantDetails?.[0]?.OverAllQty === 0);
|
|
|
|
return (
|
|
<div key={i} className="menuq-item">
|
|
<div className="menuq-image-item-price">
|
|
{(item.ProductDetail?.[0]?.ProdLogo || item.ProdLogo) && (
|
|
<img
|
|
src={item.ProductDetail?.[0]?.ProdLogo || item.ProdLogo}
|
|
alt={item.ProdName}
|
|
className="menuq-item-image"
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className="menuq-item-info">
|
|
<h3 className="menuq-item-name">
|
|
{item.ProdName}
|
|
{activeCategoryType === 'combo' && quantity > 1 && (
|
|
<span style={{ fontSize: '0.9em', color: '#666', marginLeft: '8px' }}>
|
|
(Qty: {quantity})
|
|
</span>
|
|
)}
|
|
</h3>
|
|
<p className="menuq-item-cuisine">
|
|
{item.ProdSubCatName || item.ProdCatName ||
|
|
(activeCategoryType === 'combo' ? `${item.UomName || 'Item'} - Size: ${item.Size || 'N/A'}` : '')}
|
|
</p>
|
|
<div className={`menuq-item-price ${stockAvailable ? 'not-available' : ''}`}>
|
|
{stockAvailable ? 'Not Available' : `₹${itemPrice.toFixed(2)}/-`}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})
|
|
) : (
|
|
<p>No products available.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default CustomerMenuPage; |