create product sides added configure updated
This commit is contained in:
parent
54a88fa9ea
commit
ab8ed33cd2
@ -1,13 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import "highlight.js/styles/github.css";
|
import "highlight.js/styles/github.css";
|
||||||
import client from "@auth";
|
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||||
import Breadcrumb from "@/components/Breadcrumb";
|
import Breadcrumb from "@/components/Breadcrumb";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Baseurl } from "@utils/BaseUrl.utils";
|
import { Baseurl, ImageBase } from "@utils/BaseUrl.utils";
|
||||||
|
import client from "@auth";
|
||||||
|
|
||||||
const AddNewProduct = () => {
|
const AddNewProduct = () => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -16,7 +16,7 @@ const AddNewProduct = () => {
|
|||||||
|
|
||||||
const [imagePreview, setImagePreview] = useState(null);
|
const [imagePreview, setImagePreview] = useState(null);
|
||||||
const [imageFile, setImageFile] = useState(null);
|
const [imageFile, setImageFile] = useState(null);
|
||||||
|
const [restaruntBranch, setRestaruntBranch] = useState("");
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
menuitemname: "",
|
menuitemname: "",
|
||||||
price: 0,
|
price: 0,
|
||||||
@ -28,6 +28,105 @@ const AddNewProduct = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
const [activeCategory, setActiveCategory] = useState("");
|
||||||
|
const [sidesCategoryData, setSidesCategoryData] = useState(null);
|
||||||
|
const [sidesDataByCategory, setSidesDataByCategory] = useState({}); // cache per category
|
||||||
|
const [sidesData, setSidesData] = useState(null);
|
||||||
|
|
||||||
|
// selection stored per category
|
||||||
|
const [selectionByCategory, setSelectionByCategory] = useState({}); // { [categoryName]: Set<sideName> }
|
||||||
|
const selectAllRef = useRef(null);
|
||||||
|
|
||||||
|
// derive current category selected set
|
||||||
|
const selectedSides = selectionByCategory[activeCategory] || new Set();
|
||||||
|
|
||||||
|
const isAllSelected =
|
||||||
|
sidesData && sidesData.length > 0 && selectedSides.size === sidesData.length;
|
||||||
|
const isPartialSelected =
|
||||||
|
sidesData &&
|
||||||
|
selectedSides.size > 0 &&
|
||||||
|
selectedSides.size < (sidesData?.length || 0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectAllRef.current) {
|
||||||
|
selectAllRef.current.indeterminate = !!isPartialSelected;
|
||||||
|
}
|
||||||
|
}, [isPartialSelected]);
|
||||||
|
|
||||||
|
const toggleSide = (name) => {
|
||||||
|
setSelectionByCategory((prev) => {
|
||||||
|
const prevSet = new Set(prev[activeCategory] || []);
|
||||||
|
if (prevSet.has(name)) prevSet.delete(name);
|
||||||
|
else prevSet.add(name);
|
||||||
|
return { ...prev, [activeCategory]: prevSet };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSelectAll = () => {
|
||||||
|
if (!sidesData) return;
|
||||||
|
setSelectionByCategory((prev) => {
|
||||||
|
const copy = { ...prev };
|
||||||
|
if (isAllSelected) {
|
||||||
|
copy[activeCategory] = new Set(); // clear
|
||||||
|
} else {
|
||||||
|
copy[activeCategory] = new Set(sidesData.map((s) => s.name));
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const restarunt = localStorage.getItem("restaurantbranch");
|
||||||
|
setRestaruntBranch(restarunt);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isLogin = JSON.parse(localStorage.getItem("isLogin"));
|
||||||
|
if (!isLogin) {
|
||||||
|
router.push(`/admin?restaurantbranch=${restaruntBranch}`);
|
||||||
|
}
|
||||||
|
}, [router, restaruntBranch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (restaruntBranch && restaruntBranch !== "") getSidesCategory();
|
||||||
|
}, [restaruntBranch]);
|
||||||
|
|
||||||
|
const getSidesCategory = async () => {
|
||||||
|
try {
|
||||||
|
const res = await client?.get(
|
||||||
|
`/Dine360%20Food%20Sides%20Category?fields=[%22*%22]&limit_page_length=100&filters=[["restaurantbranch","=","${restaruntBranch}"]]`
|
||||||
|
);
|
||||||
|
setSidesCategoryData(res?.data?.data);
|
||||||
|
if (res?.data?.data?.[0]) {
|
||||||
|
const first = res?.data?.data[0]?.name;
|
||||||
|
setActiveCategory(first);
|
||||||
|
await getSideData(first);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching category data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSideData = async (sidesCategoryName) => {
|
||||||
|
try {
|
||||||
|
const sideRes = await client.get(
|
||||||
|
`/Dine360%20Food%20Sides?fields=[%22*%22]&limit_page_length=100&filters=[["sidecategoryid","=","${sidesCategoryName}"]]`
|
||||||
|
);
|
||||||
|
const data = sideRes?.data?.data || [];
|
||||||
|
setSidesData(data);
|
||||||
|
setSidesDataByCategory((prev) => ({ ...prev, [sidesCategoryName]: data }));
|
||||||
|
// keep existing selectionByCategory for that category (do not clear)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching sides data:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSidesCategoryClick = async (catname) => {
|
||||||
|
setActiveCategory(catname);
|
||||||
|
await getSideData(catname);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value, type, checked } = e.target;
|
const { name, value, type, checked } = e.target;
|
||||||
@ -38,10 +137,14 @@ const AddNewProduct = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFileChange = (e) => {
|
const handleFileChange = (e) => {
|
||||||
const file = e.target.files?.[0];
|
const file = e.target.files?.[0] ?? null;
|
||||||
if (file) {
|
if (file) {
|
||||||
setImageFile(file);
|
setImageFile(file);
|
||||||
setImagePreview(URL.createObjectURL(file));
|
setImagePreview(URL.createObjectURL(file));
|
||||||
|
setErrors((prev) => {
|
||||||
|
const { item_image, ...rest } = prev;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,7 +156,7 @@ const AddNewProduct = () => {
|
|||||||
const validate = () => {
|
const validate = () => {
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
if (!formData.menuitemname || formData.menuitemname.trim().length < 3) {
|
if (!formData.menuitemname || formData.menuitemname.trim().length < 3) {
|
||||||
newErrors.menuitemname = "Menu Item Name is required";
|
newErrors.menuitemname = "Menu Item Name is required (min 3 chars)";
|
||||||
}
|
}
|
||||||
if (!formData.price || Number(formData.price) <= 0) {
|
if (!formData.price || Number(formData.price) <= 0) {
|
||||||
newErrors.price = "Price must be greater than 0";
|
newErrors.price = "Price must be greater than 0";
|
||||||
@ -68,7 +171,7 @@ const AddNewProduct = () => {
|
|||||||
newErrors.description = "Description is too short";
|
newErrors.description = "Description is too short";
|
||||||
}
|
}
|
||||||
if (!imageFile) {
|
if (!imageFile) {
|
||||||
newErrors.image_item = "Image is required";
|
newErrors.item_image = "Image is required";
|
||||||
}
|
}
|
||||||
return newErrors;
|
return newErrors;
|
||||||
};
|
};
|
||||||
@ -82,52 +185,67 @@ const AddNewProduct = () => {
|
|||||||
}
|
}
|
||||||
setErrors({});
|
setErrors({});
|
||||||
|
|
||||||
const body =
|
const body = {
|
||||||
{
|
|
||||||
menuitemname: formData.menuitemname,
|
menuitemname: formData.menuitemname,
|
||||||
price: formData.price,
|
price: Number(formData.price),
|
||||||
is_active: formData.is_active ? 1 : 0,
|
is_active: formData.is_active ? 1 : 0,
|
||||||
is_special: formData.is_special ? 1 : 0,
|
is_special: formData.is_special ? 1 : 0,
|
||||||
availability_time: formData.availability_time,
|
availability_time: formData.availability_time,
|
||||||
preparation_time: formData.preparation_time,
|
preparation_time: Number(formData.preparation_time),
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// const formDataToSend = new FormData();
|
|
||||||
// formDataToSend.append("endpoint", `Dine360%20Menu%20Category/${category}`);
|
|
||||||
// formDataToSend.append("body", JSON.stringify(body));
|
|
||||||
// formDataToSend.append("file", imageFile); // ✅ use imageFile
|
|
||||||
// formDataToSend.append("fileid", "image_item");
|
|
||||||
|
|
||||||
// const response = await axios.post(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: "token 482beca79d9c005:b8778f51fcca82b",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
const formDataToSend = new FormData();
|
const formDataToSend = new FormData();
|
||||||
formDataToSend.append("endpoint", "Dine360 Menu Category");
|
formDataToSend.append("endpoint", "Dine360 Menu Category");
|
||||||
formDataToSend.append("file", imageFile); // ✅ use imageFile
|
if (imageFile) {
|
||||||
|
formDataToSend.append("file", imageFile);
|
||||||
|
}
|
||||||
formDataToSend.append("fileid", "item_image");
|
formDataToSend.append("fileid", "item_image");
|
||||||
formDataToSend.append("childjson", JSON.stringify(body))
|
formDataToSend.append("childjson", JSON.stringify(body));
|
||||||
formDataToSend.append("childkey", "menuitems_child");
|
formDataToSend.append("childkey", "menuitems_child");
|
||||||
formDataToSend.append("docname", category);
|
formDataToSend.append("docname", category ?? "");
|
||||||
formDataToSend.append("isimageupdateorcreate", 1);
|
formDataToSend.append("isimageupdateorcreate", "1");
|
||||||
console.log(formDataToSend)
|
|
||||||
const response = await axios.post(`${Baseurl}/Upload-Image-To-Frappe/parent-child`, formDataToSend, {
|
await axios.post(
|
||||||
|
`${Baseurl}/Upload-Image-To-Frappe/parent-child`,
|
||||||
|
formDataToSend,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "token 482beca79d9c005:b8778f51fcca82b",
|
Authorization: "token 482beca79d9c005:b8778f51fcca82b",
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
console.log("response", response);
|
|
||||||
alert("Form submitted successfully!");
|
alert("Form submitted successfully!");
|
||||||
router.push("/admin/pos/product-list");
|
router.push("/admin/pos/product-list");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.error("Submission error:", error);
|
||||||
|
alert("Failed to submit. Please try again.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveSides = () => {
|
||||||
|
// aggregate all selected sides across categories
|
||||||
|
const allSelected = [];
|
||||||
|
Object.entries(selectionByCategory).forEach(([cat, setOfNames]) => {
|
||||||
|
const list = sidesDataByCategory[cat] || [];
|
||||||
|
list.forEach((side) => {
|
||||||
|
if (setOfNames.has(side?.name)) {
|
||||||
|
allSelected.push({ ...side, category: cat });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
console.log("All selected sides (full objects):", allSelected);
|
||||||
|
const names = allSelected.map((s) => s.sidename || s.name);
|
||||||
|
console.log("Selected side names:", names);
|
||||||
|
alert(`Selected sides (${names.length}): ${names.join(", ")}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MasterLayout>
|
<MasterLayout>
|
||||||
<Breadcrumb title="Create Product" />
|
<Breadcrumb title="Create Product" />
|
||||||
@ -137,7 +255,12 @@ const AddNewProduct = () => {
|
|||||||
<div className="card mt-24 p-lg-3">
|
<div className="card mt-24 p-lg-3">
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h6 className="text-xl mb-3">Add New Product</h6>
|
<h6 className="text-xl mb-3">Add New Product</h6>
|
||||||
<form onSubmit={handleSubmit} className="d-flex flex-column gap-20">
|
<form
|
||||||
|
className="d-flex flex-column gap-20"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
noValidate
|
||||||
|
>
|
||||||
|
{/* ... (form inputs same as before) ... */}
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xl-6">
|
<div className="col-xl-6">
|
||||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||||
@ -152,11 +275,15 @@ const AddNewProduct = () => {
|
|||||||
placeholder="Enter name"
|
placeholder="Enter name"
|
||||||
/>
|
/>
|
||||||
{errors.menuitemname && (
|
{errors.menuitemname && (
|
||||||
<div className="text-danger small">{errors.menuitemname}</div>
|
<div className="text-danger small">
|
||||||
|
{errors.menuitemname}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xl-6">
|
<div className="col-xl-6">
|
||||||
<label className="form-label fw-bold text-neutral-900 mb-0">Price ($)</label>
|
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||||
|
Price ($)
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="price"
|
name="price"
|
||||||
@ -183,7 +310,9 @@ const AddNewProduct = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
></textarea>
|
></textarea>
|
||||||
{errors.description && (
|
{errors.description && (
|
||||||
<div className="text-danger small">{errors.description}</div>
|
<div className="text-danger small">
|
||||||
|
{errors.description}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -201,7 +330,6 @@ const AddNewProduct = () => {
|
|||||||
Is Active
|
Is Active
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -230,7 +358,9 @@ const AddNewProduct = () => {
|
|||||||
className="form-control border border-neutral-200 radius-8"
|
className="form-control border border-neutral-200 radius-8"
|
||||||
/>
|
/>
|
||||||
{errors.availability_time && (
|
{errors.availability_time && (
|
||||||
<div className="text-danger small">{errors.availability_time}</div>
|
<div className="text-danger small">
|
||||||
|
{errors.availability_time}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xl-6">
|
<div className="col-xl-6">
|
||||||
@ -245,7 +375,9 @@ const AddNewProduct = () => {
|
|||||||
className="form-control border border-neutral-200 radius-8"
|
className="form-control border border-neutral-200 radius-8"
|
||||||
/>
|
/>
|
||||||
{errors.preparation_time && (
|
{errors.preparation_time && (
|
||||||
<div className="text-danger small">{errors.preparation_time}</div>
|
<div className="text-danger small">
|
||||||
|
{errors.preparation_time}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -263,7 +395,10 @@ const AddNewProduct = () => {
|
|||||||
className="uploaded-img__remove position-absolute top-0 end-0 z-1 text-2xxl line-height-1 me-8 mt-8 d-flex"
|
className="uploaded-img__remove position-absolute top-0 end-0 z-1 text-2xxl line-height-1 me-8 mt-8 d-flex"
|
||||||
aria-label="Remove uploaded image"
|
aria-label="Remove uploaded image"
|
||||||
>
|
>
|
||||||
<Icon icon="radix-icons:cross-2" className="text-xl text-danger-600" />
|
<Icon
|
||||||
|
icon="radix-icons:cross-2"
|
||||||
|
className="text-xl text-danger-600"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<img
|
<img
|
||||||
id="uploaded-img__preview"
|
id="uploaded-img__preview"
|
||||||
@ -277,20 +412,45 @@ const AddNewProduct = () => {
|
|||||||
className="upload-file h-160-px w-100 border input-form-light radius-8 overflow-hidden border-dashed bg-neutral-50 bg-hover-neutral-200 d-flex align-items-center flex-column justify-content-center gap-1"
|
className="upload-file h-160-px w-100 border input-form-light radius-8 overflow-hidden border-dashed bg-neutral-50 bg-hover-neutral-200 d-flex align-items-center flex-column justify-content-center gap-1"
|
||||||
htmlFor="upload-file"
|
htmlFor="upload-file"
|
||||||
>
|
>
|
||||||
<Icon icon="solar:camera-outline" className="text-xl text-secondary-light" />
|
<Icon
|
||||||
<span className="fw-semibold text-secondary-light">Upload</span>
|
icon="solar:camera-outline"
|
||||||
<input id="upload-file" type="file" hidden onChange={handleFileChange} />
|
className="text-xl text-secondary-light"
|
||||||
|
/>
|
||||||
|
<span className="fw-semibold text-secondary-light">
|
||||||
|
Upload
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
id="upload-file"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
hidden
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
{errors.image_item && (
|
{errors.item_image && (
|
||||||
<div className="text-danger small">{errors.image_item}</div>
|
<div className="text-danger small">
|
||||||
|
{errors.item_image}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-outline-theme radius-8"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
>
|
||||||
|
Link Sides
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
className="btn btn-outline-theme radius-8"
|
className="btn btn-outline-theme radius-8"
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
onClick={() => router.push("/admin/pos/product-list")}
|
onClick={() => router.push("/admin/pos/product-list")}
|
||||||
@ -299,7 +459,11 @@ const AddNewProduct = () => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button type="submit" className="btn btn-bg-theme radius-8" style={{ width: "100%" }}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-bg-theme radius-8"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -310,6 +474,182 @@ const AddNewProduct = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showModal && (
|
||||||
|
<div
|
||||||
|
className="modal fade show"
|
||||||
|
style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}
|
||||||
|
aria-modal="true"
|
||||||
|
role="dialog"
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="modal-dialog modal-xl modal-dialog-centered"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="modal-content"
|
||||||
|
style={{ backgroundColor: "#f5f6fa" }}
|
||||||
|
>
|
||||||
|
<div className="modal-header border-0 pb-0">
|
||||||
|
<h6 className="modal-title">Link Sides</h6>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-close"
|
||||||
|
aria-label="Close"
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-body">
|
||||||
|
{/* Select All / Deselect All button */}
|
||||||
|
<div className="d-flex align-items-center mb-3 gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleSelectAll}
|
||||||
|
aria-pressed={isAllSelected}
|
||||||
|
className={`btn radius-8 ${
|
||||||
|
isAllSelected ? "btn-bg-theme text-white" : "btn-outline-theme"
|
||||||
|
}`}
|
||||||
|
style={{ display: "inline-flex", alignItems: "center", gap: 6 }}
|
||||||
|
>
|
||||||
|
{isPartialSelected && (
|
||||||
|
<span
|
||||||
|
aria-label="partial"
|
||||||
|
style={{
|
||||||
|
display: "inline-block",
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#ffc107",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{isAllSelected
|
||||||
|
? "Deselect All"
|
||||||
|
: isPartialSelected
|
||||||
|
? "Select All (partial)"
|
||||||
|
: "Select All"}
|
||||||
|
</span>
|
||||||
|
<small className="text-muted" style={{ marginLeft: 4 }}>
|
||||||
|
({selectedSides.size}/{sidesData?.length || 0})
|
||||||
|
</small>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="row gy-3 gx-3 justify-content-center">
|
||||||
|
<div className="col-xl-3 col-lg-3 col-md-4 mt-3">
|
||||||
|
<div className="d-inline-block" style={{ width: "100%" }}>
|
||||||
|
<div className="card p-0 overflow-hidden radius-12 h-100">
|
||||||
|
<div className="card-body p-24">
|
||||||
|
<ul className="d-flex flex-column gap-2">
|
||||||
|
{sidesCategoryData?.map((sidesCategory) => (
|
||||||
|
<li
|
||||||
|
key={sidesCategory?.name}
|
||||||
|
className={`nav-item border rounded-2 px-3 py-3 bg-border-theme d-flex align-items-center gap-3 position-relative ${
|
||||||
|
activeCategory === sidesCategory.name
|
||||||
|
? "bg-theme text-white"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
role="presentation"
|
||||||
|
onClick={() =>
|
||||||
|
handleSidesCategoryClick(sidesCategory?.name)
|
||||||
|
}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/assets/images/menu/menu-icons/all-menu.png"
|
||||||
|
alt="menu"
|
||||||
|
className="w-28-px h-28-px"
|
||||||
|
/>
|
||||||
|
<span className="line-height-1">
|
||||||
|
{sidesCategory?.sidecategoryname}
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-xl-9 col-lg-9 col-md-8 mt-3">
|
||||||
|
<div className="row gy-3 gx-3">
|
||||||
|
{sidesData?.map((side) => {
|
||||||
|
const selected = (selectionByCategory[activeCategory] || new Set()).has(
|
||||||
|
side?.name
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="col-xxl-3 col-xl-3 col-lg-4 col-sm-6 col-6 mt-3 cursor-pointer"
|
||||||
|
key={side?.name}
|
||||||
|
onClick={() => toggleSide(side?.name)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`card p-3 shadow-2 radius-8 h-100 border position-relative ${
|
||||||
|
selected ? "bg-theme text-white" : "border-white"
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
transition: "background-color .2s, color .2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleSide(side?.name);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selected}
|
||||||
|
aria-label={`Select ${side?.sidename}`}
|
||||||
|
onChange={() => toggleSide(side?.name)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="card-body text-center p-0">
|
||||||
|
<img
|
||||||
|
src={`${ImageBase}/${side.item_image}`}
|
||||||
|
alt={side?.sidename || ""}
|
||||||
|
className="w-50-px h-50-px rounded-circle object-fit-cover"
|
||||||
|
/>
|
||||||
|
<h6 className="text-sm mb-0 mt-3">
|
||||||
|
{side?.sidename}
|
||||||
|
</h6>
|
||||||
|
<span className="text-secondary-light text-sm">
|
||||||
|
${side.price.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* footer actions */}
|
||||||
|
<div className="modal-footer border-0 pt-0">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-bg-theme radius-8"
|
||||||
|
onClick={handleSaveSides}
|
||||||
|
>
|
||||||
|
Save Selected Sides
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</MasterLayout>
|
</MasterLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user