The 'Sides' category and option to add sides are enabled, but the image upload feature for sides is still pending.
This commit is contained in:
parent
6df04ed684
commit
179975947e
@ -13179,12 +13179,12 @@ select option {
|
||||
left: 50%;
|
||||
bottom: -1px;
|
||||
transform: translateX(-50%);
|
||||
background-color: var(--primary-600);
|
||||
background-color: var(--theme-color);
|
||||
transition: 0.2s linear;
|
||||
}
|
||||
|
||||
.bordered-tab .nav-link.active {
|
||||
color: var(--primary-600);
|
||||
color: var(--theme-color);
|
||||
}
|
||||
|
||||
.bordered-tab .nav-link.active::before {
|
||||
@ -13194,7 +13194,7 @@ select option {
|
||||
/* Bordered Tab Css End */
|
||||
/* Pill Tab Css Start */
|
||||
.pill-tab .nav-link.active {
|
||||
background-color: var(--primary-600);
|
||||
background-color: var(--theme-color);
|
||||
}
|
||||
|
||||
.pill-tab.style-three {
|
||||
|
||||
67
src/app/admin/(pos-system)/pos/sides-category/page.jsx
Normal file
67
src/app/admin/(pos-system)/pos/sides-category/page.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
"use client"
|
||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||
import { useEffect, useState } from "react";
|
||||
import client from "@auth";
|
||||
import PageLoader from "@/components/common-component/PageLoader";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import SidesCategoryComponent from "@/components/admin/SidesCategoryComponent";
|
||||
|
||||
// export const metadata = {
|
||||
// title: "WowDash NEXT JS - Admin Dashboard Multipurpose Bootstrap 5 Template",
|
||||
// description:
|
||||
// "Wowdash NEXT JS is a developer-friendly, ready-to-use admin template designed for building attractive, scalable, and high-performing web applications.",
|
||||
// };
|
||||
|
||||
const SidesCategoryPage = () => {
|
||||
|
||||
const [sidesCategoryData, setSidesCategoryData] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [restaruntBranch, setRestaruntBranch] = useState("")
|
||||
|
||||
useEffect(() => {
|
||||
const restarunt = localStorage.getItem("restaurantbranch")
|
||||
setRestaruntBranch(restarunt)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (restaruntBranch && restaruntBranch !== "")
|
||||
getSidesCategory();
|
||||
}, [restaruntBranch]);
|
||||
|
||||
|
||||
|
||||
const getSidesCategory = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// const res = await client?.get(`/Dine360 Floor?fields=[\"*\"]&limit_page_length=100`);
|
||||
const res = await client?.get(`/Dine360%20Food%20Sides%20Category?fields=[%22*%22]&limit_page_length=100&filters=[["restaurantbranch","=","${restaruntBranch}"]]`);
|
||||
setSidesCategoryData(res?.data?.data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching floor data:", error);
|
||||
setError(error?.message || "Failed to fetch floor data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
console.log("")
|
||||
return (
|
||||
<>
|
||||
<MasterLayout>
|
||||
|
||||
<Breadcrumb title='Floor' />
|
||||
|
||||
{loading ? (
|
||||
<PageLoader />
|
||||
) : (
|
||||
<SidesCategoryComponent sidesCategoryData={sidesCategoryData} getSidesCategory={getSidesCategory} />
|
||||
)
|
||||
}
|
||||
</MasterLayout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidesCategoryPage;
|
||||
416
src/app/admin/(pos-system)/pos/sides/page.jsx
Normal file
416
src/app/admin/(pos-system)/pos/sides/page.jsx
Normal file
@ -0,0 +1,416 @@
|
||||
"use client";
|
||||
import React, { Suspense, useEffect, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||
import client from "@auth";
|
||||
import { gradientClasses } from "@utils/constant.utils";
|
||||
import PageLoader from "@/components/common-component/PageLoader";
|
||||
import PageNoData from "@/components/common-component/PageNoData";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
const SidesPageInner = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const sidesCategoryName = decodeURIComponent(searchParams.get("sidescategoryname"));
|
||||
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [editingRoomId, setEditingRoomId] = useState(null);
|
||||
const [roomData, setRoomData] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [formData, setFormData] = useState({
|
||||
sidename: "",
|
||||
description: "",
|
||||
price: "",
|
||||
item_image: null,
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
|
||||
const [restaruntBranch, setRestaruntBranch] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const restarunt = localStorage.getItem("restaurantbranch");
|
||||
setRestaruntBranch(restarunt);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const isLogin = JSON.parse(localStorage.getItem("isLogin"));
|
||||
if (!isLogin) {
|
||||
router.push(`/admin?restaurantbranch=${restaruntBranch}`);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sidesCategoryName) {
|
||||
getSideData();
|
||||
}
|
||||
}, [sidesCategoryName]);
|
||||
|
||||
const getSideData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const roomRes = await client.get(
|
||||
`/Dine360%20Food%20Sides?fields=[%22*%22]&limit_page_length=100&filters=[["sidecategoryid","=","${sidesCategoryName}"]]`
|
||||
);
|
||||
setRoomData(roomRes?.data?.data || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError(error?.message || "Failed to fetch side data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const newErrors = {};
|
||||
|
||||
if (!formData.sidename.trim()) newErrors.sidename = "Side Name is required";
|
||||
if (!formData.description.trim()) newErrors.description = "Description is required";
|
||||
if (!formData.price) newErrors.price = "Price is required";
|
||||
if (!formData.item_image) newErrors.item_image = "Item image is required";
|
||||
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
|
||||
const body = {
|
||||
sidename: formData.sidename,
|
||||
description: formData.description,
|
||||
price: parseFloat(formData.price),
|
||||
item_image: formData.item_image,
|
||||
sidecategoryid: sidesCategoryName,
|
||||
restaurantbranch: restaruntBranch,
|
||||
};
|
||||
|
||||
try {
|
||||
if (editMode) {
|
||||
await client.put(`/Dine360%20Food%20Sides/${editingRoomId}`, body);
|
||||
} else {
|
||||
await client.post(`/Dine360%20Food%20Sides`, body);
|
||||
}
|
||||
getSideData();
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
console.error("❌ Submission error:", error);
|
||||
|
||||
// Backend MySQL error handling
|
||||
const backendError = error?.response?.data?.exception || "";
|
||||
|
||||
if (backendError.includes("Data too long for column 'item_image'")) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
item_image: "Data too long for column 'item_image'",
|
||||
}));
|
||||
} else if (error?.response?.status === 500) {
|
||||
alert("Server error occurred. Please try again later or contact support.");
|
||||
} else {
|
||||
alert("Submission failed. Please check your input and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
const handleEdit = (room) => {
|
||||
setFormData({
|
||||
sidename: room.sidename || "",
|
||||
description: room.description || "",
|
||||
price: room.price || "",
|
||||
item_image: room.item_image || null,
|
||||
});
|
||||
setEditingRoomId(room.name);
|
||||
setEditMode(true);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await client.delete(`/Dine360%20Food%20Sides/${deleteConfirm?.id}`);
|
||||
setDeleteConfirm({ show: false, id: null });
|
||||
getSideData();
|
||||
} catch (error) {
|
||||
alert("Error deleting. It might be linked to other data.");
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
sidename: "",
|
||||
description: "",
|
||||
price: "",
|
||||
item_image: null,
|
||||
});
|
||||
setErrors({});
|
||||
setEditMode(false);
|
||||
setEditingRoomId(null);
|
||||
setShowModal(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||
<div className="card h-100 p-0 radius-12">
|
||||
<div className="card-body p-3 p-lg-5">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 className="mb-0">Sides</h6>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-bg-theme radius-8 px-20 py-11"
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="row gy-3 gx-3 gx-lg-5 gy-lg-5 justify-content-center">
|
||||
{loading ? (
|
||||
<PageLoader />
|
||||
) : roomData?.length === 0 ? (
|
||||
<PageNoData />
|
||||
) : (
|
||||
roomData?.map((room, index) => {
|
||||
const gradientClass = gradientClasses[index % gradientClasses.length];
|
||||
return (
|
||||
<div className="col-xxl-3 col-lg-4 col-sm-6 cursor-pointer" key={room?.name}>
|
||||
<div className={`card p-3 shadow-2 radius-8 h-100 bg-theme border border-white position-relative`}>
|
||||
<div className="position-absolute top-0 end-0 me-1 mt-1 d-flex gap-2">
|
||||
<div className='dropdown'>
|
||||
<button
|
||||
className='btn px-1 py-1 d-flex align-items-center text-primary-light'
|
||||
type='button'
|
||||
data-bs-toggle='dropdown'
|
||||
aria-expanded='false'
|
||||
>
|
||||
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
|
||||
</button>
|
||||
<ul className='dropdown-menu'>
|
||||
<li>
|
||||
<Link
|
||||
href="#"
|
||||
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleEdit(room);
|
||||
}}
|
||||
>
|
||||
<Icon icon='lucide:edit' className='menu-icon' />
|
||||
Edit
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="#"
|
||||
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDeleteConfirm({ show: true, id: room.name });
|
||||
}}
|
||||
>
|
||||
<Icon icon='fluent:delete-24-regular' className='menu-icon' />
|
||||
Delete
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card-body text-center p-0">
|
||||
<h6 className="text-lg mt-0 mb-0">{room?.sidename}</h6>
|
||||
<p className="mb-0 text-sm">{room?.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{showModal && (
|
||||
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header border-0 pb-0">
|
||||
<h6 className="modal-title text-lg">{editMode ? "Edit Side" : "Create Side"}</h6>
|
||||
<button type="button" className="btn-close" onClick={resetForm}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Side Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control ${errors.sidename ? "is-invalid" : ""}`}
|
||||
name="sidename"
|
||||
value={formData.sidename}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{errors.sidename && <div className="invalid-feedback">{errors.sidename}</div>}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<textarea
|
||||
className={`form-control ${errors.description ? "is-invalid" : ""}`}
|
||||
name="description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
rows="3"
|
||||
></textarea>
|
||||
{errors.description && <div className="invalid-feedback">{errors.description}</div>}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Price</label>
|
||||
<input
|
||||
type="number"
|
||||
className={`form-control ${errors.price ? "is-invalid" : ""}`}
|
||||
name="price"
|
||||
value={formData.price}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{errors.price && <div className="invalid-feedback">{errors.price}</div>}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Item Image</label>
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
{/* Input on the left */}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className={`form-control ${errors.item_image ? "is-invalid" : ""}`}
|
||||
style={{ maxWidth: "350px" }}
|
||||
onChange={(e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
|
||||
const maxFileSizeMB = 1;
|
||||
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
item_image: "Only JPG, JPEG, and PNG files are allowed.",
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size / (1024 * 1024) > maxFileSizeMB) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
item_image: "Max file size is 1MB.",
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
item_image: reader.result,
|
||||
}));
|
||||
setErrors((prev) => ({ ...prev, item_image: null }));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Image on the right */}
|
||||
{formData.item_image && (
|
||||
<div className="position-relative" style={{ width: "50px", height: "50px" }}>
|
||||
<img
|
||||
src={formData.item_image}
|
||||
alt="Preview"
|
||||
style={{
|
||||
width: "50px",
|
||||
height: "50px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid #ccc",
|
||||
}}
|
||||
/>
|
||||
{/* Delete icon top-right */}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-danger position-absolute top-0 end-0 p-1"
|
||||
style={{
|
||||
transform: "translate(50%, -50%)",
|
||||
borderRadius: "50%",
|
||||
fontSize: "12px",
|
||||
lineHeight: "1",
|
||||
}}
|
||||
onClick={() =>
|
||||
setFormData((prev) => ({ ...prev, item_image: null }))
|
||||
}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{errors.item_image && <div className="invalid-feedback d-block">{errors.item_image}</div>}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div className="d-flex justify-content-end">
|
||||
<button type="submit" className="btn btn-bg-theme">
|
||||
{editMode ? "Update" : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation */}
|
||||
{deleteConfirm.show && (
|
||||
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-body">
|
||||
<div className="d-flex justify-content-between mb-1">
|
||||
<h6 className="text-lg mb-0">Confirm Delete</h6>
|
||||
<button type="button" className="btn-close" onClick={() => setDeleteConfirm({ show: false, id: null })}></button>
|
||||
</div>
|
||||
<p className="mb-0">Are you sure you want to delete this item?</p>
|
||||
<div className="d-flex justify-content-end gap-2 mt-1">
|
||||
<button className="btn btn-outline-danger px-14 py-6 text-sm" onClick={() => setDeleteConfirm({ show: false, id: null })}>Cancel</button>
|
||||
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleDelete}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SidesPage = () => {
|
||||
return (
|
||||
<MasterLayout>
|
||||
<Breadcrumb title="Sides Management" />
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<SidesPageInner />
|
||||
</Suspense>
|
||||
</MasterLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidesPage;
|
||||
@ -33,73 +33,155 @@ const MenuItemsCategory = () => {
|
||||
const [orderStatus, setOrderStatus] = useState('pending');
|
||||
const HST_TAX_RATE = 0.13; // 13% HST tax rate
|
||||
|
||||
const [catMenu, setCatMenu] = useState(null);
|
||||
const [catMenuActive, setCatMenuActive] = useState(null)
|
||||
const [selectedItem, setSelectedItem] = useState(null);
|
||||
const [showItemModal, setShowItemModal] = useState(false);
|
||||
const [selectedExtras, setSelectedExtras] = useState([]);
|
||||
const [selectedDrink, setSelectedDrink] = useState(null);
|
||||
const [orderItems, setOrderItems] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
getMenuItem();
|
||||
}, []);
|
||||
|
||||
if (menuName) {
|
||||
getMenuItems();
|
||||
const getMenuItem = async () => {
|
||||
try {
|
||||
const res = await client?.get(`/Dine360 Menu?fields=["*"]&limit_page_length=100`);
|
||||
setCatMenu(res?.data?.data || []);
|
||||
setCatMenuActive(res?.data?.data[0]?.name || [])
|
||||
} catch (error) {
|
||||
console.error("Error fetching menu list:", error);
|
||||
}
|
||||
}, [menuName]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (menuData) {
|
||||
getMenuFoodItems(menuData)
|
||||
setActiveCategory(menuData[0]?.name)
|
||||
if (catMenu?.length > 0) {
|
||||
getMenuItems(catMenu[0]?.menuname); // auto-load first menu's categories
|
||||
}
|
||||
}, [menuData])
|
||||
}, [catMenu]);
|
||||
|
||||
const getMenuItems = async () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (menuData?.length > 0) {
|
||||
getMenuFoodItems(menuData[0]);
|
||||
setActiveCategory(menuData[0]?.name);
|
||||
}
|
||||
}, [menuData]);
|
||||
|
||||
const getMenuItems = async (menuname) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// Fetch floor data using name
|
||||
const menuRes = await client.get(`/Dine360%20Menu%20Category%20Link?fields=[\"*\"]&limit_page_length=100&filters=[["menu","=","${menuName}"]]`);
|
||||
console.log("menuRes", menuRes?.data?.data);
|
||||
const menuRes = await client.get(
|
||||
`/Dine360%20Menu%20Category%20Link?fields=["*"]&limit_page_length=100&filters=[["menu","=","${menuname}"]]`
|
||||
);
|
||||
|
||||
// Get menu categories based on the menucategory from menuRes
|
||||
const menuCategories = menuRes?.data?.data || [];
|
||||
const menuCategoryPromises = menuCategories.map(async (menuItem) => {
|
||||
const menuCategoryRes = await client.get(`/Dine360%20Menu%20Category?fields=[\"*\"]&limit_page_length=100&filters=[["name","=","${menuItem.menucategory}"]]`);
|
||||
console.log("menuCategoryRes", menuCategoryRes?.data?.data);
|
||||
return menuCategoryRes?.data?.data?.[0] || null;
|
||||
const menuLinks = menuRes?.data?.data || [];
|
||||
|
||||
const menuCategoryPromises = menuLinks.map(async (menuItem) => {
|
||||
const res = await client.get(
|
||||
`/Dine360%20Menu%20Category?fields=["*"]&limit_page_length=100&filters=[["name","=","${menuItem.menucategory}"]]`
|
||||
);
|
||||
return res?.data?.data?.[0] || null;
|
||||
});
|
||||
|
||||
const menuCategoryResults = await Promise.all(menuCategoryPromises);
|
||||
const filteredMenuCategories = menuCategoryResults.filter(category => category !== null);
|
||||
const categories = await Promise.all(menuCategoryPromises);
|
||||
const validCategories = categories.filter(Boolean);
|
||||
|
||||
setMenuData(filteredMenuCategories);
|
||||
setMenuData(validCategories);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError(error?.message || "Failed to fetch floor data");
|
||||
console.error("Error fetching menu categories:", error);
|
||||
setError(error?.message || "Failed to fetch menu categories");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getMenuFoodItems = (async (menuData) => {
|
||||
console.log("menuDataaaa", menuData)
|
||||
const getMenuFoodItems = async (category) => {
|
||||
if (!category?.name) return;
|
||||
try {
|
||||
const res = await client.get(`/Dine360%20Menu%20Category/${menuData[0].name}?fields=[\"*\"]&limit_page_length=100`);
|
||||
console.log("res", res)
|
||||
setMenuItems(res?.data?.data)
|
||||
const res = await client.get(
|
||||
`/Dine360%20Menu%20Category/${category.name}?fields=["*"]&limit_page_length=100`
|
||||
);
|
||||
setMenuItems(res?.data?.data || []);
|
||||
} catch (error) {
|
||||
console.log("error", error)
|
||||
}
|
||||
})
|
||||
|
||||
const handleMenuClick = async (menu) => {
|
||||
try {
|
||||
const menuItemsRes = await client.get(`/Dine360%20Menu%20Category/${menu.name}?fields=[\"*\"]&limit_page_length=100`);
|
||||
console.log("menuItemsRes", menuItemsRes?.data?.data);
|
||||
setMenuItems(menuItemsRes?.data?.data);
|
||||
setActiveCategory(menu?.name);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
setError(error?.message || "Failed to fetch floor data");
|
||||
console.error("Error fetching menu items:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Fetch sides linked to a specific food item
|
||||
const getLinkedSides = async (foodItemName) => {
|
||||
try {
|
||||
const res = await client.get(
|
||||
`/Dine360 FoodItem Sides Link?fields=["*"]&filters=[["menuitemname","=","${foodItemName}"]]`
|
||||
);
|
||||
return res.data.data; // Sides linked to this menu item
|
||||
} catch (error) {
|
||||
console.error(`Error fetching linked sides for item: ${foodItemName}`, error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch and group all sides by their category
|
||||
const getAllSidesGroupedByCategory = async (foodItemName) => {
|
||||
try {
|
||||
const res = await client.get(`/Dine360 FoodItem Sides Link?fields=["*"]&filters=[["menuitemname","=","${foodItemName}"]]`);
|
||||
console.log("Grouped Sides:", res.data.data);
|
||||
|
||||
const SliderLink = res?.data?.data || [];
|
||||
|
||||
const SidesList = SliderLink.map(async (sideItem) => {
|
||||
const res = await client.get(
|
||||
`/Dine360 Food Sides?fields=["*"]&filters=[["name","=","${sideItem.menucategory}"]]`
|
||||
);
|
||||
return res?.data?.data?.[0] || null;
|
||||
});
|
||||
const categories = await Promise.all(SidesList);
|
||||
const validSides = categories.filter(Boolean);
|
||||
console.log("validSides", validSides)
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error fetching grouped sides:", err);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToOrder = () => {
|
||||
const cartItem = cart.find(item => item.name === selectedItem.name);
|
||||
|
||||
if (!cartItem) {
|
||||
alert("Please add the item to cart before adding to order.");
|
||||
return;
|
||||
}
|
||||
|
||||
setOrderItems(cart);
|
||||
setShowItemModal(false);
|
||||
};
|
||||
|
||||
// For user clicking category from catMenu
|
||||
const handleCatMenuClick = async (menuname) => {
|
||||
await getMenuItems(menuname);
|
||||
setCatMenuActive(menuname)
|
||||
};
|
||||
|
||||
// For user clicking individual menu category
|
||||
const handleMenuClick = async (menu) => {
|
||||
try {
|
||||
const res = await client.get(
|
||||
`/Dine360%20Menu%20Category/${menu.name}?fields=["*"]&limit_page_length=100`
|
||||
);
|
||||
setMenuItems(res?.data?.data || []);
|
||||
setActiveCategory(menu?.name);
|
||||
} catch (error) {
|
||||
console.error("Error fetching menu data:", error);
|
||||
setError(error?.message || "Failed to fetch menu data");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
console.log("menuItems", menuItems);
|
||||
const addToCart = (item) => {
|
||||
const existingItem = cart.find(cartItem => cartItem.name === item.name);
|
||||
@ -112,24 +194,40 @@ const MenuItemsCategory = () => {
|
||||
} else {
|
||||
setCart([...cart, { ...item, quantity: 1 }]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const removeFromCart = (itemName) => {
|
||||
setCart(cart.filter(item => item.name !== itemName));
|
||||
setCart(prev => prev.filter(item => item.name !== itemName));
|
||||
setOrderItems(prev => prev.filter(item => item.name !== itemName));
|
||||
};
|
||||
|
||||
const updateQuantity = (itemName, newQuantity) => {
|
||||
if (newQuantity < 1) {
|
||||
removeFromCart(itemName);
|
||||
removeItem(itemName);
|
||||
return;
|
||||
}
|
||||
setCart(cart.map(item =>
|
||||
|
||||
// Update cart
|
||||
setCart(prev =>
|
||||
prev.map(item =>
|
||||
item.name === itemName
|
||||
? { ...item, quantity: newQuantity }
|
||||
: item
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
// Update orderItems
|
||||
setOrderItems(prev =>
|
||||
prev.map(item =>
|
||||
item.name === itemName
|
||||
? { ...item, quantity: newQuantity }
|
||||
: item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const calculateSubtotal = () => {
|
||||
return cart.reduce((total, item) => total + (item.price * item.quantity), 0);
|
||||
};
|
||||
@ -146,7 +244,19 @@ const MenuItemsCategory = () => {
|
||||
setShowOrderModal(true);
|
||||
};
|
||||
|
||||
console.log
|
||||
// Toggle extras like checkbox buttons
|
||||
const toggleExtra = (extra) => {
|
||||
setSelectedExtras(prev =>
|
||||
prev.includes(extra)
|
||||
? prev.filter(item => item !== extra)
|
||||
: [...prev, extra]
|
||||
);
|
||||
};
|
||||
|
||||
// Select one drink at a time
|
||||
const selectDrink = (drink) => {
|
||||
setSelectedDrink(drink === selectedDrink ? null : drink);
|
||||
};
|
||||
|
||||
const confirmOrder = async () => {
|
||||
// Get current time in YYYY-MM-DD HH:mm:ss format (Canada Eastern Time)
|
||||
@ -164,7 +274,7 @@ const MenuItemsCategory = () => {
|
||||
console.log(orderStartTime);
|
||||
|
||||
// Prepare formatted order data
|
||||
const formattedOrder = cart.map((item) => ({
|
||||
const formattedOrder = orderItems.map((item) => ({
|
||||
menuitem: item.name,
|
||||
quantity: item.quantity,
|
||||
rate: item.price,
|
||||
@ -212,21 +322,36 @@ const MenuItemsCategory = () => {
|
||||
|
||||
|
||||
const renderMenuItem = (menu) => {
|
||||
console.log("menu", menu)
|
||||
const cartItem = cart.find(item => item.name === menu.name);
|
||||
const isInCart = !!cartItem;
|
||||
|
||||
return (
|
||||
<div key={menu.name} className="col-xxl-3 col-md-6 user-grid-card">
|
||||
<div className="position-relative border bg-tb-lilac radius-16 overflow-hidden">
|
||||
<div className="p-16 text-center">
|
||||
<div className="position-relative border radius-16 overflow-hidden">
|
||||
<div className="card cursor-pointer" onClick={() => {
|
||||
setSelectedItem(menu);
|
||||
getAllSidesGroupedByCategory(menu?.name)
|
||||
setShowItemModal(true);
|
||||
}}
|
||||
>
|
||||
<div className="card-body text-center p-0">
|
||||
<img
|
||||
src={menu.profileImg}
|
||||
alt=""
|
||||
className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
|
||||
/>
|
||||
<h6 className="text-md mb-0 mt-3">{menu.menuitemname}</h6>
|
||||
<span className="text-secondary-light text-sm mb-16">${menu.price.toFixed(2)}</span>
|
||||
{isInCart ? (
|
||||
{/* <div className="w-100 max-h-150-px radius-0 overflow-hidden" >
|
||||
<img alt="" className="w-100 h-150-px object-fit-cover" src="/assets/images/blog/blog5.png" style={{ height: "150px" }} />
|
||||
</div> */}
|
||||
<div className="p-3 pt-0">
|
||||
<h6 className="text-lg mb-1 mt-1">{menu.menuitemname}</h6>
|
||||
<span className="text-secondary-light text-sm lh-sm mb-1">{menu.parent}</span>
|
||||
<h6 className="text-md mb-0 mt-1">${menu.price.toFixed(2)}</h6>
|
||||
</div>
|
||||
|
||||
|
||||
{/* {isInCart ? (
|
||||
<div className="d-flex align-items-center justify-content-center gap-2">
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger-500"
|
||||
@ -249,7 +374,8 @@ const MenuItemsCategory = () => {
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -257,12 +383,18 @@ const MenuItemsCategory = () => {
|
||||
};
|
||||
|
||||
|
||||
const cartItem = cart.find(item => item.name === selectedItem.name);
|
||||
const isInCart = !!cartItem;
|
||||
const quantity = cartItem?.quantity || 1;
|
||||
|
||||
console.log("catMenuActive", catMenuActive)
|
||||
console.log("cartItem?.quantity", cartItem)
|
||||
|
||||
return (
|
||||
<MasterLayout>
|
||||
<Breadcrumb title={`Menu Items`} />
|
||||
|
||||
<div className="container-fluid">
|
||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||
|
||||
{
|
||||
loading ? (
|
||||
@ -273,12 +405,42 @@ const MenuItemsCategory = () => {
|
||||
<div className="row gy-4">
|
||||
{/* Menu Category - Always col-xxl-2 */}
|
||||
<div className="col-xxl-2">
|
||||
<div className="d-inline-block w-100">
|
||||
|
||||
<div className="card p-0 overflow-hidden position-relative radius-12 h-100">
|
||||
<div className="card-body p-24">
|
||||
<ul className="d-flex flex-column gap-2">
|
||||
{catMenu.map((menu) => (
|
||||
<li
|
||||
className={`nav-item border rounded-2 px-3 py-3 bg-border-theme d-flex align-items-center gap-3 ${catMenuActive === menu.menuname ? "bg-theme " : ""
|
||||
}`}
|
||||
role="presentation"
|
||||
key={menu?.name}
|
||||
onClick={() => handleCatMenuClick(menu?.name)}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<img
|
||||
src="/assets/images/menu/menu-icons/all-menu.png"
|
||||
alt="all menu"
|
||||
className="w-28-px h-28-px"
|
||||
/>
|
||||
<span className="line-height-1">{menu?.menuname}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu Items - col-xxl-10 if cart is empty, else col-xxl-7 */}
|
||||
<div className="col-xxl-7">
|
||||
<div className="card p-0 overflow-hidden position-relative radius-12 mb-3">
|
||||
<div className="card-body p-24">
|
||||
<ul className="d-flex gap-2 mb-0">
|
||||
{menuData.map((menu) => (
|
||||
<li
|
||||
className={`nav-item border rounded-2 px-3 py-3 bg-tb-lilac d-flex align-items-center gap-3 ${activeCategory === menu.name ? "bg-danger-500 text-white" : ""
|
||||
className={`nav-item border rounded-2 px-3 py-1 bg-outline-theme d-flex align-items-center gap-3 ${activeCategory === menu.name ? "bg-theme" : ""
|
||||
}`}
|
||||
role="presentation"
|
||||
key={menu?.name}
|
||||
@ -296,27 +458,83 @@ const MenuItemsCategory = () => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Menu Items - col-xxl-10 if cart is empty, else col-xxl-7 */}
|
||||
<div className={cart.length === 0 ? "col-xxl-10" : "col-xxl-7"}>
|
||||
<div className="card p-0 overflow-hidden position-relative radius-12 h-100">
|
||||
<div className="card-body p-24">
|
||||
<div className="row gy-4">
|
||||
{menuItems?.menuitems_child?.map(renderMenuItem)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Cart - Show only if cart has items */}
|
||||
{cart.length > 0 && (
|
||||
{/* {cart.length > 0 && ( */}
|
||||
<div className="col-xxl-3">
|
||||
<div className="card p-0 overflow-hidden position-relative radius-12 h-100">
|
||||
<div className="card-body p-24">
|
||||
<h6 className="text-lg mb-3">Order Details</h6>
|
||||
{cart.map((item) => (
|
||||
<div key={item.name} className="card bg-tb-lilac mb-3 border">
|
||||
<h6 className="text-lg mb-3">POS Dine-in</h6>
|
||||
<div className="d-flex flex-wrap align-items-center gap-4 mb-3">
|
||||
<div className="d-flex flex-wrap align-items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
|
||||
>
|
||||
Items
|
||||
<span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary-600 border border-white">
|
||||
{orderItems.length}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="d-flex flex-wrap align-items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-warning-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
|
||||
>
|
||||
Table Name
|
||||
{/* <span className="position-absolute top-0 end-0 translate-middle-y badge rounded-pill bg-danger-600">
|
||||
99+
|
||||
</span> */}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="d-flex flex-wrap align-items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
|
||||
>
|
||||
Members
|
||||
<span className="position-absolute top-0 end-0 translate-middle-y badge rounded-pill bg-success-600 border border-white ">
|
||||
3
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="mb-1 border-bottom pb-3 mb-3">
|
||||
<ul className="list-group radius-8">
|
||||
<li className="list-group-item d-flex align-items-center justify-content-between border text-secondary-light p-6 bg-base border-bottom-0">
|
||||
<div className="d-flex text-sm align-items-center gap-2">
|
||||
<img className="w-20-px h-20-px rounded-circle" alt="" src="/assets/images/lists/list-img1.png" />
|
||||
Contact Number
|
||||
</div>
|
||||
<span className="text-xs bg-success-100 text-success-600 radius-4 px-10 py-2 fw-semibold cursor-pointer" data-bs-toggle="tooltip" title="This is the contact number">Show</span>
|
||||
</li>
|
||||
|
||||
<li className="list-group-item d-flex align-items-center justify-content-between border text-secondary-light p-6 bg-base">
|
||||
<div className="d-flex align-items-center text-sm gap-2">
|
||||
<img className="w-20-px h-20-px rounded-circle" alt="" src="/assets/images/lists/list-img2.png" />
|
||||
Customer Info
|
||||
</div>
|
||||
<span className="text-xs bg-success-100 text-success-600 radius-4 px-10 py-2 fw-semibold cursor-pointer" data-bs-toggle="tooltip" title="Customer details here">Show</span>
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{orderItems.length === 0 ? (
|
||||
<p className="text-muted">Your cart is empty.</p>
|
||||
) : (
|
||||
<>
|
||||
{orderItems.map((item) => (
|
||||
<div key={item.name} className="card bg-theme mb-3 border">
|
||||
<div className="card-body p-3">
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
@ -343,7 +561,7 @@ const MenuItemsCategory = () => {
|
||||
<div className="text-end mb-2">
|
||||
<button
|
||||
className="btn btn-sm btn-link text-danger-500 p-0 ms-2"
|
||||
onClick={() => removeFromCart(item.id)}
|
||||
onClick={() => removeFromCart(item.name)}
|
||||
>
|
||||
<Icon icon="mdi:delete" className="fs-5" />
|
||||
</button>
|
||||
@ -373,22 +591,160 @@ const MenuItemsCategory = () => {
|
||||
|
||||
<div className="order-summary border-top pt-3">
|
||||
<button
|
||||
className="btn bg-danger-500 text-white w-100"
|
||||
className="btn btn-bg-theme w-100"
|
||||
onClick={handlePlaceOrder}
|
||||
>
|
||||
Place Order
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* )} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
{showItemModal && selectedItem && (
|
||||
|
||||
<>
|
||||
<div
|
||||
className="modal-backdrop fade show"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0, left: 0, right: 0, bottom: 0,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
zIndex: 1040
|
||||
}}
|
||||
onClick={() => setShowItemModal(false)}
|
||||
/>
|
||||
<div className="modal fade show" style={{ display: 'block', zIndex: 1050 }}>
|
||||
<div className="modal-dialog modal-xl modal-dialog-centered">
|
||||
<div className="modal-content radius-12 overflow-hidden">
|
||||
<div className="modal-header border-0">
|
||||
<h6 className="modal-title text-lg">{selectedItem.menuitemname}</h6>
|
||||
<button type="button" className="btn-close" onClick={() => setShowItemModal(false)} />
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="row gy-3">
|
||||
{/* Col 1 - Image */}
|
||||
<div className="col-md-4">
|
||||
<img
|
||||
src="https://images.immediate.co.uk/production/volatile/sites/30/2020/08/chorizo-mozarella-gnocchi-bake-cropped-9ab73a3.jpg?quality=90&resize=700,636"
|
||||
alt={selectedItem.menuitemname}
|
||||
className="w-100 rounded"
|
||||
style={{ objectFit: 'cover' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Col 2 - Size + Quantity */}
|
||||
<div className="col-md-4">
|
||||
<h6 className="mb-2 text-lg">Choose Size</h6>
|
||||
<select className="form-select mb-3">
|
||||
<option>Regular</option>
|
||||
<option>Large</option>
|
||||
<option>Family</option>
|
||||
</select>
|
||||
|
||||
<h6 className="mb-1 text-lg">Quantity</h6>
|
||||
{isInCart ? (
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<button
|
||||
className="btn btn-sm btn-outline-danger-500"
|
||||
onClick={() => updateQuantity(selectedItem.name, cartItem.quantity - 1)}
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span className="mx-2 border border-white px-3 py-1 radius-6">{cartItem.quantity}</span>
|
||||
<button
|
||||
className="btn btn-sm btn-outline-success-500"
|
||||
onClick={() => updateQuantity(selectedItem.name, cartItem.quantity + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => addToCart(selectedItem)}
|
||||
className="btn-bg-theme text-white p-10 text-sm btn-sm px-14 py-12 radius-8 d-flex align-items-center justify-content-center mt-2 fw-medium gap-2 w-100"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Col 3 - Extras */}
|
||||
{/* Col 3 - Extras + Order Button + Total */}
|
||||
<div className="col-md-4">
|
||||
|
||||
<h6 className="mb-2 text-lg">Add Extras</h6>
|
||||
<div className="d-flex flex-wrap gap-3 mb-3">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn text-sm btn-sm px-14 py-6 radius-8 d-flex justify-content-between align-items-center gap-2 border ${selectedExtras.includes('cheese') ? 'btn-bg-theme text-white' : 'btn-outline-theme'}`}
|
||||
onClick={() => toggleExtra('cheese')}
|
||||
>
|
||||
<span>Extra Cheese</span>
|
||||
<span>$1.50</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn text-sm btn-sm px-14 py-6 d-flex justify-content-between align-items-center gap-2 border ${selectedExtras.includes('sauce') ? 'btn-bg-theme text-white' : 'btn-outline-theme'}`}
|
||||
onClick={() => toggleExtra('sauce')}
|
||||
>
|
||||
<span>Spicy Sauce</span>
|
||||
<span>$0.75</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h6 className="mb-1 text-lg">Drinks</h6>
|
||||
<div className="d-flex flex-column gap-2 mb-2">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn text-sm btn-sm px-14 py-6 d-flex justify-content-between align-items-center border ${selectedDrink === 'coke' ? 'btn-bg-theme text-white' : 'btn-outline-theme'}`}
|
||||
onClick={() => selectDrink('coke')}
|
||||
>
|
||||
<span>Coke</span>
|
||||
<span>$2.00</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn text-sm btn-sm px-14 py-6 d-flex justify-content-between align-items-center border ${selectedDrink === 'pepsi' ? 'btn-bg-theme text-white' : 'btn-outline-theme'}`}
|
||||
onClick={() => selectDrink('pepsi')}
|
||||
>
|
||||
<span>Pepsi</span>
|
||||
<span>$2.00</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="bg-success-100 p-3 mb-3 mt-3 rounded">
|
||||
<h6 className="mb-2">Total: <span className="text-success-500">$12.99</span></h6>
|
||||
</div>
|
||||
|
||||
<button className="btn btn-bg-theme w-100 mb-3 text-white" onClick={handleAddToOrder}>
|
||||
Add to Order
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{/*
|
||||
<div className="modal-footer border-0">
|
||||
<button className="btn btn-danger-500 w-100 text-white">Add to Order</button>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
{/* Order Confirmation Modal */}
|
||||
{
|
||||
showOrderModal && (
|
||||
|
||||
@ -158,7 +158,7 @@ const TableOrderInner = () => {
|
||||
|
||||
// Push the route with formatted timestamp
|
||||
router.push(
|
||||
`/waiter/menu-category?restaurantbranch=${restaruntBranch}&table=${encodeURIComponent(selectedTable.name)}&seats=${seatCount}&time=${encodeURIComponent(formattedTime)}`
|
||||
`/waiter/menu-items?restaurantbranch=${restaruntBranch}&table=${encodeURIComponent(selectedTable.name)}&seats=${seatCount}&time=${encodeURIComponent(formattedTime)}`
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -525,7 +525,7 @@ const TableOrderInner = () => {
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header border-0 pb-0">
|
||||
<h6 className="modal-title text-lg">Enter Seat Count</h6>
|
||||
<h6 className="modal-title text-lg">Enter Seat Count - {selectedTable.tablename}</h6>
|
||||
<button type="button" className="btn-close" onClick={() => setShowModalTable(false)}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
@ -552,8 +552,8 @@ const TableOrderInner = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer border-0 pt-0">
|
||||
<button type="button" className="btn btn-sm btn-outline-danger" onClick={() => setShowModalTable(false)}>Cancel</button>
|
||||
<button type="button" className="btn btn-sm btn-danger-500" onClick={handleSeatSubmit}>Proceed to Menu</button>
|
||||
<button type="button" className="btn btn-sm btn-outline-theme" onClick={() => setShowModalTable(false)}>Cancel</button>
|
||||
<button type="button" className="btn btn-sm btn-bg-theme" onClick={handleSeatSubmit}>Proceed to Menu</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
323
src/components/admin/SidesCategoryComponent.jsx
Normal file
323
src/components/admin/SidesCategoryComponent.jsx
Normal file
@ -0,0 +1,323 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { gradientClasses } from "../../../utils/constant.utils";
|
||||
import client from "../../../Auth";
|
||||
import { Icon } from "@iconify/react";
|
||||
import PageNoData from "../common-component/PageNoData";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
|
||||
const SidesCategoryComponent = ({ sidesCategoryData, getSidesCategory }) => {
|
||||
|
||||
const router = useRouter()
|
||||
const params = useParams();
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [selectedSidesCategoryId, setSelectedSidesCategoryId] = useState(null);
|
||||
const [formData, setFormData] = useState({
|
||||
sidesCategoryname: "",
|
||||
description: "",
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
|
||||
const [restaruntBranch, setRestaruntBranch] = useState("")
|
||||
|
||||
useEffect(() => {
|
||||
const restarunt = localStorage.getItem("restaurantbranch")
|
||||
setRestaruntBranch(restarunt)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const isLogin = JSON.parse(localStorage.getItem("isLogin"));
|
||||
if (!isLogin) {
|
||||
router.push(`/admin?restaurantbranch=${restaruntBranch}`);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const newErrors = {};
|
||||
Object.entries(formData).forEach(([key, value]) => {
|
||||
if (!value.trim()) newErrors[key] = `${key} is required`;
|
||||
});
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
|
||||
const body = {
|
||||
// ...(isEditMode && { name: selectedFloorId }), // only adds `name` if editing
|
||||
sidecategoryname: formData?.sidesCategoryname,
|
||||
description: formData?.description,
|
||||
restaurantbranch: restaruntBranch
|
||||
};
|
||||
try {
|
||||
if (isEditMode) {
|
||||
await client.put(`/Dine360%20Food%20Sides%20Category/${selectedSidesCategoryId}`, body);
|
||||
} else {
|
||||
await client.post(`/Dine360%20Food%20Sides%20Category`, body);
|
||||
}
|
||||
getSidesCategory();
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("SidesCategory with this name already exists. Please use a different name.");
|
||||
} else if (
|
||||
error?.response?.data?.exception?.includes("UniqueValidationError") ||
|
||||
error?.response?.data?.message?.includes("UniqueValidationError entry")
|
||||
) {
|
||||
alert("SidesCategory with this name already exists. Please use a different name.");
|
||||
} else {
|
||||
alert("An error occurred. Please try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFormData({
|
||||
sidesCategoryname: "", description: "",
|
||||
});
|
||||
setShowModal(false);
|
||||
setIsEditMode(false);
|
||||
setSelectedSidesCategoryId(null);
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
const handleEdit = (sidesCategory) => {
|
||||
setIsEditMode(true);
|
||||
setSelectedSidesCategoryId(sidesCategory.name);
|
||||
setFormData({
|
||||
sidesCategoryname: sidesCategory.sidesCategoryname || "",
|
||||
description: sidesCategory.description || "",
|
||||
});
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await client.delete(`/Dine360%20Food%20Sides%20Category/${deleteConfirm.id}`);
|
||||
setDeleteConfirm({ show: false, id: null });
|
||||
getSidesCategory();
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("SidesCategory with this name already exists. Please use a different name.");
|
||||
} else if (error?.response?.data?.exception?.includes("LinkExistsError") ||
|
||||
error?.response?.data?.message?.includes("LinkExistsError")) {
|
||||
alert(" Cannot delete or cancel because Dine360 SidesCategory three is linked with Dine360 Room ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||
<div className="card h-100 p-0 radius-12">
|
||||
<div className="card-body p-3 p-lg-5">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 className="mb-0">SidesCategory</h6>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-bg-theme radius-8 px-20 py-11"
|
||||
onClick={() => setShowModal(true)}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="row gy-3 gx-3 gx-lg-5 gy-lg-5 justify-content-center">
|
||||
{
|
||||
sidesCategoryData?.lenght === 0 ? (
|
||||
<PageNoData />
|
||||
) : (
|
||||
<>
|
||||
{sidesCategoryData.map((sidesCategory, index) => {
|
||||
const gradientClass = gradientClasses[index % gradientClasses.length];
|
||||
return (
|
||||
<div className="col-xxl-3 col-lg-4 col-sm-6" key={sidesCategory.name}>
|
||||
<div className={`card p-3 shadow-2 radius-8 h-100 bg-theme border border-white position-relative`}>
|
||||
|
||||
{/* Top-right action buttons */}
|
||||
<div className="position-absolute top-0 end-0 me-1 mt-1 ">
|
||||
<div className='dropdown'>
|
||||
<button
|
||||
className='btn px-1 py-1 d-flex align-items-center text-primary-light'
|
||||
type='button'
|
||||
data-bs-toggle='dropdown'
|
||||
aria-expanded='false'
|
||||
>
|
||||
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
|
||||
</button>
|
||||
<ul className='dropdown-menu'>
|
||||
<li>
|
||||
<Link
|
||||
href="#"
|
||||
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleEdit(sidesCategory);
|
||||
}}
|
||||
> <Icon icon='lucide:edit' className='menu-icon' />
|
||||
Edit
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
href="#"
|
||||
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900'
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setDeleteConfirm({ show: true, id: sidesCategory.name });
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
icon='fluent:delete-24-regular'
|
||||
className='menu-icon'
|
||||
/>
|
||||
Delete
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Centered content */}
|
||||
<Link href={`/admin/pos/sides?restaurantbranch=${restaruntBranch}&sidescategoryname=${sidesCategory.name}`} className="text-decoration-none text-dark">
|
||||
<div className="card-body text-center d-flex flex-column justify-content-center align-items-center p-0 h-100">
|
||||
<h6 className="text-lg mb-0">{sidesCategory.sidecategoryname}</h6>
|
||||
<p className="mb-0 text-sm">{sidesCategory.description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create/Edit Modal */}
|
||||
{showModal && (
|
||||
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header border-0 pb-0">
|
||||
<h6 className="modal-title text-lg">
|
||||
{isEditMode ? "Edit SidesCategory" : "Create SidesCategory"}
|
||||
</h6>
|
||||
<button type="button" className="btn-close" onClick={resetForm}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">SidesCategory Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className={`form-control ${errors.sidesCategoryname ? "is-invalid" : ""}`}
|
||||
name="sidesCategoryname"
|
||||
value={formData.sidesCategoryname}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{errors.sidesCategoryname && (
|
||||
<div className="invalid-feedback">{errors.sidesCategoryname}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<textarea
|
||||
className={`form-control ${errors.description ? "is-invalid" : ""}`}
|
||||
name="description"
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
rows="3"
|
||||
></textarea>
|
||||
{errors.description && (
|
||||
<div className="invalid-feedback">{errors.description}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="mb-3">
|
||||
<label className="form-label">Branch</label>
|
||||
<select
|
||||
className={`form-select ${errors.branch ? "is-invalid" : ""}`}
|
||||
name="branch"
|
||||
value={formData.branch}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">Select Branch</option>
|
||||
{branchData.map((branch) => (
|
||||
<option key={branch.name} value={branch.name}>
|
||||
{branch.branch}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{errors.branch && (
|
||||
<div className="invalid-feedback">{errors.branch}</div>
|
||||
)}
|
||||
</div> */}
|
||||
|
||||
<div className="d-flex justify-content-end">
|
||||
<button type="submit" className="btn btn-bg-theme">
|
||||
{isEditMode ? "Update" : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{deleteConfirm.show && (
|
||||
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
|
||||
<div className="modal-body">
|
||||
<div className="d-flex justify-content-between mb-1">
|
||||
<h6 className="text-lg mb-0">Confirm Delete</h6>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={() => setDeleteConfirm({ show: false, id: null })}
|
||||
></button>
|
||||
</div>
|
||||
<p className="m-0">Are you sure you want to delete this sidesCategory?</p>
|
||||
<div className="d-flex justify-content-end gap-2 mt-1 ">
|
||||
<button
|
||||
className="btn btn-outline-danger px-14 py-6 text-sm"
|
||||
onClick={() => setDeleteConfirm({ show: false, id: null })}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidesCategoryComponent;
|
||||
Loading…
x
Reference in New Issue
Block a user