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:
Alaguraj0361 2025-07-10 09:29:32 +05:30
parent 6df04ed684
commit 179975947e
6 changed files with 1327 additions and 165 deletions

View File

@ -13179,12 +13179,12 @@ select option {
left: 50%; left: 50%;
bottom: -1px; bottom: -1px;
transform: translateX(-50%); transform: translateX(-50%);
background-color: var(--primary-600); background-color: var(--theme-color);
transition: 0.2s linear; transition: 0.2s linear;
} }
.bordered-tab .nav-link.active { .bordered-tab .nav-link.active {
color: var(--primary-600); color: var(--theme-color);
} }
.bordered-tab .nav-link.active::before { .bordered-tab .nav-link.active::before {
@ -13194,7 +13194,7 @@ select option {
/* Bordered Tab Css End */ /* Bordered Tab Css End */
/* Pill Tab Css Start */ /* Pill Tab Css Start */
.pill-tab .nav-link.active { .pill-tab .nav-link.active {
background-color: var(--primary-600); background-color: var(--theme-color);
} }
.pill-tab.style-three { .pill-tab.style-three {

View 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;

View 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;

View File

@ -33,73 +33,155 @@ const MenuItemsCategory = () => {
const [orderStatus, setOrderStatus] = useState('pending'); const [orderStatus, setOrderStatus] = useState('pending');
const HST_TAX_RATE = 0.13; // 13% HST tax rate 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(() => { useEffect(() => {
getMenuItem();
}, []);
if (menuName) { const getMenuItem = async () => {
getMenuItems(); 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(() => { useEffect(() => {
if (menuData) { if (catMenu?.length > 0) {
getMenuFoodItems(menuData) getMenuItems(catMenu[0]?.menuname); // auto-load first menu's categories
setActiveCategory(menuData[0]?.name)
} }
}, [menuData]) }, [catMenu]);
const getMenuItems = async () => {
useEffect(() => {
if (menuData?.length > 0) {
getMenuFoodItems(menuData[0]);
setActiveCategory(menuData[0]?.name);
}
}, [menuData]);
const getMenuItems = async (menuname) => {
try { try {
setLoading(true); setLoading(true);
setError(null); setError(null);
// Fetch floor data using name const menuRes = await client.get(
const menuRes = await client.get(`/Dine360%20Menu%20Category%20Link?fields=[\"*\"]&limit_page_length=100&filters=[["menu","=","${menuName}"]]`); `/Dine360%20Menu%20Category%20Link?fields=["*"]&limit_page_length=100&filters=[["menu","=","${menuname}"]]`
console.log("menuRes", menuRes?.data?.data); );
// Get menu categories based on the menucategory from menuRes const menuLinks = menuRes?.data?.data || [];
const menuCategories = menuRes?.data?.data || [];
const menuCategoryPromises = menuCategories.map(async (menuItem) => { const menuCategoryPromises = menuLinks.map(async (menuItem) => {
const menuCategoryRes = await client.get(`/Dine360%20Menu%20Category?fields=[\"*\"]&limit_page_length=100&filters=[["name","=","${menuItem.menucategory}"]]`); const res = await client.get(
console.log("menuCategoryRes", menuCategoryRes?.data?.data); `/Dine360%20Menu%20Category?fields=["*"]&limit_page_length=100&filters=[["name","=","${menuItem.menucategory}"]]`
return menuCategoryRes?.data?.data?.[0] || null; );
return res?.data?.data?.[0] || null;
}); });
const menuCategoryResults = await Promise.all(menuCategoryPromises); const categories = await Promise.all(menuCategoryPromises);
const filteredMenuCategories = menuCategoryResults.filter(category => category !== null); const validCategories = categories.filter(Boolean);
setMenuData(filteredMenuCategories); setMenuData(validCategories);
} catch (error) { } catch (error) {
console.error("Error fetching data:", error); console.error("Error fetching menu categories:", error);
setError(error?.message || "Failed to fetch floor data"); setError(error?.message || "Failed to fetch menu categories");
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const getMenuFoodItems = (async (menuData) => { const getMenuFoodItems = async (category) => {
console.log("menuDataaaa", menuData) if (!category?.name) return;
try { try {
const res = await client.get(`/Dine360%20Menu%20Category/${menuData[0].name}?fields=[\"*\"]&limit_page_length=100`); const res = await client.get(
console.log("res", res) `/Dine360%20Menu%20Category/${category.name}?fields=["*"]&limit_page_length=100`
setMenuItems(res?.data?.data) );
setMenuItems(res?.data?.data || []);
} catch (error) { } catch (error) {
console.log("error", error) console.error("Error fetching menu items:", 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");
} }
}; };
// 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); console.log("menuItems", menuItems);
const addToCart = (item) => { const addToCart = (item) => {
const existingItem = cart.find(cartItem => cartItem.name === item.name); const existingItem = cart.find(cartItem => cartItem.name === item.name);
@ -112,24 +194,40 @@ const MenuItemsCategory = () => {
} else { } else {
setCart([...cart, { ...item, quantity: 1 }]); setCart([...cart, { ...item, quantity: 1 }]);
} }
}; };
const removeFromCart = (itemName) => { 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) => { const updateQuantity = (itemName, newQuantity) => {
if (newQuantity < 1) { if (newQuantity < 1) {
removeFromCart(itemName); removeItem(itemName);
return; return;
} }
setCart(cart.map(item =>
item.name === itemName // Update cart
? { ...item, quantity: newQuantity } setCart(prev =>
: item 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 = () => { const calculateSubtotal = () => {
return cart.reduce((total, item) => total + (item.price * item.quantity), 0); return cart.reduce((total, item) => total + (item.price * item.quantity), 0);
}; };
@ -146,7 +244,19 @@ const MenuItemsCategory = () => {
setShowOrderModal(true); 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 () => { const confirmOrder = async () => {
// Get current time in YYYY-MM-DD HH:mm:ss format (Canada Eastern Time) // Get current time in YYYY-MM-DD HH:mm:ss format (Canada Eastern Time)
@ -164,7 +274,7 @@ const MenuItemsCategory = () => {
console.log(orderStartTime); console.log(orderStartTime);
// Prepare formatted order data // Prepare formatted order data
const formattedOrder = cart.map((item) => ({ const formattedOrder = orderItems.map((item) => ({
menuitem: item.name, menuitem: item.name,
quantity: item.quantity, quantity: item.quantity,
rate: item.price, rate: item.price,
@ -212,44 +322,60 @@ const MenuItemsCategory = () => {
const renderMenuItem = (menu) => { const renderMenuItem = (menu) => {
console.log("menu", menu)
const cartItem = cart.find(item => item.name === menu.name); const cartItem = cart.find(item => item.name === menu.name);
const isInCart = !!cartItem; const isInCart = !!cartItem;
return ( return (
<div key={menu.name} className="col-xxl-3 col-md-6 user-grid-card"> <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="position-relative border radius-16 overflow-hidden">
<div className="p-16 text-center"> <div className="card cursor-pointer" onClick={() => {
<img setSelectedItem(menu);
src={menu.profileImg} getAllSidesGroupedByCategory(menu?.name)
alt="" setShowItemModal(true);
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> <div className="card-body text-center p-0">
<span className="text-secondary-light text-sm mb-16">${menu.price.toFixed(2)}</span> <img
{isInCart ? ( src={menu.profileImg}
<div className="d-flex align-items-center justify-content-center gap-2"> alt=""
<button className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
className="btn btn-sm btn-outline-danger-500" />
onClick={() => updateQuantity(menu.name, cartItem.quantity - 1)} {/* <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> */}
</button> <div className="p-3 pt-0">
<span className="mx-2 border border-white px-3 py-1 radius-6">{cartItem.quantity}</span> <h6 className="text-lg mb-1 mt-1">{menu.menuitemname}</h6>
<button <span className="text-secondary-light text-sm lh-sm mb-1">{menu.parent}</span>
className="btn btn-sm btn-outline-success-500" <h6 className="text-md mb-0 mt-1">${menu.price.toFixed(2)}</h6>
onClick={() => updateQuantity(menu.name, cartItem.quantity + 1)}
>
+
</button>
</div> </div>
) : (
<button
onClick={() => addToCart(menu)} {/* {isInCart ? (
className="bg-danger-500 text-white p-10 text-sm btn-sm px-12 py-12 radius-8 d-flex align-items-center justify-content-center mt-2 fw-medium gap-2 w-100" <div className="d-flex align-items-center justify-content-center gap-2">
> <button
Add to Cart className="btn btn-sm btn-outline-danger-500"
</button> onClick={() => updateQuantity(menu.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(menu.name, cartItem.quantity + 1)}
>
+
</button>
</div>
) : (
<button
onClick={() => addToCart(menu)}
className="bg-danger-500 text-white p-10 text-sm btn-sm px-12 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>
</div> </div>
</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 ( return (
<MasterLayout> <MasterLayout>
<Breadcrumb title={`Menu Items`} /> <Breadcrumb title={`Menu Items`} />
<div className="container-fluid"> <div className="container-fluid" style={{ marginBottom: "100px" }}>
{ {
loading ? ( loading ? (
@ -273,12 +405,42 @@ const MenuItemsCategory = () => {
<div className="row gy-4"> <div className="row gy-4">
{/* Menu Category - Always col-xxl-2 */} {/* Menu Category - Always col-xxl-2 */}
<div className="col-xxl-2"> <div className="col-xxl-2">
<div className="card p-0 overflow-hidden position-relative radius-12 h-100"> <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"> <div className="card-body p-24">
<ul className="d-flex flex-column gap-2"> <ul className="d-flex gap-2 mb-0">
{menuData.map((menu) => ( {menuData.map((menu) => (
<li <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" role="presentation"
key={menu?.name} key={menu?.name}
@ -296,99 +458,293 @@ const MenuItemsCategory = () => {
</ul> </ul>
</div> </div>
</div> </div>
</div> <div className="row gy-4">
{menuItems?.menuitems_child?.map(renderMenuItem)}
{/* 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>
</div> </div>
{/* Cart - Show only if cart has items */} {/* Cart - Show only if cart has items */}
{cart.length > 0 && ( {/* {cart.length > 0 && ( */}
<div className="col-xxl-3"> <div className="col-xxl-3">
<div className="card p-0 overflow-hidden position-relative radius-12 h-100"> <div className="card p-0 overflow-hidden position-relative radius-12 h-100">
<div className="card-body p-24"> <div className="card-body p-24">
<h6 className="text-lg mb-3">Order Details</h6> <h6 className="text-lg mb-3">POS Dine-in</h6>
{cart.map((item) => ( <div className="d-flex flex-wrap align-items-center gap-4 mb-3">
<div key={item.name} className="card bg-tb-lilac mb-3 border"> <div className="d-flex flex-wrap align-items-center gap-3">
<div className="card-body p-3"> <button
<div className="d-flex align-items-center gap-3"> type="button"
<div className="flex-shrink-0"> className="btn btn-primary-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
<img >
src={item.profileImg} Items
alt={item.menuitemname} <span className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary-600 border border-white">
className="rounded-circle" {orderItems.length}
style={{ </span>
width: "60px", </button>
height: "60px", </div>
objectFit: "cover", <div className="d-flex flex-wrap align-items-center gap-3">
}} <button
/> type="button"
</div> className="btn btn-warning-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
<div className="flex-grow-1"> >
<div className="d-flex justify-content-between align-items-center"> Table Name
<div> {/* <span className="position-absolute top-0 end-0 translate-middle-y badge rounded-pill bg-danger-600">
<h6 className="mb-0 text-md text-truncate"> 99+
{item.menuitemname} </span> */}
</h6> </button>
<p className="mb-0 text-sm">${item.price.toFixed(2)}</p> </div>
</div>
<div> <div className="d-flex flex-wrap align-items-center gap-3">
<div className="text-end mb-2"> <button
<button type="button"
className="btn btn-sm btn-link text-danger-500 p-0 ms-2" className="btn btn-success-600 position-relative px-20 py-8 text-sm line-height-1 d-flex align-items-center"
onClick={() => removeFromCart(item.id)} >
> Members
<Icon icon="mdi:delete" className="fs-5" /> <span className="position-absolute top-0 end-0 translate-middle-y badge rounded-pill bg-success-600 border border-white ">
</button> 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">
<img
src={item.profileImg}
alt={item.menuitemname}
className="rounded-circle"
style={{
width: "60px",
height: "60px",
objectFit: "cover",
}}
/>
</div>
<div className="flex-grow-1">
<div className="d-flex justify-content-between align-items-center">
<div>
<h6 className="mb-0 text-md text-truncate">
{item.menuitemname}
</h6>
<p className="mb-0 text-sm">${item.price.toFixed(2)}</p>
</div> </div>
<div className="d-flex align-items-center gap-1 mt-2"> <div>
<button <div className="text-end mb-2">
className="px-1 py-0 text-sm text-danger-500 border border-danger-500 radius-4" <button
onClick={() => updateQuantity(item.name, item.quantity - 1)} className="btn btn-sm btn-link text-danger-500 p-0 ms-2"
> onClick={() => removeFromCart(item.name)}
- >
</button> <Icon icon="mdi:delete" className="fs-5" />
<span className="mx-1 text-sm border border-white px-1 radius-4">{item.quantity}</span> </button>
<button </div>
className="px-1 py-0 text-sm text-success-500 radius-4 border border-success-500" <div className="d-flex align-items-center gap-1 mt-2">
onClick={() => updateQuantity(item.name, item.quantity + 1)} <button
> className="px-1 py-0 text-sm text-danger-500 border border-danger-500 radius-4"
+ onClick={() => updateQuantity(item.name, item.quantity - 1)}
</button> >
-
</button>
<span className="mx-1 text-sm border border-white px-1 radius-4">{item.quantity}</span>
<button
className="px-1 py-0 text-sm text-success-500 radius-4 border border-success-500"
onClick={() => updateQuantity(item.name, item.quantity + 1)}
>
+
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> ))}
))}
<div className="order-summary border-top pt-3"> <div className="order-summary border-top pt-3">
<button <button
className="btn bg-danger-500 text-white w-100" className="btn btn-bg-theme w-100"
onClick={handlePlaceOrder} onClick={handlePlaceOrder}
> >
Place Order Place Order
</button> </button>
</div> </div>
</div> </>
)}
</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 */} {/* Order Confirmation Modal */}
{ {
showOrderModal && ( showOrderModal && (

View File

@ -158,7 +158,7 @@ const TableOrderInner = () => {
// Push the route with formatted timestamp // Push the route with formatted timestamp
router.push( 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-dialog modal-dialog-centered">
<div className="modal-content"> <div className="modal-content">
<div className="modal-header border-0 pb-0"> <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> <button type="button" className="btn-close" onClick={() => setShowModalTable(false)}></button>
</div> </div>
<div className="modal-body"> <div className="modal-body">
@ -552,8 +552,8 @@ const TableOrderInner = () => {
</div> </div>
</div> </div>
<div className="modal-footer border-0 pt-0"> <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-outline-theme" 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-bg-theme" onClick={handleSeatSubmit}>Proceed to Menu</button>
</div> </div>
</div> </div>
</div> </div>

View 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;