sides and table create time auto enable day creation updated

This commit is contained in:
Alaguraj0361 2025-07-10 17:43:27 +05:30
parent 179975947e
commit b6695372c7
5 changed files with 154 additions and 159 deletions

View File

@ -9,6 +9,8 @@ import PageLoader from "@/components/common-component/PageLoader";
import PageNoData from "@/components/common-component/PageNoData";
import Breadcrumb from "@/components/Breadcrumb";
import { Icon } from "@iconify/react";
import axios from "axios";
import { Baseurl } from "@utils/BaseUrl.utils";
const DineInInner = () => {
@ -33,6 +35,7 @@ const DineInInner = () => {
minimumoccupancy: "",
totalcapacity: "",
description: "",
enable: 0,
});
const [errors, setErrors] = useState({});
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
@ -144,7 +147,25 @@ const DineInInner = () => {
if (isEditMode) {
await client.put(`/Dine360 Table/${selectedFloorId}`, Body);
} else {
await client.post(`/Dine360 Table`, Body);
const createTable = await client.post(`/Dine360 Table`, Body);
if (formData.enable == 1) {
const tableName = createTable?.data?.data?.name;
const RestaurantBranch = createTable?.data?.data?.restaurantbranch;
const AutoCreateBody = {
table: tableName,
restaurantbranch: RestaurantBranch,
enabled: formData.enable, // Uncomment if needed
};
try {
await axios.post(`${Baseurl}/Add-Table-To-Day`, AutoCreateBody);
} catch (apiError) {
console.error("Error while auto-creating table for the day:", apiError);
alert("Table created, but failed to assign it to the day.");
}
}
}
getTableData();
resetForm();
@ -167,6 +188,7 @@ const DineInInner = () => {
totalcapacity: "",
// branch: "",
description: "",
enable: 0,
});
setShowModal(false);
setIsEditMode(false);
@ -532,6 +554,29 @@ const DineInInner = () => {
<div className="invalid-feedback">{errors.branch}</div>
)}
</div> */}
{
!isEditMode && (
<div className="mb-3 form-check d-flex align-items-center gap-2">
<input
type="checkbox"
className="form-check-input"
id="enableTable"
name="enable"
checked={formData.enable === 1}
onChange={(e) =>
setFormData({
...formData,
enable: e.target.checked ? 1 : 0,
})
}
/>
<label className="form-check-label" htmlFor="enableTable">
Auto Generate Day table creation
</label>
</div>
)
}
<div className="d-flex justify-content-end">
<button type="submit" className="btn btn-bg-theme">

View File

