sides edit module updated and menuitem create and update flow set
This commit is contained in:
parent
a75de8c589
commit
f9f29e885e
@ -386,7 +386,7 @@ const ProductListInner = () => {
|
|||||||
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"
|
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) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.push(`/admin/pos/update-product?menuitemname=${menu.name}`)
|
router.push(`/admin/pos/update-product?category=${activeCategory}&menuitemname=${menu.name}`)
|
||||||
// handleFloorEdit(menu);
|
// handleFloorEdit(menu);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import "highlight.js/styles/github.css";
|
import "highlight.js/styles/github.css";
|
||||||
import client from "@auth";
|
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 { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import axios from "axios";
|
||||||
|
import { Baseurl } from "@utils/BaseUrl.utils";
|
||||||
|
|
||||||
const UpdateProduct = () => {
|
const UpdateProduct = () => {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const searchParams = useSearchParams()
|
const category = searchParams.get("category");
|
||||||
const category = searchParams.get('category')
|
const MenuItemName = searchParams.get("menuitemname");
|
||||||
console.log("category", category)
|
|
||||||
|
|
||||||
const [imagePreview, setImagePreview] = useState(null);
|
const [imagePreview, setImagePreview] = useState(null);
|
||||||
const [imageFile, setImageFile] = useState(null);
|
const [imageFile, setImageFile] = useState(null);
|
||||||
|
const [errors, setErrors] = useState({});
|
||||||
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
menuitemname: "",
|
menuitemname: "",
|
||||||
@ -26,7 +28,33 @@ const UpdateProduct = () => {
|
|||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
useEffect(() => {
|
||||||
|
if (category) {
|
||||||
|
getMenuFoodItems();
|
||||||
|
}
|
||||||
|
}, [category]);
|
||||||
|
|
||||||
|
const getMenuFoodItems = async () => {
|
||||||
|
try {
|
||||||
|
const res = await client.get(
|
||||||
|
`/Dine360%20Menu%20Category/${category}?fields=["*"]&limit_page_length=100`
|
||||||
|
);
|
||||||
|
const item = res?.data?.data?.menuitems_child?.[0];
|
||||||
|
if (item) {
|
||||||
|
setFormData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...item,
|
||||||
|
is_active: !!item.is_active,
|
||||||
|
is_special: !!item.is_special,
|
||||||
|
}));
|
||||||
|
if (item.image_item) {
|
||||||
|
setImagePreview(`${process.env.NEXT_PUBLIC_IMAGE_BASE}/${item.image_item}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching menu items:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value, type, checked } = e.target;
|
const { name, value, type, checked } = e.target;
|
||||||
@ -66,7 +94,7 @@ const UpdateProduct = () => {
|
|||||||
if (!formData.description || formData.description.trim().length < 5) {
|
if (!formData.description || formData.description.trim().length < 5) {
|
||||||
newErrors.description = "Description is too short";
|
newErrors.description = "Description is too short";
|
||||||
}
|
}
|
||||||
if (!imageFile) {
|
if (!imageFile && !imagePreview) {
|
||||||
newErrors.image_item = "Image is required";
|
newErrors.image_item = "Image is required";
|
||||||
}
|
}
|
||||||
return newErrors;
|
return newErrors;
|
||||||
@ -81,19 +109,6 @@ const UpdateProduct = () => {
|
|||||||
}
|
}
|
||||||
setErrors({});
|
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 = {
|
const body = {
|
||||||
menucategoryname: category,
|
menucategoryname: category,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
@ -107,41 +122,47 @@ const UpdateProduct = () => {
|
|||||||
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: formData.preparation_time,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await client.post(`/Dine360%20Menu%20Category/${category}`, body);
|
const formDataToSend = new FormData();
|
||||||
console.log('res', res)
|
formDataToSend.append("endpoint", `Dine360%20Menu%20Category/${category}`);
|
||||||
|
formDataToSend.append("body", JSON.stringify(body));
|
||||||
|
formDataToSend.append("file", imageFile); // ✅ use imageFile
|
||||||
|
formDataToSend.append("fileid", "image_item");
|
||||||
|
formDataToSend.append("data", JSON.stringify({ name: MenuItemName }));
|
||||||
|
const response = await axios.put(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "token 482beca79d9c005:b8778f51fcca82b",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("response", response);
|
||||||
alert("Form submitted successfully!");
|
alert("Form submitted successfully!");
|
||||||
|
router.push("/admin/pos/product-list");
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
}
|
}
|
||||||
catch (error) {
|
|
||||||
console.log("error", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MasterLayout>
|
<MasterLayout>
|
||||||
{/* Breadcrumb */}
|
<Breadcrumb title="Create Product" />
|
||||||
<Breadcrumb title='Create Product' />
|
|
||||||
<div className="container" style={{ marginBottom: "100px" }}>
|
<div className="container" style={{ marginBottom: "100px" }}>
|
||||||
<div className='row gy-4 d-flex justify-content-center'>
|
<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="col-xxl-6 col-xl-6 col-lg-7 col-md-9">
|
||||||
<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
|
||||||
<form onSubmit={handleSubmit} className='d-flex flex-column gap-20'>
|
onSubmit={handleSubmit}
|
||||||
|
className="d-flex flex-column gap-20"
|
||||||
|
>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xl-6">
|
<div className="col-xl-6">
|
||||||
<label
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
className='form-label fw-bold text-neutral-900'
|
|
||||||
htmlFor='title'
|
|
||||||
>
|
|
||||||
Menu Item Name
|
Menu Item Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -149,47 +170,55 @@ const UpdateProduct = () => {
|
|||||||
name="menuitemname"
|
name="menuitemname"
|
||||||
value={formData.menuitemname}
|
value={formData.menuitemname}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="form-control border border-neutral-200 radius-8"
|
className="form-control border border-neutral-200 radius-8"
|
||||||
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">Price ($)</label>
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
|
Price ($)
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="price"
|
name="price"
|
||||||
value={formData.price}
|
value={formData.price}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="form-control border border-neutral-200 radius-8 "
|
className="form-control border border-neutral-200 radius-8"
|
||||||
/>
|
/>
|
||||||
{errors.price && <div className="text-danger small">{errors.price}</div>}
|
{errors.price && (
|
||||||
|
<div className="text-danger small">
|
||||||
|
{errors.price}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="form-label fw-bold text-neutral-900">Description</label>
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
|
Description
|
||||||
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
name="description"
|
name="description"
|
||||||
rows={4}
|
rows={4}
|
||||||
className="form-control border border-neutral-200 radius-8"
|
className="form-control border border-neutral-200 radius-8"
|
||||||
placeholder="Enter description..."
|
placeholder="Enter description..."
|
||||||
value={formData.description}
|
value={formData.description}
|
||||||
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>
|
||||||
|
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{/* Is Active */}
|
<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"
|
||||||
name="is_active"
|
name="is_active"
|
||||||
@ -198,13 +227,14 @@ const UpdateProduct = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
id="is_active"
|
id="is_active"
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="is_active">
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
htmlFor="is_active"
|
||||||
|
>
|
||||||
Is Active
|
Is Active
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center">
|
||||||
{/* Is Special */}
|
|
||||||
<div className="form-check col-6 d-flex justify-content-start gap-2 align-items-center ">
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="is_special"
|
name="is_special"
|
||||||
@ -213,7 +243,10 @@ const UpdateProduct = () => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
id="is_special"
|
id="is_special"
|
||||||
/>
|
/>
|
||||||
<label className="form-check-label" htmlFor="is_special">
|
<label
|
||||||
|
className="form-check-label"
|
||||||
|
htmlFor="is_special"
|
||||||
|
>
|
||||||
Is Special
|
Is Special
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@ -221,75 +254,76 @@ const UpdateProduct = () => {
|
|||||||
|
|
||||||
<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">Availability Time</label>
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
|
Availability Time
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="time"
|
type="time"
|
||||||
name="availability_time"
|
name="availability_time"
|
||||||
value={formData.availability_time}
|
value={formData.availability_time}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
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">
|
||||||
<label className="form-label fw-bold text-neutral-900">Preparation Time (minutes)</label>
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
|
Preparation Time (minutes)
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="preparation_time"
|
name="preparation_time"
|
||||||
value={formData.preparation_time}
|
value={formData.preparation_time}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
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>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className='form-label fw-bold text-neutral-900'>
|
<label className="form-label fw-bold text-neutral-900">
|
||||||
Menu Item Image
|
Menu Item Image
|
||||||
</label>
|
</label>
|
||||||
<div className='upload-image-wrapper'>
|
<div className="upload-image-wrapper">
|
||||||
{imagePreview ? (
|
{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'>
|
<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
|
<button
|
||||||
type='button'
|
type="button"
|
||||||
onClick={handleRemoveImage}
|
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'
|
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
|
||||||
icon='radix-icons:cross-2'
|
icon="radix-icons:cross-2"
|
||||||
className='text-xl text-danger-600'
|
className="text-xl text-danger-600"
|
||||||
></Icon>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<img
|
<img
|
||||||
id='uploaded-img__preview'
|
id="uploaded-img__preview"
|
||||||
className='w-100 h-100 object-fit-cover'
|
className="w-100 h-100 object-fit-cover"
|
||||||
src={imagePreview}
|
src={imagePreview}
|
||||||
alt='Uploaded'
|
alt="Uploaded"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<label
|
<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'
|
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"
|
||||||
>
|
>
|
||||||
<iconify-icon
|
<Icon icon="solar:camera-outline" className="text-xl text-secondary-light" />
|
||||||
icon='solar:camera-outline'
|
<span className="fw-semibold text-secondary-light">Upload</span>
|
||||||
className='text-xl text-secondary-light'
|
|
||||||
></iconify-icon>
|
|
||||||
<span className='fw-semibold text-secondary-light'>
|
|
||||||
Upload
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
id='upload-file'
|
id="upload-file"
|
||||||
type='file'
|
type="file"
|
||||||
hidden
|
hidden
|
||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
@ -300,14 +334,15 @@ const UpdateProduct = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button type='submit' className='btn btn-outline-theme radius-8' style={{ width: "100%" }}>
|
<button type="reset" className="btn btn-outline-theme radius-8 w-100">
|
||||||
Cancel
|
Cancel
|
||||||
</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 w-100">
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -316,7 +351,6 @@ const UpdateProduct = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MasterLayout>
|
</MasterLayout>
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const SidesPageInner = () => {
|
|||||||
description: "",
|
description: "",
|
||||||
price: "",
|
price: "",
|
||||||
item_image: null,
|
item_image: null,
|
||||||
|
image_preview: null,
|
||||||
});
|
});
|
||||||
const [errors, setErrors] = useState({});
|
const [errors, setErrors] = useState({});
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
|
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
|
||||||
@ -75,15 +76,12 @@ const SidesPageInner = () => {
|
|||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newErrors = {};
|
const newErrors = {};
|
||||||
|
|
||||||
if (!formData.sidename.trim()) newErrors.sidename = "Side Name is required";
|
if (!formData.sidename.trim()) newErrors.sidename = "Side Name is required";
|
||||||
if (!formData.description.trim()) newErrors.description = "Description is required";
|
if (!formData.description.trim()) newErrors.description = "Description is required";
|
||||||
if (!formData.price) newErrors.price = "Price is required";
|
if (!formData.price) newErrors.price = "Price is required";
|
||||||
|
|
||||||
if (!editMode && !formData.item_image) {
|
if (!editMode && !formData.item_image) {
|
||||||
newErrors.item_image = "Item image is required";
|
newErrors.item_image = "Item image is required";
|
||||||
}
|
}
|
||||||
|
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
if (Object.keys(newErrors).length > 0) return;
|
if (Object.keys(newErrors).length > 0) return;
|
||||||
|
|
||||||
@ -96,50 +94,27 @@ const SidesPageInner = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let itemId;
|
const formDataToSend = new FormData();
|
||||||
|
formDataToSend.append("endpoint", "Dine360 Food Sides");
|
||||||
|
formDataToSend.append("body", JSON.stringify(body));
|
||||||
|
if (formData.item_image instanceof File) {
|
||||||
|
formDataToSend.append("file", formData.item_image);
|
||||||
|
formDataToSend.append("fileid", "item_image");
|
||||||
|
}
|
||||||
|
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
itemId = editingRoomId;
|
formDataToSend.append("data", JSON.stringify({ name: editingRoomId }));
|
||||||
|
await client.put(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
||||||
// Step 2: Upload new image if updated
|
|
||||||
if (formData.item_image?.startsWith("data:image")) {
|
|
||||||
const uploadPath = await uploadImage(formData.item_image, "Dine360 Food Sides", itemId);
|
|
||||||
body.item_image = uploadPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Update
|
|
||||||
await client.put(`/Dine360%20Food%20Sides/${itemId}`, body);
|
|
||||||
} else {
|
|
||||||
// Step 1: Create new item with image
|
|
||||||
const formDataToSend = new FormData();
|
|
||||||
formDataToSend.append("endpoint", "Dine360 Food Sides");
|
|
||||||
formDataToSend.append("body", JSON.stringify(body));
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
const response = await axios.post(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": "token 482beca79d9c005:b8778f51fcca82b",
|
Authorization: "token 482beca79d9c005:b8778f51fcca82b",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
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();
|
getSideData();
|
||||||
@ -147,56 +122,45 @@ const SidesPageInner = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Submission error:", error);
|
console.error("❌ Submission error:", error);
|
||||||
const backendError = error?.response?.data?.exception || "";
|
const backendError = error?.response?.data?.exception || "";
|
||||||
|
|
||||||
if (backendError.includes("Data too long for column 'item_image'")) {
|
if (backendError.includes("Data too long for column 'item_image'")) {
|
||||||
setErrors((prev) => ({
|
setErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
item_image: "Data too long for column 'item_image'",
|
item_image: "Data too long for column 'item_image'",
|
||||||
}));
|
}));
|
||||||
} else if (error?.response?.status === 500) {
|
|
||||||
alert("Server error occurred. Please try again later.");
|
|
||||||
} else {
|
} else {
|
||||||
alert("Submission failed. Please try again.");
|
alert("Submission failed. Please try again.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadImage = async (base64Data, doctype, docname) => {
|
|
||||||
const blob = await (await fetch(base64Data)).blob();
|
|
||||||
const form = new FormData();
|
|
||||||
form.append("file", blob, "side.jpg");
|
|
||||||
form.append("is_private", "0");
|
|
||||||
form.append("doctype", doctype);
|
|
||||||
form.append("docname", docname);
|
|
||||||
|
|
||||||
const res = await fetch(`${ImageBase}/api/method/upload_file`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Authorization": "token 482beca79d9c005:b8778f51fcca82b",
|
|
||||||
// ❌ Don't set Content-Type when using FormData — let the browser set it
|
|
||||||
},
|
|
||||||
body: form,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await res.json();
|
|
||||||
|
|
||||||
if (!result.message?.file_url) throw new Error("Upload failed");
|
|
||||||
return result.message.file_url;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleEdit = (room) => {
|
const handleEdit = (room) => {
|
||||||
setFormData({
|
setFormData({
|
||||||
sidename: room.sidename || "",
|
sidename: room.sidename || "",
|
||||||
description: room.description || "",
|
description: room.description || "",
|
||||||
price: room.price || "",
|
price: room.price || "",
|
||||||
item_image: room.item_image || null,
|
item_image: room.item_image || null,
|
||||||
|
image_preview: null,
|
||||||
});
|
});
|
||||||
setEditingRoomId(room.name);
|
setEditingRoomId(room.name);
|
||||||
setEditMode(true);
|
setEditMode(true);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
if (formData.image_preview) URL.revokeObjectURL(formData.image_preview);
|
||||||
|
setFormData({
|
||||||
|
sidename: "",
|
||||||
|
description: "",
|
||||||
|
price: "",
|
||||||
|
item_image: null,
|
||||||
|
image_preview: null,
|
||||||
|
});
|
||||||
|
setErrors({});
|
||||||
|
setEditMode(false);
|
||||||
|
setEditingRoomId(null);
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
try {
|
try {
|
||||||
await client.delete(`/Dine360%20Food%20Sides/${deleteConfirm?.id}`);
|
await client.delete(`/Dine360%20Food%20Sides/${deleteConfirm?.id}`);
|
||||||
@ -207,32 +171,15 @@ const SidesPageInner = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
setFormData({
|
|
||||||
sidename: "",
|
|
||||||
description: "",
|
|
||||||
price: "",
|
|
||||||
item_image: null,
|
|
||||||
});
|
|
||||||
setErrors({});
|
|
||||||
setEditMode(false);
|
|
||||||
setEditingRoomId(null);
|
|
||||||
setShowModal(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||||
{/* Header + Create Button */}
|
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h6 className="mb-0">Sides</h6>
|
<h6 className="mb-0">Sides</h6>
|
||||||
<button className="btn btn-bg-theme radius-8 px-20 py-11" onClick={() => setShowModal(true)}>
|
<button className="btn btn-bg-theme radius-8 px-20 py-11" onClick={() => setShowModal(true)}>Create</button>
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Grid */}
|
|
||||||
<div className="row gy-3 gx-3 gx-lg-5 gy-lg-5 justify-content-center">
|
<div className="row gy-3 gx-3 gx-lg-5 gy-lg-5 justify-content-center">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<PageLoader />
|
<PageLoader />
|
||||||
@ -243,65 +190,31 @@ const SidesPageInner = () => {
|
|||||||
const gradientClass = gradientClasses[index % gradientClasses.length];
|
const gradientClass = gradientClasses[index % gradientClasses.length];
|
||||||
return (
|
return (
|
||||||
<div className="col-xxl-2 col-lg-4 col-sm-6 cursor-pointer" key={room?.name}>
|
<div className="col-xxl-2 col-lg-4 col-sm-6 cursor-pointer" key={room?.name}>
|
||||||
<div className={`card p-3 shadow-2 radius-8 h-100 border border-white position-relative`}>
|
<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="position-absolute top-0 end-0 me-1 mt-1 d-flex gap-2">
|
||||||
<div className="dropdown">
|
<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'>
|
||||||
<button
|
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
|
||||||
className='btn px-1 py-1 d-flex align-items-center text-primary-light'
|
</button>
|
||||||
type='button'
|
<ul className='dropdown-menu'>
|
||||||
data-bs-toggle='dropdown'
|
<li>
|
||||||
aria-expanded='false'
|
<Link href="#" className='dropdown-item' onClick={(e) => { e.preventDefault(); handleEdit(room); }}>
|
||||||
>
|
<Icon icon='lucide:edit' className='menu-icon' /> Edit
|
||||||
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
|
</Link>
|
||||||
</button>
|
</li>
|
||||||
<ul className='dropdown-menu'>
|
<li>
|
||||||
<li>
|
<Link href="#" className='dropdown-item' onClick={(e) => { e.preventDefault(); setDeleteConfirm({ show: true, id: room.name }); }}>
|
||||||
<Link
|
<Icon icon='fluent:delete-24-regular' className='menu-icon' /> Delete
|
||||||
href="#"
|
</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'
|
</li>
|
||||||
onClick={(e) => {
|
</ul>
|
||||||
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>
|
</div>
|
||||||
<div className="card-body text-center p-3">
|
<div className="card-body text-center p-3">
|
||||||
<img
|
<img src={`${ImageBase}/${room.item_image}`} alt="" className="w-100-px h-100-px rounded-circle object-fit-cover" />
|
||||||
src={`${ImageBase}/${room.item_image}`}
|
<h6 className="text-lg mb-0 mt-1">{room?.sidename}</h6>
|
||||||
alt=""
|
<span className="text-secondary-light text-sm">{room?.description}</span>
|
||||||
className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
|
<h6 className="text-md mb-0 mt-1">${room.price.toFixed(2)}</h6>
|
||||||
/>
|
|
||||||
<div className="p-0">
|
|
||||||
<h6 className="text-lg mb-0 mt-1">{room?.sidename}</h6>
|
|
||||||
<span className="text-secondary-light text-sm lh-sm mb-1">{room?.description}</span>
|
|
||||||
<h6 className="text-md mb-0 mt-1">${room.price.toFixed(2)}</h6>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -318,12 +231,11 @@ const SidesPageInner = () => {
|
|||||||
<div className="modal-dialog modal-dialog-centered">
|
<div className="modal-dialog modal-dialog-centered">
|
||||||
<div className="modal-content">
|
<div className="modal-content">
|
||||||
<div className="modal-header border-0 pb-0">
|
<div className="modal-header border-0 pb-0">
|
||||||
<h6 className="modal-title text-lg">{editMode ? "Edit Side" : "Create Side"}</h6>
|
<h6 className="modal-title">{editMode ? "Edit Side" : "Create Side"}</h6>
|
||||||
<button type="button" className="btn-close" onClick={resetForm}></button>
|
<button type="button" className="btn-close" onClick={resetForm}></button>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{/* Form fields */}
|
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Side Name</label>
|
<label className="form-label">Side Name</label>
|
||||||
<input type="text" className={`form-control ${errors.sidename ? "is-invalid" : ""}`} name="sidename" value={formData.sidename} onChange={handleChange} />
|
<input type="text" className={`form-control ${errors.sidename ? "is-invalid" : ""}`} name="sidename" value={formData.sidename} onChange={handleChange} />
|
||||||
@ -345,38 +257,55 @@ const SidesPageInner = () => {
|
|||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
<label className="form-label">Item Image</label>
|
<label className="form-label">Item Image</label>
|
||||||
<div className="d-flex align-items-center gap-3">
|
<div className="d-flex align-items-center gap-3">
|
||||||
<input type="file" accept="image/*" className={`form-control ${errors.item_image ? "is-invalid" : ""}`}
|
<input
|
||||||
style={{ maxWidth: "350px" }}
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
className={`form-control ${errors.item_image ? "is-invalid" : ""}`}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
if (!file.type.startsWith("image/")) {
|
if (!file.type.startsWith("image/")) {
|
||||||
setErrors(prev => ({ ...prev, item_image: "Only image files are allowed." }));
|
setErrors(prev => ({ ...prev, item_image: "Only image files allowed." }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size / (1024 * 1024) > 1) {
|
if (file.size / 1024 / 1024 > 1) {
|
||||||
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
|
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFormData(prev => ({ ...prev, item_image: file })); // Save raw File
|
const previewURL = URL.createObjectURL(file);
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
item_image: file,
|
||||||
|
image_preview: previewURL,
|
||||||
|
}));
|
||||||
setErrors(prev => ({ ...prev, item_image: null }));
|
setErrors(prev => ({ ...prev, item_image: null }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{formData.item_image && (
|
{(formData.image_preview || formData.item_image) && (
|
||||||
<div style={{ position: "relative", width: "60px", height: "60px" }}>
|
<div style={{ position: "relative", width: "60px", height: "60px" }}>
|
||||||
<img
|
<img
|
||||||
src={URL.createObjectURL(formData.item_image)}
|
src={
|
||||||
|
formData.image_preview
|
||||||
|
? formData.image_preview
|
||||||
|
: `${ImageBase}/${formData.item_image}`
|
||||||
|
}
|
||||||
alt="preview"
|
alt="preview"
|
||||||
className="img-thumbnail"
|
className="img-thumbnail"
|
||||||
style={{ objectFit: "cover" }}
|
style={{ objectFit: "cover", width: "100%", height: "100%" }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm btn-danger position-absolute top-0 end-0 p-1 pt-0 pb-0"
|
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 }))}
|
onClick={() =>
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
item_image: null,
|
||||||
|
image_preview: null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
@ -419,7 +348,6 @@ const SidesPageInner = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidesPage = () => (
|
const SidesPage = () => (
|
||||||
<MasterLayout>
|
<MasterLayout>
|
||||||
<Breadcrumb title="Sides Management" />
|
<Breadcrumb title="Sides Management" />
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user