menu, menu category modulefully completed menu item create and update page ui updated
This commit is contained in:
parent
5ba2b7581c
commit
a75de8c589
309
src/app/admin/(pos-system)/pos/(product)/create-product/page.jsx
Normal file
309
src/app/admin/(pos-system)/pos/(product)/create-product/page.jsx
Normal file
@ -0,0 +1,309 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import "highlight.js/styles/github.css";
|
||||
import client from "@auth";
|
||||
import { Icon } from "@iconify/react";
|
||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import axios from "axios";
|
||||
import { Baseurl } from "@utils/BaseUrl.utils";
|
||||
|
||||
const AddNewProduct = () => {
|
||||
const searchParams = useSearchParams();
|
||||
const category = searchParams.get("category");
|
||||
const router = useRouter();
|
||||
|
||||
const [imagePreview, setImagePreview] = useState(null);
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
menuitemname: "",
|
||||
price: 0,
|
||||
is_active: false,
|
||||
is_special: false,
|
||||
availability_time: "",
|
||||
preparation_time: 0,
|
||||
description: "",
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setImageFile(file);
|
||||
setImagePreview(URL.createObjectURL(file));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveImage = () => {
|
||||
setImageFile(null);
|
||||
setImagePreview(null);
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.menuitemname || formData.menuitemname.trim().length < 3) {
|
||||
newErrors.menuitemname = "Menu Item Name is required";
|
||||
}
|
||||
if (!formData.price || Number(formData.price) <= 0) {
|
||||
newErrors.price = "Price must be greater than 0";
|
||||
}
|
||||
if (!formData.availability_time) {
|
||||
newErrors.availability_time = "Availability Time is required";
|
||||
}
|
||||
if (!formData.preparation_time || Number(formData.preparation_time) <= 0) {
|
||||
newErrors.preparation_time = "Preparation Time must be greater than 0";
|
||||
}
|
||||
if (!formData.description || formData.description.trim().length < 5) {
|
||||
newErrors.description = "Description is too short";
|
||||
}
|
||||
if (!imageFile) {
|
||||
newErrors.image_item = "Image is required";
|
||||
}
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const validationErrors = validate();
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors);
|
||||
return;
|
||||
}
|
||||
setErrors({});
|
||||
|
||||
const body = {
|
||||
menucategoryname: category,
|
||||
description: formData.description,
|
||||
is_active: 0,
|
||||
doctype: "Dine360 Menu Category",
|
||||
menuitems_child: [
|
||||
{
|
||||
menuitemname: formData.menuitemname,
|
||||
price: formData.price,
|
||||
is_active: formData.is_active ? 1 : 0,
|
||||
is_special: formData.is_special ? 1 : 0,
|
||||
availability_time: formData.availability_time,
|
||||
preparation_time: formData.preparation_time,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
console.log("response", response);
|
||||
alert("Form submitted successfully!");
|
||||
router.push("/admin/pos/product-list");
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MasterLayout>
|
||||
<Breadcrumb title="Create Product" />
|
||||
<div className="container" style={{ marginBottom: "100px" }}>
|
||||
<div className="row gy-4 d-flex justify-content-center">
|
||||
<div className="col-xxl-6 col-xl-6 col-lg-7 col-md-9">
|
||||
<div className="card mt-24 p-lg-3">
|
||||
<div className="card-body">
|
||||
<h6 className="text-xl mb-3">Add New Product</h6>
|
||||
<form onSubmit={handleSubmit} className="d-flex flex-column gap-20">
|
||||
<div className="row">
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||
Menu Item Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="menuitemname"
|
||||
value={formData.menuitemname}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
{errors.menuitemname && (
|
||||
<div className="text-danger small">{errors.menuitemname}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">Price ($)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="price"
|
||||
value={formData.price}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.price && (
|
||||
<div className="text-danger small">{errors.price}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
rows={4}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
placeholder="Enter description..."
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
{errors.description && (
|
||||
<div className="text-danger small">{errors.description}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_active"
|
||||
className="form-check-input"
|
||||
checked={formData.is_active}
|
||||
onChange={handleChange}
|
||||
id="is_active"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_active">
|
||||
Is Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_special"
|
||||
className="form-check-input"
|
||||
checked={formData.is_special}
|
||||
onChange={handleChange}
|
||||
id="is_special"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_special">
|
||||
Is Special
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||
Availability Time
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
name="availability_time"
|
||||
value={formData.availability_time}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.availability_time && (
|
||||
<div className="text-danger small">{errors.availability_time}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||
Preparation Time (minutes)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="preparation_time"
|
||||
value={formData.preparation_time}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.preparation_time && (
|
||||
<div className="text-danger small">{errors.preparation_time}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="form-label fw-bold text-neutral-900 mb-0">
|
||||
Menu Item Image
|
||||
</label>
|
||||
<div className="upload-image-wrapper">
|
||||
{imagePreview ? (
|
||||
<div className="uploaded-img position-relative h-160-px w-100 border input-form-light radius-8 overflow-hidden border-dashed bg-neutral-50">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRemoveImage}
|
||||
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"
|
||||
>
|
||||
<Icon icon="radix-icons:cross-2" className="text-xl text-danger-600" />
|
||||
</button>
|
||||
<img
|
||||
id="uploaded-img__preview"
|
||||
className="w-100 h-100 object-fit-cover"
|
||||
src={imagePreview}
|
||||
alt="Uploaded"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<label
|
||||
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"
|
||||
>
|
||||
<Icon icon="solar:camera-outline" className="text-xl text-secondary-light" />
|
||||
<span className="fw-semibold text-secondary-light">Upload</span>
|
||||
<input id="upload-file" type="file" hidden onChange={handleFileChange} />
|
||||
</label>
|
||||
)}
|
||||
{errors.image_item && (
|
||||
<div className="text-danger small">{errors.image_item}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-6">
|
||||
<button
|
||||
className="btn btn-outline-theme radius-8"
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => router.push("/admin/pos/product-list")}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<button type="submit" className="btn btn-bg-theme radius-8" style={{ width: "100%" }}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MasterLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddNewProduct;
|
||||
851
src/app/admin/(pos-system)/pos/(product)/product-list/page.jsx
Normal file
851
src/app/admin/(pos-system)/pos/(product)/product-list/page.jsx
Normal file
@ -0,0 +1,851 @@
|
||||
"use client";
|
||||
import React, { Suspense, useEffect, useState } from "react";
|
||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||
import client from "@auth";
|
||||
import { Icon } from "@iconify/react";
|
||||
import PageLoader from "@/components/common-component/PageLoader";
|
||||
import PageNoData from "@/components/common-component/PageNoData";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const ProductListInner = () => {
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const [menuData, setMenuData] = useState(null);
|
||||
const [menuItems, setMenuItems] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [activeCategory, setActiveCategory] = useState(null);
|
||||
const [catMenu, setCatMenu] = useState(null);
|
||||
const [catMenuActive, setCatMenuActive] = useState(null);
|
||||
|
||||
const [menuFieldsModel, setMenuFieldsModel] = useState(false);
|
||||
const [isMenuFieldEditMode, setIsMenuFieldEditMode] = useState(false);
|
||||
const [editingMenuId, setEditingMenuId] = useState(null);
|
||||
const [formErrors, setFormErrors] = useState({});
|
||||
const [menuFieldDeleteConfirm, setMenuFieldDeleteConfirm] = useState({ show: false, id: null });
|
||||
const [menuFieldsFormData, setMenuFieldsFormData] = useState({
|
||||
menuname: "",
|
||||
description: "",
|
||||
is_active: 0,
|
||||
});
|
||||
|
||||
const [menuCategoryModel, setMenuCategoryModel] = useState(false);
|
||||
const [isMenuCategoryEditMode, setIsMenuCategoryEditMode] = useState(false);
|
||||
const [editingMenuCategoryId, setEditingMenuCategoryId] = useState(null);
|
||||
const [formMenuCategoryErrors, setFormMenuCategoryErrors] = useState({});
|
||||
const [menuCategoryDeleteConfirm, setMenuCategoryDeleteConfirm] = useState({ show: false, id: null });
|
||||
const [menuCategoryFormData, setMenuCategoryFormData] = useState({
|
||||
menucategoryname: "",
|
||||
description: "",
|
||||
is_active: 0,
|
||||
});
|
||||
|
||||
const [menuItemDeleteConfirm, setMenuItemDeleteConfirm] = 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(() => {
|
||||
getMenuItem();
|
||||
}, []);
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (catMenu?.length > 0) {
|
||||
getMenuCategoryItems(catMenu[0]?.menuname);
|
||||
}
|
||||
}, [catMenu]);
|
||||
|
||||
useEffect(() => {
|
||||
if (menuData?.length > 0) {
|
||||
getMenuFoodItems(menuData[0]);
|
||||
setActiveCategory(menuData[0]?.name);
|
||||
}
|
||||
}, [menuData]);
|
||||
|
||||
const getMenuCategoryItems = async (menuname) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const menuRes = await client.get(
|
||||
`/Dine360%20Menu%20Category%20Link?fields=["*"]&limit_page_length=100&filters=[["menu","=","${menuname}"]]`
|
||||
);
|
||||
|
||||
const menuLinks = menuRes?.data?.data || [];
|
||||
console.log("menuLinks", menuLinks)
|
||||
|
||||
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 categories = await Promise.all(menuCategoryPromises);
|
||||
const validCategories = categories.filter(Boolean);
|
||||
|
||||
setMenuData(validCategories);
|
||||
} catch (error) {
|
||||
console.error("Error fetching menu categories:", error);
|
||||
setError(error?.message || "Failed to fetch menu categories");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getMenuFoodItems = async (category) => {
|
||||
if (!category?.name) return;
|
||||
try {
|
||||
const res = await client.get(
|
||||
`/Dine360%20Menu%20Category/${category.name}?fields=["*"]&limit_page_length=100`
|
||||
);
|
||||
setMenuItems(res?.data?.data || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching menu items:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCatMenuClick = async (menuname) => {
|
||||
await getMenuCategoryItems(menuname);
|
||||
setCatMenuActive(menuname);
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setMenuFieldsFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? (checked ? 1 : 0) : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const openCreateModal = () => {
|
||||
setIsMenuFieldEditMode(false);
|
||||
setEditingMenuId(null);
|
||||
setMenuFieldsFormData({ menuname: "", description: "", is_active: 0 });
|
||||
setFormErrors({});
|
||||
setMenuFieldsModel(true);
|
||||
};
|
||||
|
||||
const openEditModal = (menu) => {
|
||||
setIsMenuFieldEditMode(true);
|
||||
setEditingMenuId(menu.name);
|
||||
setMenuFieldsFormData({
|
||||
menuname: menu.menuname || "",
|
||||
description: menu.description || "",
|
||||
is_active: menu.is_active || 0,
|
||||
});
|
||||
setFormErrors({});
|
||||
setMenuFieldsModel(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setMenuFieldsModel(false);
|
||||
setFormErrors({});
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const errors = {};
|
||||
if (!menuFieldsFormData.menuname.trim()) errors.menuname = "Menu name is required";
|
||||
if (!menuFieldsFormData.description.trim()) errors.description = "Description is required";
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFormErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = {
|
||||
restaurantbranch: restaruntBranch,
|
||||
menuname: menuFieldsFormData?.menuname,
|
||||
description: menuFieldsFormData?.description,
|
||||
is_active: menuFieldsFormData?.is_active
|
||||
}
|
||||
|
||||
try {
|
||||
if (isMenuFieldEditMode && editingMenuId) {
|
||||
await client.put(`/Dine360 Menu/${editingMenuId}`, body);
|
||||
} else {
|
||||
await client.post(`/Dine360 Menu`, body);
|
||||
}
|
||||
closeModal();
|
||||
getMenuItem();
|
||||
} catch (error) {
|
||||
console.error("Error saving menu:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuFieldDelete = async () => {
|
||||
try {
|
||||
await client.delete(`/Dine360 Menu/${menuFieldDeleteConfirm.id}`);
|
||||
setMenuFieldDeleteConfirm({ show: false, id: null });
|
||||
getMenuItem();
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("Menu 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 Menu three is linked with Dine360 Menu Category ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// MenuCategory
|
||||
const handleMenuCategoryInputChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setMenuCategoryFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? (checked ? 1 : 0) : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const openMenuCategoryCreateModal = () => {
|
||||
setIsMenuCategoryEditMode(false);
|
||||
setEditingMenuCategoryId(null);
|
||||
setMenuCategoryFormData({ menucategoryname: "", description: "", is_active: 0 });
|
||||
setFormMenuCategoryErrors({});
|
||||
setMenuCategoryModel(true);
|
||||
};
|
||||
|
||||
const openMenuCategoryEditModal = (menu) => {
|
||||
setIsMenuCategoryEditMode(true);
|
||||
setEditingMenuCategoryId(menu.name);
|
||||
setMenuCategoryFormData({
|
||||
menucategoryname: menu.menucategoryname || "",
|
||||
description: menu.description || "",
|
||||
is_active: menu.is_active || 0,
|
||||
});
|
||||
setFormMenuCategoryErrors({});
|
||||
setMenuCategoryModel(true);
|
||||
};
|
||||
|
||||
const closeMenuCategoryModal = () => {
|
||||
setMenuCategoryModel(false);
|
||||
setFormMenuCategoryErrors({});
|
||||
};
|
||||
|
||||
const handleMenuCategoryFormSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const errors = {};
|
||||
if (!menuCategoryFormData.menucategoryname.trim()) errors.menucategoryname = "Menu Category name is required";
|
||||
if (!menuCategoryFormData.description.trim()) errors.description = "Description is required";
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setFormMenuCategoryErrors(errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = {
|
||||
restaurantbranch: restaruntBranch,
|
||||
menucategoryname: menuCategoryFormData?.menucategoryname,
|
||||
description: menuCategoryFormData?.description,
|
||||
is_active: menuCategoryFormData?.is_active
|
||||
}
|
||||
try {
|
||||
if (isMenuCategoryEditMode && editingMenuCategoryId) {
|
||||
await client.put(`/Dine360%20Menu%20Category/${editingMenuCategoryId}`, body);
|
||||
} else {
|
||||
const res = await client.post(`/Dine360%20Menu%20Category`, body);
|
||||
const CategoryLinkBody = {
|
||||
menu: catMenuActive,
|
||||
menucategory: res?.data?.data?.name,
|
||||
restaurantbranch: restaruntBranch,
|
||||
}
|
||||
await client.post(`/Dine360%20Menu%20Category%20Link`, CategoryLinkBody)
|
||||
}
|
||||
closeMenuCategoryModal();
|
||||
getMenuCategoryItems(catMenuActive);
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exc_type?.includes("UniqueValidationError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("Menu Category 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 Menu three is linked with Dine360 Menu Category ");
|
||||
}
|
||||
}
|
||||
};
|
||||
console.log("catMenuActive", catMenuActive)
|
||||
|
||||
const handleMenuCategoryDelete = async () => {
|
||||
try {
|
||||
await client.delete(`/Dine360%20Menu%20Category%20Link/${catMenuActive} - ${menuCategoryDeleteConfirm.id}`)
|
||||
|
||||
await client.delete(`/Dine360%20Menu%20Category/${menuCategoryDeleteConfirm.id}`);
|
||||
setMenuCategoryDeleteConfirm({ show: false, id: null });
|
||||
getMenuItem();
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("Menu 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 Menu three is linked with Dine360 Menu Category ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleMenuItemDelete = async () => {
|
||||
try {
|
||||
await client.delete(`/Dine360%20Menu%20Category/${activeCategory}/${menuItemDeleteConfirm.id}`);
|
||||
setMenuItemDeleteConfirm({ show: false, id: null });
|
||||
getMenuItem();
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
||||
error?.response?.data?.message?.includes("Duplicate entry")
|
||||
) {
|
||||
alert("Menu 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 Menu three is linked with Dine360 Menu Category ");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const renderMenuItem = (menu) => (
|
||||
<div key={menu.name} className="col-xxl-2 col-md-6 user-grid-card">
|
||||
<div className="border radius-16 overflow-hidden">
|
||||
<div className="card cursor-pointer position-relative ">
|
||||
<div className="card-body text-center p-3">
|
||||
<img
|
||||
src={menu.profileImg}
|
||||
alt=""
|
||||
className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
|
||||
/>
|
||||
<div className="p-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>
|
||||
</div>
|
||||
<div className="position-absolute top-0 end-0 me-1 mt-1" onClick={(e) => e.stopPropagation()}>
|
||||
<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();
|
||||
router.push(`/admin/pos/update-product?menuitemname=${menu.name}`)
|
||||
// handleFloorEdit(menu);
|
||||
}}
|
||||
>
|
||||
<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();
|
||||
e.stopPropagation(); // optional
|
||||
setMenuItemDeleteConfirm({ show: true, id: menu.name });
|
||||
}}
|
||||
>
|
||||
<Icon icon="fluent:delete-24-regular" className="menu-icon" />
|
||||
Delete
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||
{loading ? (
|
||||
<PageLoader />
|
||||
) : (
|
||||
<div className="row gy-4">
|
||||
<div className="col-xxl-2">
|
||||
<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">
|
||||
<li
|
||||
className="nav-item border rounded-2 px-3 py-3 d-flex align-items-center gap-3 justify-content-between"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={openCreateModal}
|
||||
>
|
||||
<div className="d-flex align-items-center gap-3 ">
|
||||
<Icon icon="lucide:plus" className="text-lg w-28-px h-28-px " />
|
||||
<span className="fw-semibold">New</span>
|
||||
</div>
|
||||
</li>
|
||||
{catMenu.map((menu) => (
|
||||
<li
|
||||
key={menu?.name}
|
||||
className={`nav-item border rounded-2 px-3 py-3 bg-border-theme d-flex align-items-center gap-3 position-relative ${catMenuActive === menu.menuname ? "bg-theme" : ""
|
||||
}`}
|
||||
role="presentation"
|
||||
onClick={() => handleCatMenuClick(menu?.menuname)}
|
||||
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">{menu?.menuname}</span>
|
||||
<div className="position-absolute top-0 end-0 me-1 mt-1" onClick={(e) => e.stopPropagation()}>
|
||||
<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();
|
||||
openEditModal(menu);// optional: prevents bubbling from link
|
||||
// handleFloorEdit(menu);
|
||||
}}
|
||||
>
|
||||
<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();
|
||||
e.stopPropagation(); // optional
|
||||
setMenuFieldDeleteConfirm({ show: true, id: menu.name });
|
||||
}}
|
||||
>
|
||||
<Icon icon="fluent:delete-24-regular" className="menu-icon" />
|
||||
Delete
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-xxl-10">
|
||||
<div className="card p-0 radius-12 mb-3">
|
||||
<div className="card-body p-24">
|
||||
<ul className="d-flex gap-2 mb-0 flex-wrap">
|
||||
{/* Create New Category Button */}
|
||||
<li
|
||||
className="nav-item border rounded-2 px-3 py-1 bg-outline-theme d-flex align-items-center gap-2"
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={openMenuCategoryCreateModal} >
|
||||
<Icon icon="lucide:plus" className="text-lg w-28-px h-28-px " />
|
||||
<span className="fw-semibold">New</span>
|
||||
</li>
|
||||
|
||||
{/* Menu Items */}
|
||||
{menuData.map((menu) => (
|
||||
<li
|
||||
key={menu?.name}
|
||||
className={`nav-item border rounded-2 px-3 py-1 bg-outline-theme position-relative d-flex justify-content-between align-items-center gap-3 ${activeCategory === menu.name ? "bg-theme text-white" : ""}`}
|
||||
role="presentation"
|
||||
onClick={() => handleMenuClick(menu)} // ✅ Keep this
|
||||
style={{ cursor: "pointer", minWidth: "150px", maxWidth: "220px" }}
|
||||
>
|
||||
{/* Left: Icon and Name */}
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<img
|
||||
src="/assets/images/menu/menu-icons/all-menu.png"
|
||||
alt="menu icon"
|
||||
className="w-28-px h-28-px"
|
||||
/>
|
||||
<span className="line-height-1">{menu?.menucategoryname}</span>
|
||||
</div>
|
||||
|
||||
{/* Three Dots Menu (Absolute) */}
|
||||
<div
|
||||
className="position-absolute"
|
||||
style={{ top: "4px", right: "4px", zIndex: 100 }}
|
||||
onClick={(e) => e.stopPropagation()} // ✅ Prevent parent click
|
||||
>
|
||||
<div className="dropdown">
|
||||
<button
|
||||
className="btn px-1 py-1 d-flex align-items-center text-primary-light"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
>
|
||||
<Icon icon="entypo:dots-three-vertical" />
|
||||
</button>
|
||||
<ul className="dropdown-menu">
|
||||
<li>
|
||||
<Link
|
||||
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"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
openMenuCategoryEditModal(menu);
|
||||
}}
|
||||
>
|
||||
<Icon icon="lucide:edit" /> Edit
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link
|
||||
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"
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMenuCategoryDeleteConfirm({ show: true, id: menu.name }); // 👈 use menu
|
||||
}}
|
||||
>
|
||||
<Icon icon="fluent:delete-24-regular" /> Delete
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row gy-4">
|
||||
<div className="col-xxl-2 col-md-6 user-grid-card">
|
||||
<div className="position-relative border radius-16 overflow-hidden h-100 w-100">
|
||||
<div className="card cursor-pointer h-100 w-100 d-flex justify-content-center align-items-center" onClick={() => router.push(`/admin/pos/create-product?category=${activeCategory}`)}>
|
||||
<div className="card-body text-center p-3 d-flex justify-content-center align-items-center">
|
||||
<div>
|
||||
<Icon icon="lucide:plus" className="text-lg w-20-px h-20-px " />
|
||||
<h6 className="text-md mb-0 mt-1">Create</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{menuItems?.menuitems_child?.map(renderMenuItem)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal */}
|
||||
{menuFieldsModel && (
|
||||
<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">{isMenuFieldEditMode ? "Edit Menu" : "Create Menu"}</h6>
|
||||
<button type="button" className="btn-close" onClick={closeModal}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Menu Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="menuname"
|
||||
className={`form-control ${formErrors.menuname ? "is-invalid" : ""}`}
|
||||
value={menuFieldsFormData.menuname}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
{formErrors.menuname && (
|
||||
<div className="invalid-feedback">{formErrors.menuname}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
className={`form-control ${formErrors.description ? "is-invalid" : ""}`}
|
||||
value={menuFieldsFormData.description}
|
||||
onChange={handleInputChange}
|
||||
rows="3"
|
||||
></textarea>
|
||||
{formErrors.description && (
|
||||
<div className="invalid-feedback">{formErrors.description}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-check mb-3 d-flex justify-content-start align-items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
id="is_active"
|
||||
name="is_active"
|
||||
checked={menuFieldsFormData.is_active === 1}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_active">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end">
|
||||
<button type="submit" className="btn btn-bg-theme">
|
||||
{isMenuFieldEditMode ? "Update" : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{menuFieldDeleteConfirm.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={() => setMenuFieldDeleteConfirm({ show: false, id: null })}
|
||||
></button>
|
||||
</div>
|
||||
<p className="m-0">Are you sure you want to delete this Menu?</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={() => setMenuFieldDeleteConfirm({ show: false, id: null })}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleMenuFieldDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Menu category create & Upadte */}
|
||||
{menuCategoryModel && (
|
||||
<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">{isMenuCategoryEditMode ? "Edit Menu Category" : "Create Menu Category"}</h6>
|
||||
<button type="button" className="btn-close" onClick={closeMenuCategoryModal}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleMenuCategoryFormSubmit}>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Menu Category Name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="menucategoryname"
|
||||
className={`form-control ${formMenuCategoryErrors.menucategoryname ? "is-invalid" : ""}`}
|
||||
value={menuCategoryFormData.menucategoryname}
|
||||
onChange={handleMenuCategoryInputChange}
|
||||
/>
|
||||
{formMenuCategoryErrors.menucategoryname && (
|
||||
<div className="invalid-feedback">{formMenuCategoryErrors.menucategoryname}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
className={`form-control ${formMenuCategoryErrors.description ? "is-invalid" : ""}`}
|
||||
value={menuCategoryFormData.description}
|
||||
onChange={handleMenuCategoryInputChange}
|
||||
rows="3"
|
||||
></textarea>
|
||||
{formMenuCategoryErrors.description && (
|
||||
<div className="invalid-feedback">{formMenuCategoryErrors.description}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-check mb-3 d-flex justify-content-start align-items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="form-check-input"
|
||||
id="is_active"
|
||||
name="is_active"
|
||||
checked={menuCategoryFormData.is_active === 1}
|
||||
onChange={handleMenuCategoryInputChange}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_active">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end">
|
||||
<button type="submit" className="btn btn-bg-theme">
|
||||
{isMenuCategoryEditMode ? "Update" : "Submit"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{menuCategoryDeleteConfirm.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={() => setMenuCategoryDeleteConfirm({ show: false, id: null })}
|
||||
></button>
|
||||
</div>
|
||||
<p className="m-0">Are you sure you want to delete this Menu?</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={() => setMenuCategoryDeleteConfirm({ show: false, id: null })}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleMenuCategoryDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{menuItemDeleteConfirm.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={() => setMenuItemDeleteConfirm({ show: false, id: null })}
|
||||
></button>
|
||||
</div>
|
||||
<p className="m-0">Are you sure you want to delete this Menu?</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={() => setMenuItemDeleteConfirm({ show: false, id: null })}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleMenuItemDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductList = () => (
|
||||
<MasterLayout>
|
||||
<Breadcrumb title="Menu Items" />
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<ProductListInner />
|
||||
</Suspense>
|
||||
</MasterLayout>
|
||||
);
|
||||
|
||||
export default ProductList;
|
||||
326
src/app/admin/(pos-system)/pos/(product)/update-product/page.jsx
Normal file
326
src/app/admin/(pos-system)/pos/(product)/update-product/page.jsx
Normal file
@ -0,0 +1,326 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import "highlight.js/styles/github.css";
|
||||
import client from "@auth";
|
||||
import { Icon } from "@iconify/react";
|
||||
import MasterLayout from "@/masterLayout/MasterLayout";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
const UpdateProduct = () => {
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const category = searchParams.get('category')
|
||||
console.log("category", category)
|
||||
|
||||
const [imagePreview, setImagePreview] = useState(null);
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
menuitemname: "",
|
||||
price: 0,
|
||||
is_active: false,
|
||||
is_special: false,
|
||||
availability_time: "",
|
||||
preparation_time: 0,
|
||||
description: "",
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
setImageFile(file);
|
||||
setImagePreview(URL.createObjectURL(file));
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveImage = () => {
|
||||
setImageFile(null);
|
||||
setImagePreview(null);
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
const newErrors = {};
|
||||
if (!formData.menuitemname || formData.menuitemname.trim().length < 3) {
|
||||
newErrors.menuitemname = "Menu Item Name is required";
|
||||
}
|
||||
if (!formData.price || Number(formData.price) <= 0) {
|
||||
newErrors.price = "Price must be greater than 0";
|
||||
}
|
||||
if (!formData.availability_time) {
|
||||
newErrors.availability_time = "Availability Time is required";
|
||||
}
|
||||
if (!formData.preparation_time || Number(formData.preparation_time) <= 0) {
|
||||
newErrors.preparation_time = "Preparation Time must be greater than 0";
|
||||
}
|
||||
if (!formData.description || formData.description.trim().length < 5) {
|
||||
newErrors.description = "Description is too short";
|
||||
}
|
||||
if (!imageFile) {
|
||||
newErrors.image_item = "Image is required";
|
||||
}
|
||||
return newErrors;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const validationErrors = validate();
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
setErrors(validationErrors);
|
||||
return;
|
||||
}
|
||||
setErrors({});
|
||||
|
||||
const data = new FormData();
|
||||
data.append("menuitemname", formData.menuitemname);
|
||||
data.append("price", formData.price.toString());
|
||||
data.append("is_active", formData.is_active ? "1" : "0");
|
||||
data.append("is_special", formData.is_special ? "1" : "0");
|
||||
data.append("availability_time", formData.availability_time);
|
||||
data.append("preparation_time", formData.preparation_time.toString());
|
||||
data.append("description", formData.description);
|
||||
if (imageFile) data.append("image_item", imageFile);
|
||||
|
||||
// for (let pair of data.entries()) {
|
||||
// console.log(pair[0], pair[1]);
|
||||
// }
|
||||
const body = {
|
||||
menucategoryname: category,
|
||||
description: formData.description,
|
||||
is_active: 0,
|
||||
doctype: "Dine360 Menu Category",
|
||||
menuitems_child: [
|
||||
{
|
||||
menuitemname: formData.menuitemname,
|
||||
price: formData.price,
|
||||
is_active: formData.is_active ? 1 : 0,
|
||||
is_special: formData.is_special ? 1 : 0,
|
||||
availability_time: formData.availability_time,
|
||||
preparation_time: formData.preparation_time,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await client.post(`/Dine360%20Menu%20Category/${category}`, body);
|
||||
console.log('res', res)
|
||||
alert("Form submitted successfully!");
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.log("error", error)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<MasterLayout>
|
||||
{/* Breadcrumb */}
|
||||
<Breadcrumb title='Create Product' />
|
||||
<div className="container" style={{ marginBottom: "100px" }}>
|
||||
<div className='row gy-4 d-flex justify-content-center'>
|
||||
<div className='col-xxl-6 col-xl-6 col-lg-7 col-md-9'>
|
||||
<div className='card mt-24 p-lg-3'>
|
||||
|
||||
<div className='card-body'>
|
||||
<h6 className='text-xl mb-3'>Add New Product</h6>
|
||||
<form onSubmit={handleSubmit} className='d-flex flex-column gap-20'>
|
||||
<div className="row">
|
||||
<div className="col-xl-6">
|
||||
<label
|
||||
className='form-label fw-bold text-neutral-900'
|
||||
htmlFor='title'
|
||||
>
|
||||
Menu Item Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="menuitemname"
|
||||
value={formData.menuitemname}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
{errors.menuitemname && (
|
||||
<div className="text-danger small">{errors.menuitemname}</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900">Price ($)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="price"
|
||||
value={formData.price}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8 "
|
||||
/>
|
||||
{errors.price && <div className="text-danger small">{errors.price}</div>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<label className="form-label fw-bold text-neutral-900">Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
rows={4}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
placeholder="Enter description..."
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
{errors.description && (
|
||||
<div className="text-danger small">{errors.description}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="row">
|
||||
{/* Is Active */}
|
||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_active"
|
||||
className="form-check-input"
|
||||
checked={formData.is_active}
|
||||
onChange={handleChange}
|
||||
id="is_active"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_active">
|
||||
Is Active
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Is Special */}
|
||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center ">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="is_special"
|
||||
className="form-check-input"
|
||||
checked={formData.is_special}
|
||||
onChange={handleChange}
|
||||
id="is_special"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_special">
|
||||
Is Special
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900">Availability Time</label>
|
||||
<input
|
||||
type="time"
|
||||
name="availability_time"
|
||||
value={formData.availability_time}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.availability_time && (
|
||||
<div className="text-danger small">{errors.availability_time}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-xl-6">
|
||||
<label className="form-label fw-bold text-neutral-900">Preparation Time (minutes)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="preparation_time"
|
||||
value={formData.preparation_time}
|
||||
onChange={handleChange}
|
||||
className="form-control border border-neutral-200 radius-8 "
|
||||
/>
|
||||
{errors.preparation_time && (
|
||||
<div className="text-danger small">{errors.preparation_time}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className='form-label fw-bold text-neutral-900'>
|
||||
Menu Item Image
|
||||
</label>
|
||||
<div className='upload-image-wrapper'>
|
||||
{imagePreview ? (
|
||||
<div className='uploaded-img position-relative h-160-px w-100 border input-form-light radius-8 overflow-hidden border-dashed bg-neutral-50'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={handleRemoveImage}
|
||||
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'
|
||||
>
|
||||
<Icon
|
||||
icon='radix-icons:cross-2'
|
||||
className='text-xl text-danger-600'
|
||||
></Icon>
|
||||
</button>
|
||||
<img
|
||||
id='uploaded-img__preview'
|
||||
className='w-100 h-100 object-fit-cover'
|
||||
src={imagePreview}
|
||||
alt='Uploaded'
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<label
|
||||
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'
|
||||
>
|
||||
<iconify-icon
|
||||
icon='solar:camera-outline'
|
||||
className='text-xl text-secondary-light'
|
||||
></iconify-icon>
|
||||
<span className='fw-semibold text-secondary-light'>
|
||||
Upload
|
||||
</span>
|
||||
<input
|
||||
id='upload-file'
|
||||
type='file'
|
||||
hidden
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
{errors.image_item && (
|
||||
<div className="text-danger small">{errors.image_item}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-6">
|
||||
<button type='submit' className='btn btn-outline-theme radius-8' style={{ width: "100%" }}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<button type='submit' className='btn btn-bg-theme radius-8' style={{ width: "100%" }}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</MasterLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateProduct;
|
||||
@ -10,6 +10,7 @@ import PageNoData from "@/components/common-component/PageNoData";
|
||||
import Breadcrumb from "@/components/Breadcrumb";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Baseurl, ImageBase } from "@utils/BaseUrl.utils";
|
||||
import axios from "axios";
|
||||
|
||||
const SidesPageInner = () => {
|
||||
const router = useRouter();
|
||||
@ -109,17 +110,36 @@ const SidesPageInner = () => {
|
||||
// Step 3: Update
|
||||
await client.put(`/Dine360%20Food%20Sides/${itemId}`, body);
|
||||
} else {
|
||||
// Step 1: Create without image
|
||||
const res = await client.post(`/Dine360%20Food%20Sides`, body);
|
||||
itemId = res?.data?.data?.name;
|
||||
// Step 1: Create new item with image
|
||||
const formDataToSend = new FormData();
|
||||
formDataToSend.append("endpoint", "Dine360 Food Sides");
|
||||
formDataToSend.append("body", JSON.stringify(body));
|
||||
|
||||
// Step 2: Upload image
|
||||
if (formData.item_image) {
|
||||
const uploadPath = await uploadImage(formData.item_image, "Dine360 Food Sides", itemId);
|
||||
// Append image file (should be File object, not base64)
|
||||
formDataToSend.append("file", formData.item_image); // `imageFile` should be a real File from <input type="file" />
|
||||
formDataToSend.append("fileid", "item_image");
|
||||
|
||||
// Step 3: Update with image
|
||||
await client.put(`/Dine360%20Food%20Sides/${itemId}`, { item_image: uploadPath });
|
||||
}
|
||||
const response = await axios.post(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
||||
headers: {
|
||||
"Authorization": "token 482beca79d9c005:b8778f51fcca82b",
|
||||
},
|
||||
});
|
||||
|
||||
// Handle response if needed
|
||||
console.log("Upload success:", response.data);
|
||||
|
||||
|
||||
// // Step 1: Create without image
|
||||
// const res = await client.post(`/Dine360%20Food%20Sides`, body);
|
||||
// itemId = res?.data?.data?.name;
|
||||
|
||||
// // Step 2: Upload image
|
||||
// if (formData.item_image) {
|
||||
// const uploadPath = await uploadImage(formData.item_image, "Dine360 Food Sides", itemId);
|
||||
|
||||
// // Step 3: Update with image
|
||||
// await client.put(`/Dine360%20Food%20Sides/${itemId}`, { item_image: uploadPath });
|
||||
// }
|
||||
}
|
||||
|
||||
getSideData();
|
||||
@ -226,53 +246,53 @@ const SidesPageInner = () => {
|
||||
<div className={`card p-3 shadow-2 radius-8 h-100 border border-white position-relative`}>
|
||||
<div className="position-absolute top-0 end-0 me-1 mt-1 d-flex gap-2">
|
||||
<div className="dropdown">
|
||||
<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(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 bg-hover-neutral-200 text-hover-neutral-900'
|
||||
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 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(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 bg-hover-neutral-200 text-hover-neutral-900'
|
||||
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>
|
||||
<div className="card-body text-center p-3">
|
||||
<img
|
||||
src={`${ImageBase}/api${room.item_image}`}
|
||||
src={`${ImageBase}/${room.item_image}`}
|
||||
alt=""
|
||||
className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
|
||||
/>
|
||||
@ -325,31 +345,41 @@ const SidesPageInner = () => {
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Item Image</label>
|
||||
<div className="d-flex align-items-center gap-3">
|
||||
<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;
|
||||
<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;
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
setErrors(prev => ({ ...prev, item_image: "Only image files are allowed." }));
|
||||
return;
|
||||
}
|
||||
if (!file.type.startsWith("image/")) {
|
||||
setErrors(prev => ({ ...prev, item_image: "Only image files are allowed." }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size / (1024 * 1024) > 1) {
|
||||
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
|
||||
return;
|
||||
}
|
||||
if (file.size / (1024 * 1024) > 1) {
|
||||
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setFormData(prev => ({ ...prev, item_image: reader.result }));
|
||||
setFormData(prev => ({ ...prev, item_image: file })); // Save raw File
|
||||
setErrors(prev => ({ ...prev, item_image: null }));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
{formData.item_image && (
|
||||
<div style={{ position: "relative", width: "60px", height: "60px" }}>
|
||||
<img src={formData.item_image} alt="preview" className="img-thumbnail" style={{ objectFit: "cover" }} />
|
||||
<button type="button" className="btn btn-sm btn-danger position-absolute top-0 end-0 p-1 pt-0 pb-0" onClick={() => setFormData(prev => ({ ...prev, item_image: null }))}>×</button>
|
||||
<img
|
||||
src={URL.createObjectURL(formData.item_image)}
|
||||
alt="preview"
|
||||
className="img-thumbnail"
|
||||
style={{ objectFit: "cover" }}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm btn-danger position-absolute top-0 end-0 p-1 pt-0 pb-0"
|
||||
onClick={() => setFormData(prev => ({ ...prev, item_image: null }))}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -8,96 +8,74 @@ import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
const DayReservationTableEnableInner = () => {
|
||||
const router = useRouter();
|
||||
|
||||
// const day_name = params.name;
|
||||
const searchParams = useSearchParams();
|
||||
const day = searchParams.get('day');
|
||||
const day_name = searchParams.get('name');
|
||||
|
||||
const [tables, setTables] = useState([]);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [selectedTable, setSelectedTable] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [tableConfigs, setTableConfigs] = useState([]);
|
||||
const [selectedTable, setSelectedTable] = useState(null);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [formData, setFormData] = useState({ enabled: false, start_time: '', end_time: '', interval: '' });
|
||||
const [errors, setErrors] = useState({});
|
||||
const [restaurantBranch, setRestaurantBranch] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
enabled: false,
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
interval: ''
|
||||
});
|
||||
const [restaruntBranch, setRestaruntBranch] = useState("")
|
||||
useEffect(() => {
|
||||
const branch = localStorage.getItem('restaurantbranch');
|
||||
setRestaurantBranch(branch);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const restarunt = localStorage.getItem("restaurantbranch")
|
||||
setRestaruntBranch(restarunt)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const isLogin = JSON.parse(localStorage.getItem("isLogin"));
|
||||
const isLogin = JSON.parse(localStorage.getItem('isLogin'));
|
||||
if (!isLogin) {
|
||||
router.push(`/admin?restaurantbranch=${restaruntBranch}`);
|
||||
router.push(`/admin?restaurantbranch=${restaurantBranch}`);
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
|
||||
}, [router, restaurantBranch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (restaruntBranch && restaruntBranch !== "")
|
||||
getTableData();
|
||||
}, [restaruntBranch]);
|
||||
|
||||
if (restaurantBranch) getTableData();
|
||||
}, [restaurantBranch]);
|
||||
|
||||
const getTableData = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const [tableRes, enableTableRes] = await Promise.all([
|
||||
// client.get(`/Dine360%20Table?fields=["*"]`),
|
||||
client.get(`/Dine360%20Table?fields=["*"]&filters=[["restaurantbranch","=","${restaruntBranch}"]]`),
|
||||
setLoading(true);
|
||||
const [tableRes, configRes] = await Promise.all([
|
||||
client.get(`/Dine360%20Table?fields=["*"]&filters=[["restaurantbranch","=","${restaurantBranch}"]]`),
|
||||
client.get(`/Dine360%20Days/${day_name}?fields=["*"]`)
|
||||
]);
|
||||
|
||||
const allTables = tableRes.data.data || [];
|
||||
const enabledTables = enableTableRes.data.data?.table_configuration || [];
|
||||
const savedConfigs = configRes.data.data?.table_configuration || [];
|
||||
|
||||
setTableConfigs(enabledTables);
|
||||
setTableConfigs(savedConfigs);
|
||||
|
||||
const enabledNames = new Set(enabledTables.map((t) => t.table));
|
||||
const enabledNames = new Set(savedConfigs.filter(t => t.enabled).map(t => t.table));
|
||||
|
||||
const mergedTables = allTables.map((table) => ({
|
||||
const merged = allTables.map(table => ({
|
||||
...table,
|
||||
enabled: enabledNames.has(table.name)
|
||||
}));
|
||||
|
||||
setTables(mergedTables);
|
||||
setLoading(false)
|
||||
setTables(merged);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error fetching table or enabled list:", error);
|
||||
setLoading(false)
|
||||
console.error('Error loading table data:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
console.log("tableConfigs", tableConfigs)
|
||||
console.log("tables", tables)
|
||||
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return '';
|
||||
const parts = timeStr.split(':');
|
||||
return parts.length >= 2 ? `${parts[0].padStart(2, '0')}:${parts[1].padStart(2, '0')}` : '';
|
||||
};
|
||||
|
||||
const handleTableClick = (table) => {
|
||||
setSelectedTable(table);
|
||||
setError('');
|
||||
|
||||
const existingConfig = tableConfigs.find(cfg => cfg.table === table.name);
|
||||
if (existingConfig) {
|
||||
const existing = tableConfigs.find(cfg => cfg.table === table.name);
|
||||
|
||||
if (existing && existing.enabled) {
|
||||
setFormData({
|
||||
enabled: true,
|
||||
start_time: formatTime(existingConfig.start_time),
|
||||
end_time: formatTime(existingConfig.end_time),
|
||||
interval: existingConfig.interval?.toString() || ''
|
||||
start_time: existing.start_time?.slice(0, 5) || '',
|
||||
end_time: existing.end_time?.slice(0, 5) || '',
|
||||
interval: existing.interval?.toString() || ''
|
||||
});
|
||||
} else {
|
||||
setFormData({
|
||||
@ -109,72 +87,59 @@ const DayReservationTableEnableInner = () => {
|
||||
}
|
||||
|
||||
setShowModal(true);
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setFormData((prev) => ({
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: type === 'checkbox' ? checked : value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const handleSubmit = () => {
|
||||
const errs = {};
|
||||
if (formData.enabled) {
|
||||
if (!formData.start_time) errs.start_time = 'Start time required';
|
||||
if (!formData.end_time) errs.end_time = 'End time required';
|
||||
if (!formData.interval || isNaN(formData.interval) || formData.interval <= 0) {
|
||||
errs.interval = 'Positive interval required';
|
||||
}
|
||||
}
|
||||
|
||||
const newErrors = {};
|
||||
|
||||
if (!formData.enabled) {
|
||||
// Remove any config for this table when disabling
|
||||
setTableConfigs(prev => prev.filter(cfg => cfg.table !== selectedTable?.name));
|
||||
setShowModal(false);
|
||||
if (Object.keys(errs).length > 0) {
|
||||
setErrors(errs);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.start_time) {
|
||||
newErrors.start_time = 'Start time is required.';
|
||||
}
|
||||
if (!formData.end_time) {
|
||||
newErrors.end_time = 'End time is required.';
|
||||
}
|
||||
if (!formData.interval) {
|
||||
newErrors.interval = 'Interval is required.';
|
||||
} else if (isNaN(formData.interval) || formData.interval <= 0) {
|
||||
newErrors.interval = 'Interval must be a positive number.';
|
||||
}
|
||||
const updatedConfig = formData.enabled
|
||||
? {
|
||||
day,
|
||||
table: selectedTable.name,
|
||||
enabled: 1,
|
||||
start_time: `${formData.start_time}:00`,
|
||||
end_time: `${formData.end_time}:00`,
|
||||
interval: Number(formData.interval)
|
||||
}
|
||||
: {
|
||||
day,
|
||||
table: selectedTable.name,
|
||||
enabled: 0,
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
interval: null
|
||||
};
|
||||
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return '';
|
||||
const [hour, minute] = timeStr.split(':');
|
||||
const hh = hour.padStart(2, '0');
|
||||
const mm = minute.padStart(2, '0');
|
||||
return `${hh}:${mm}:00`;
|
||||
};
|
||||
|
||||
const newConfig = {
|
||||
day,
|
||||
table: selectedTable?.name,
|
||||
enabled: 1,
|
||||
start_time: formatTime(formData.start_time),
|
||||
end_time: formatTime(formData.end_time),
|
||||
interval: Number(formData.interval)
|
||||
};
|
||||
|
||||
// Replace or add only enabled configs
|
||||
setTableConfigs(prevConfigs => {
|
||||
const filtered = prevConfigs.filter(cfg => cfg.table !== newConfig.table);
|
||||
return [...filtered, newConfig];
|
||||
setTableConfigs(prev => {
|
||||
const others = prev.filter(cfg => cfg.table !== selectedTable.name);
|
||||
return [...others, updatedConfig];
|
||||
});
|
||||
|
||||
setShowModal(false);
|
||||
setErrors({});
|
||||
setFormData({ enabled: false, start_time: '', end_time: '', interval: '' });
|
||||
};
|
||||
|
||||
|
||||
const handleModelClose = () => {
|
||||
setShowModal(false);
|
||||
setErrors({});
|
||||
@ -182,34 +147,40 @@ const DayReservationTableEnableInner = () => {
|
||||
};
|
||||
|
||||
const handleSaveAllChanges = async () => {
|
||||
if (tableConfigs.length === 0) {
|
||||
alert("No changes to save.");
|
||||
return;
|
||||
}
|
||||
const confirm = window.confirm('Are you sure you want to save all changes?');
|
||||
if (!confirm) return;
|
||||
|
||||
const confirmed = window.confirm("Are you sure you want to save all changes?");
|
||||
if (!confirmed) return;
|
||||
|
||||
const payload = {
|
||||
name: day_name,
|
||||
table_configuration: tableConfigs
|
||||
};
|
||||
const fullConfigs = tables.map(table => {
|
||||
const cfg = tableConfigs.find(c => c.table === table.name);
|
||||
return cfg
|
||||
? cfg
|
||||
: {
|
||||
day,
|
||||
table: table.name,
|
||||
enabled: 0,
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
interval: null
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await client.put(`/Dine360%20Days/${day_name}`, payload, {
|
||||
await client.put(`/Dine360%20Days/${day_name}`, {
|
||||
name: day_name,
|
||||
table_configuration: fullConfigs
|
||||
}, {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
alert("All table configurations saved successfully!");
|
||||
alert('Saved successfully!');
|
||||
setTableConfigs([]);
|
||||
getTableData();
|
||||
} catch (error) {
|
||||
console.error("Error saving all changes:", error);
|
||||
alert("Failed to save configurations.");
|
||||
console.error('Save failed:', error);
|
||||
alert('Failed to save changes.');
|
||||
}
|
||||
};
|
||||
|
||||
const sortedTables = [...tables].sort((a, b) => a.tablename.localeCompare(b.tablename));
|
||||
console.log("sortedTables", sortedTables)
|
||||
return (
|
||||
<>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user