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"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push(`/admin/pos/update-product?menuitemname=${menu.name}`)
|
||||
router.push(`/admin/pos/update-product?category=${activeCategory}&menuitemname=${menu.name}`)
|
||||
// handleFloorEdit(menu);
|
||||
}}
|
||||
>
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { useEffect, 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";
|
||||
import axios from "axios";
|
||||
import { Baseurl } from "@utils/BaseUrl.utils";
|
||||
|
||||
const UpdateProduct = () => {
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const category = searchParams.get('category')
|
||||
console.log("category", category)
|
||||
const searchParams = useSearchParams();
|
||||
const category = searchParams.get("category");
|
||||
const MenuItemName = searchParams.get("menuitemname");
|
||||
|
||||
const [imagePreview, setImagePreview] = useState(null);
|
||||
const [imageFile, setImageFile] = useState(null);
|
||||
const [errors, setErrors] = useState({});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
menuitemname: "",
|
||||
@ -26,7 +28,33 @@ const UpdateProduct = () => {
|
||||
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 { name, value, type, checked } = e.target;
|
||||
@ -66,7 +94,7 @@ const UpdateProduct = () => {
|
||||
if (!formData.description || formData.description.trim().length < 5) {
|
||||
newErrors.description = "Description is too short";
|
||||
}
|
||||
if (!imageFile) {
|
||||
if (!imageFile && !imagePreview) {
|
||||
newErrors.image_item = "Image is required";
|
||||
}
|
||||
return newErrors;
|
||||
@ -81,19 +109,6 @@ const UpdateProduct = () => {
|
||||
}
|
||||
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,
|
||||
@ -107,41 +122,47 @@ const UpdateProduct = () => {
|
||||
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)
|
||||
}
|
||||
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
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");
|
||||
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!");
|
||||
router.push("/admin/pos/product-list");
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MasterLayout>
|
||||
{/* Breadcrumb */}
|
||||
<Breadcrumb title='Create Product' />
|
||||
<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 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'
|
||||
>
|
||||
<label className="form-label fw-bold text-neutral-900">
|
||||
Menu Item Name
|
||||
</label>
|
||||
<input
|
||||
@ -153,12 +174,15 @@ const UpdateProduct = () => {
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
{errors.menuitemname && (
|
||||
<div className="text-danger small">{errors.menuitemname}</div>
|
||||
<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>
|
||||
<label className="form-label fw-bold text-neutral-900">
|
||||
Price ($)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="price"
|
||||
@ -166,13 +190,18 @@ const UpdateProduct = () => {
|
||||
onChange={handleChange}
|
||||
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>
|
||||
<label className="form-label fw-bold text-neutral-900">Description</label>
|
||||
<label className="form-label fw-bold text-neutral-900">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
rows={4}
|
||||
@ -182,13 +211,13 @@ const UpdateProduct = () => {
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
{errors.description && (
|
||||
<div className="text-danger small">{errors.description}</div>
|
||||
<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"
|
||||
@ -198,12 +227,13 @@ const UpdateProduct = () => {
|
||||
onChange={handleChange}
|
||||
id="is_active"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="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"
|
||||
@ -213,7 +243,10 @@ const UpdateProduct = () => {
|
||||
onChange={handleChange}
|
||||
id="is_special"
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="is_special">
|
||||
<label
|
||||
className="form-check-label"
|
||||
htmlFor="is_special"
|
||||
>
|
||||
Is Special
|
||||
</label>
|
||||
</div>
|
||||
@ -221,7 +254,9 @@ const UpdateProduct = () => {
|
||||
|
||||
<div className="row">
|
||||
<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
|
||||
type="time"
|
||||
name="availability_time"
|
||||
@ -230,12 +265,15 @@ const UpdateProduct = () => {
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.availability_time && (
|
||||
<div className="text-danger small">{errors.availability_time}</div>
|
||||
<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>
|
||||
<label className="form-label fw-bold text-neutral-900">
|
||||
Preparation Time (minutes)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="preparation_time"
|
||||
@ -244,52 +282,48 @@ const UpdateProduct = () => {
|
||||
className="form-control border border-neutral-200 radius-8"
|
||||
/>
|
||||
{errors.preparation_time && (
|
||||
<div className="text-danger small">{errors.preparation_time}</div>
|
||||
<div className="text-danger small">
|
||||
{errors.preparation_time}
|
||||
</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
|
||||
</label>
|
||||
<div className='upload-image-wrapper'>
|
||||
<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'>
|
||||
<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'
|
||||
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'
|
||||
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>
|
||||
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'
|
||||
id="uploaded-img__preview"
|
||||
className="w-100 h-100 object-fit-cover"
|
||||
src={imagePreview}
|
||||
alt='Uploaded'
|
||||
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'
|
||||
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>
|
||||
<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'
|
||||
id="upload-file"
|
||||
type="file"
|
||||
hidden
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
@ -300,14 +334,15 @@ const UpdateProduct = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
@ -316,7 +351,6 @@ const UpdateProduct = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</MasterLayout>
|
||||
|
||||
@ -28,6 +28,7 @@ const SidesPageInner = () => {
|
||||
description: "",
|
||||
price: "",
|
||||
item_image: null,
|
||||
image_preview: null,
|
||||
});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
|
||||
@ -75,15 +76,12 @@ const SidesPageInner = () => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const newErrors = {};
|
||||
|
||||
if (!formData.sidename.trim()) newErrors.sidename = "Side Name is required";
|
||||
if (!formData.description.trim()) newErrors.description = "Description is required";
|
||||
if (!formData.price) newErrors.price = "Price is required";
|
||||
|
||||
if (!editMode && !formData.item_image) {
|
||||
newErrors.item_image = "Item image is required";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
if (Object.keys(newErrors).length > 0) return;
|
||||
|
||||
@ -96,50 +94,27 @@ const SidesPageInner = () => {
|
||||
};
|
||||
|
||||
try {
|
||||
let itemId;
|
||||
|
||||
if (editMode) {
|
||||
itemId = editingRoomId;
|
||||
|
||||
// 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" />
|
||||
if (formData.item_image instanceof File) {
|
||||
formDataToSend.append("file", formData.item_image);
|
||||
formDataToSend.append("fileid", "item_image");
|
||||
}
|
||||
|
||||
const response = await axios.post(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
||||
if (editMode) {
|
||||
formDataToSend.append("data", JSON.stringify({ name: editingRoomId }));
|
||||
await client.put(`${Baseurl}/Upload-Image-To-Frappe`, formDataToSend, {
|
||||
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();
|
||||
@ -147,56 +122,45 @@ const SidesPageInner = () => {
|
||||
} catch (error) {
|
||||
console.error("❌ Submission error:", error);
|
||||
const backendError = error?.response?.data?.exception || "";
|
||||
|
||||
if (backendError.includes("Data too long for column 'item_image'")) {
|
||||
setErrors((prev) => ({
|
||||
...prev,
|
||||
item_image: "Data too long for column 'item_image'",
|
||||
}));
|
||||
} else if (error?.response?.status === 500) {
|
||||
alert("Server error occurred. Please try again later.");
|
||||
} else {
|
||||
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) => {
|
||||
setFormData({
|
||||
sidename: room.sidename || "",
|
||||
description: room.description || "",
|
||||
price: room.price || "",
|
||||
item_image: room.item_image || null,
|
||||
image_preview: null,
|
||||
});
|
||||
setEditingRoomId(room.name);
|
||||
setEditMode(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 () => {
|
||||
try {
|
||||
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 (
|
||||
<div className="container-fluid" style={{ marginBottom: "100px" }}>
|
||||
{/* Header + Create Button */}
|
||||
<div className="">
|
||||
<div className="">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3">
|
||||
<h6 className="mb-0">Sides</h6>
|
||||
<button className="btn btn-bg-theme radius-8 px-20 py-11" onClick={() => setShowModal(true)}>
|
||||
Create
|
||||
</button>
|
||||
<button className="btn btn-bg-theme radius-8 px-20 py-11" onClick={() => setShowModal(true)}>Create</button>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="row gy-3 gx-3 gx-lg-5 gy-lg-5 justify-content-center">
|
||||
{loading ? (
|
||||
<PageLoader />
|
||||
@ -243,65 +190,31 @@ const SidesPageInner = () => {
|
||||
const gradientClass = gradientClasses[index % gradientClasses.length];
|
||||
return (
|
||||
<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="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'
|
||||
>
|
||||
<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' 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 href="#" className='dropdown-item' 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 href="#" className='dropdown-item' 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}/${room.item_image}`}
|
||||
alt=""
|
||||
className="border br-white border-width-2-px w-100-px h-100-px rounded-circle object-fit-cover"
|
||||
/>
|
||||
<div className="p-0">
|
||||
<img src={`${ImageBase}/${room.item_image}`} alt="" className="w-100-px h-100-px rounded-circle object-fit-cover" />
|
||||
<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>
|
||||
<span className="text-secondary-light text-sm">{room?.description}</span>
|
||||
<h6 className="text-md mb-0 mt-1">${room.price.toFixed(2)}</h6>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -318,12 +231,11 @@ const SidesPageInner = () => {
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header border-0 pb-0">
|
||||
<h6 className="modal-title text-lg">{editMode ? "Edit Side" : "Create Side"}</h6>
|
||||
<h6 className="modal-title">{editMode ? "Edit Side" : "Create Side"}</h6>
|
||||
<button type="button" className="btn-close" onClick={resetForm}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{/* Form fields */}
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Side Name</label>
|
||||
<input type="text" className={`form-control ${errors.sidename ? "is-invalid" : ""}`} name="sidename" value={formData.sidename} onChange={handleChange} />
|
||||
@ -345,38 +257,55 @@ 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" }}
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className={`form-control ${errors.item_image ? "is-invalid" : ""}`}
|
||||
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." }));
|
||||
setErrors(prev => ({ ...prev, item_image: "Only image files allowed." }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size / (1024 * 1024) > 1) {
|
||||
if (file.size / 1024 / 1024 > 1) {
|
||||
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
|
||||
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 }));
|
||||
}}
|
||||
/>
|
||||
{formData.item_image && (
|
||||
{(formData.image_preview || formData.item_image) && (
|
||||
<div style={{ position: "relative", width: "60px", height: "60px" }}>
|
||||
<img
|
||||
src={URL.createObjectURL(formData.item_image)}
|
||||
src={
|
||||
formData.image_preview
|
||||
? formData.image_preview
|
||||
: `${ImageBase}/${formData.item_image}`
|
||||
}
|
||||
alt="preview"
|
||||
className="img-thumbnail"
|
||||
style={{ objectFit: "cover" }}
|
||||
style={{ objectFit: "cover", width: "100%", height: "100%" }}
|
||||
/>
|
||||
<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 }))}
|
||||
onClick={() =>
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
item_image: null,
|
||||
image_preview: null,
|
||||
}))
|
||||
}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@ -419,7 +348,6 @@ const SidesPageInner = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SidesPage = () => (
|
||||
<MasterLayout>
|
||||
<Breadcrumb title="Sides Management" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user