@ -77,7 +77,10 @@ const SidesPageInner = () => {
if (!formData.sidename.trim()) newErrors.sidename = "Side Name is required";
if (!formData.description.trim()) newErrors.description = "Description is required";
if (!formData.price) newErrors.price = "Price is required";
if (!formData.item_image) newErrors.item_image = "Item image is required";
if (!editMode && !formData.item_image) {
newErrors.item_image = "Item image is required";
}
setErrors(newErrors);
if (Object.keys(newErrors).length > 0) return;
@ -86,23 +89,42 @@ const SidesPageInner = () => {
sidename: formData.sidename,
description: formData.description,
price: parseFloat(formData.price),
item_image: formData.item_image,
sidecategoryid: sidesCategoryName,
restaurantbranch: restaruntBranch,
};
try {
let itemId;
if (editMode) {
await client.put(`/Dine360%20Food%20Sides/${editingRoomId}`, body);
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 {
await client.post(`/Dine360%20Food%20Sides`, body);
// 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();
resetForm();
} catch (error) {
console.error("❌ Submission error:", error);
// Backend MySQL error handling
const backendError = error?.response?.data?.exception || "";
if (backendError.includes("Data too long for column 'item_image'")) {
@ -111,14 +133,30 @@ const SidesPageInner = () => {
item_image: "Data too long for column 'item_image'",
}));
} else if (error?.response?.status === 500) {
alert("Server error occurred. Please try again later or contact support.");
alert("Server error occurred. Please try again later.");
} else {
alert("Submission failed. Please check your input and 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("https://dev.dine360.ca/api/method/upload_file", {
method: "POST",
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({
@ -157,19 +195,17 @@ const SidesPageInner = () => {
return (
<div className="container-fluid" style={{ marginBottom: "100px" }}>
{/* Header + Create Button */}
<div className="card h-100 p-0 radius-12">
<div className="card-body p-3 p-lg-5">
<div className="d-flex justify-content-between align-items-center mb-3">
<h6 className="mb-0">Sides</h6>
<button
type="button"
className="btn btn-bg-theme radius-8 px-20 py-11"
onClick={() => setShowModal(true)}
>
<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 />
@ -182,46 +218,30 @@ const SidesPageInner = () => {
<div className="col-xxl-3 col-lg-4 col-sm-6 cursor-pointer" key={room?.name}>
<div className={`card p-3 shadow-2 radius-8 h-100 bg-theme border border-white position-relative`}>
<div className="position-absolute top-0 end-0 me-1 mt-1 d-flex gap-2">
<div className='dropdown'>
<button
className='btn px-1 py-1 d-flex align-items-center text-primary-light'
type='button'
data-bs-toggle='dropdown'
aria-expanded='false'
>
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
<div className="dropdown">
<button className="btn px-1 py-1 d-flex align-items-center text-primary-light" data-bs-toggle="dropdown">
<Icon icon="entypo:dots-three-vertical" />
</button>
<ul className='dropdown-menu'>
<ul className="dropdown-menu">
<li>
<Link
href="#"
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light'
onClick={(e) => {
e.preventDefault();
handleEdit(room);
}}
>
<Icon icon='lucide:edit' className='menu-icon' />
Edit
<Link href="#" className="dropdown-item" onClick={(e) => {
e.preventDefault();
handleEdit(room);
}}>
<Icon icon="lucide:edit" /> Edit
</Link>
</li>
<li>
<Link
href="#"
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light'
onClick={(e) => {
e.preventDefault();
setDeleteConfirm({ show: true, id: room.name });
}}
>
<Icon icon='fluent:delete-24-regular' className='menu-icon' />
Delete
<Link href="#" className="dropdown-item" onClick={(e) => {
e.preventDefault();
setDeleteConfirm({ show: true, id: room.name });
}}>
<Icon icon="fluent:delete-24-regular" /> Delete
</Link>
</li>
</ul>
</div>
</div>
<div className="card-body text-center p-0">
<h6 className="text-lg mt-0 mb-0">{room?.sidename}</h6>
<p className="mb-0 text-sm">{room?.description}</p>
@ -246,130 +266,61 @@ const SidesPageInner = () => {
</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}
/>
<input type="text" className={`form-control ${errors.sidename ? "is-invalid" : ""}`} name="sidename" value={formData.sidename} onChange={handleChange} />
{errors.sidename && <div className="invalid-feedback">{errors.sidename}</div>}
</div>
<div className="mb-3">
<label className="form-label">Description</label>
<textarea
className={`form-control ${errors.description ? "is-invalid" : ""}`}
name="description"
value={formData.description}
onChange={handleChange}
rows="3"
></textarea>
<textarea className={`form-control ${errors.description ? "is-invalid" : ""}`} name="description" value={formData.description} onChange={handleChange}></textarea>
{errors.description && <div className="invalid-feedback">{errors.description}</div>}
</div>
<div className="mb-3">
<label className="form-label">Price</label>
<input
type="number"
className={`form-control ${errors.price ? "is-invalid" : ""}`}
name="price"
value={formData.price}
onChange={handleChange}
/>
<input type="number" className={`form-control ${errors.price ? "is-invalid" : ""}`} name="price" value={formData.price} onChange={handleChange} />
{errors.price && <div className="invalid-feedback">{errors.price}</div>}
</div>
<div className="mb-3">
<label className="form-label">Item Image</label>
<div className="d-flex align-items-center gap-3">
{/* Input on the left */}
<input
type="file"
accept="image/*"
className={`form-control ${errors.item_image ? "is-invalid" : ""}`}
style={{ maxWidth: "350px" }}
onChange={(e) => {
const file = e.target.files[0];
if (!file) return;
<input type="file" accept="image/*" className={`form-control ${errors.item_image ? "is-invalid" : ""}`} style={{ maxWidth: "350px" }} onChange={(e) => {
const file = e.target.files[0];
if (!file) return;
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
const maxFileSizeMB = 1;
if (!file.type.startsWith("image/")) {
setErrors(prev => ({ ...prev, item_image: "Only image files are allowed." }));
return;
}
if (!allowedTypes.includes(file.type)) {
setErrors((prev) => ({
...prev,
item_image: "Only JPG, JPEG, and PNG files are allowed.",
}));
return;
}
if (file.size / (1024 * 1024) > 1) {
setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
return;
}
if (file.size / (1024 * 1024) > maxFileSizeMB) {
setErrors((prev) => ({
...prev,
item_image: "Max file size is 1MB.",
}));
return;
}
const reader = new FileReader();
reader.onloadend = () => {
setFormData((prev) => ({
...prev,
item_image: reader.result,
}));
setErrors((prev) => ({ ...prev, item_image: null }));
};
reader.readAsDataURL(file);
}}
/>
{/* Image on the right */}
const reader = new FileReader();
reader.onloadend = () => {
setFormData(prev => ({ ...prev, item_image: reader.result }));
setErrors(prev => ({ ...prev, item_image: null }));
};
reader.readAsDataURL(file);
}} />
{formData.item_image && (
<div className="position-relative" style={{ width: "50px", height: "50px" }}>
<img
src={formData.item_image}
alt="Preview"
style={{
width: "50px",
height: "50px",
objectFit: "cover",
borderRadius: "8px",
border: "1px solid #ccc",
}}
/>
{/* Delete icon top-right */}
<button
type="button"
className="btn btn-sm btn-danger position-absolute top-0 end-0 p-1"
style={{
transform: "translate(50%, -50%)",
borderRadius: "50%",
fontSize: "12px",
lineHeight: "1",
}}
onClick={() =>
setFormData((prev) => ({ ...prev, item_image: null }))
}
>
×
</button>
<div 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>
</div>
)}
</div>
{errors.item_image && <div className="invalid-feedback d-block">{errors.item_image}</div>}
</div>
<div className="d-flex justify-content-end">
<button type="submit" className="btn btn-bg-theme">
{editMode ? "Update" : "Submit"}
</button>
<button type="submit" className="btn btn-bg-theme">{editMode ? "Update" : "Submit"}</button>
</div>
</form>
</div>
@ -378,7 +329,7 @@ const SidesPageInner = () => {
</div>
)}
{/* Delete Confirmation */}
{/* Delete Modal */}
{deleteConfirm.show && (
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
<div className="modal-dialog modal-dialog-centered">
@ -388,10 +339,10 @@ const SidesPageInner = () => {
<h6 className="text-lg mb-0">Confirm Delete</h6>
<button type="button" className="btn-close" onClick={() => setDeleteConfirm({ show: false, id: null })}></button>
</div>
<p className="mb-0">Are you sure you want to delete this item?</p>
<p>Are you sure you want to delete this item?</p>
<div className="d-flex justify-content-end gap-2 mt-1">
<button className="btn btn-outline-danger px-14 py-6 text-sm" onClick={() => setDeleteConfirm({ show: false, id: null })}>Cancel</button>
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleDelete}>Delete</button>
<button className="btn btn-outline-danger" onClick={() => setDeleteConfirm({ show: false, id: null })}>Cancel</button>
<button className="btn btn-danger" onClick={handleDelete}>Delete</button>
</div>
</div>
</div>
@ -402,15 +353,13 @@ const SidesPageInner = () => {
);
};
const SidesPage = () => {
return (
<MasterLayout>
<Breadcrumb title="Sides Management" />
<Suspense fallback={<PageLoader />}>
<SidesPageInner />
</Suspense>
</MasterLayout>
);
};
const SidesPage = () => (
<MasterLayout>
<Breadcrumb title="Sides Management" />
<Suspense fallback={<PageLoader />}>
<SidesPageInner />
</Suspense>
</MasterLayout>
);
export default SidesPage;

View File

@ -392,7 +392,7 @@ const TableOrderInner = () => {
<div className="cursor-pointer"
onClick={() => handleTableClick(table)}
>
<div style={{ position: 'relative', paddingTop: "20px" }}>
<div style={{ position: 'relative', paddingTop: "20px", width: "120px" }}>
{
table?.totalcapacity <= 2 ? (
<img
@ -487,13 +487,13 @@ const TableOrderInner = () => {
}
<h6
className={`position-absolute text-md translate-middle text-white fw-bold text-center d-flex align-items-center justify-content-center ${table?.status === "Available" ? "bg-success-500" : table?.status === "Booked" ? "bg-warning-300" : "bg-secondary-500"}`}
className={`position-absolute text-xs translate-middle text-white fw-bold text-center d-flex align-items-center justify-content-center ${table?.status === "Available" ? "bg-success-500" : table?.status === "Booked" ? "bg-warning-300" : "bg-secondary-500"}`}
style={{
pointerEvents: "none",
top: "55%",
left: "50%",
width: "60px",
height: "60px",
width: "50px",
height: "50px",
borderRadius: "50%",
}}

View File

@ -79,6 +79,7 @@ const DayReservationTableEnableInner = () => {
}
};
console.log("tableConfigs", tableConfigs)
console.log("tables", tables)
const formatTime = (timeStr) => {
if (!timeStr) return '';

View File

@ -94,7 +94,7 @@ const SidesCategoryComponent = ({ sidesCategoryData, getSidesCategory }) => {
setIsEditMode(true);
setSelectedSidesCategoryId(sidesCategory.name);
setFormData({
sidesCategoryname: sidesCategory.sidesCategoryname || "",
sidesCategoryname: sidesCategory.sidecategoryname || "",
description: sidesCategory.description || "",
});
setShowModal(true);