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 PageNoData from "@/components/common-component/PageNoData";
import Breadcrumb from "@/components/Breadcrumb"; import Breadcrumb from "@/components/Breadcrumb";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import axios from "axios";
import { Baseurl } from "@utils/BaseUrl.utils";
const DineInInner = () => { const DineInInner = () => {
@ -33,6 +35,7 @@ const DineInInner = () => {
minimumoccupancy: "", minimumoccupancy: "",
totalcapacity: "", totalcapacity: "",
description: "", description: "",
enable: 0,
}); });
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null }); const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null });
@ -144,7 +147,25 @@ const DineInInner = () => {
if (isEditMode) { if (isEditMode) {
await client.put(`/Dine360 Table/${selectedFloorId}`, Body); await client.put(`/Dine360 Table/${selectedFloorId}`, Body);
} else { } 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(); getTableData();
resetForm(); resetForm();
@ -167,6 +188,7 @@ const DineInInner = () => {
totalcapacity: "", totalcapacity: "",
// branch: "", // branch: "",
description: "", description: "",
enable: 0,
}); });
setShowModal(false); setShowModal(false);
setIsEditMode(false); setIsEditMode(false);
@ -532,6 +554,29 @@ const DineInInner = () => {
<div className="invalid-feedback">{errors.branch}</div> <div className="invalid-feedback">{errors.branch}</div>
)} )}
</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"> <div className="d-flex justify-content-end">
<button type="submit" className="btn btn-bg-theme"> <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.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 (!formData.item_image) newErrors.item_image = "Item image is required";
if (!editMode && !formData.item_image) {
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;
@ -86,23 +89,42 @@ const SidesPageInner = () => {
sidename: formData.sidename, sidename: formData.sidename,
description: formData.description, description: formData.description,
price: parseFloat(formData.price), price: parseFloat(formData.price),
item_image: formData.item_image,
sidecategoryid: sidesCategoryName, sidecategoryid: sidesCategoryName,
restaurantbranch: restaruntBranch, restaurantbranch: restaruntBranch,
}; };
try { try {
let itemId;
if (editMode) { 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 { } 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(); getSideData();
resetForm(); resetForm();
} catch (error) { } catch (error) {
console.error("❌ Submission error:", error); console.error("❌ Submission error:", error);
// Backend MySQL error handling
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'")) {
@ -111,14 +133,30 @@ const SidesPageInner = () => {
item_image: "Data too long for column 'item_image'", item_image: "Data too long for column 'item_image'",
})); }));
} else if (error?.response?.status === 500) { } 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 { } 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) => { const handleEdit = (room) => {
setFormData({ setFormData({
@ -157,19 +195,17 @@ const SidesPageInner = () => {
return ( return (
<div className="container-fluid" style={{ marginBottom: "100px" }}> <div className="container-fluid" style={{ marginBottom: "100px" }}>
{/* Header + Create Button */}
<div className="card h-100 p-0 radius-12"> <div className="card h-100 p-0 radius-12">
<div className="card-body p-3 p-lg-5"> <div className="card-body p-3 p-lg-5">
<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 <button className="btn btn-bg-theme radius-8 px-20 py-11" onClick={() => setShowModal(true)}>
type="button"
className="btn btn-bg-theme radius-8 px-20 py-11"
onClick={() => setShowModal(true)}
>
Create Create
</button> </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 />
@ -182,46 +218,30 @@ const SidesPageInner = () => {
<div className="col-xxl-3 col-lg-4 col-sm-6 cursor-pointer" key={room?.name}> <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={`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="position-absolute top-0 end-0 me-1 mt-1 d-flex gap-2">
<div className='dropdown'> <div className="dropdown">
<button <button className="btn px-1 py-1 d-flex align-items-center text-primary-light" data-bs-toggle="dropdown">
className='btn px-1 py-1 d-flex align-items-center text-primary-light' <Icon icon="entypo:dots-three-vertical" />
type='button'
data-bs-toggle='dropdown'
aria-expanded='false'
>
<Icon icon='entypo:dots-three-vertical' className='menu-icon' />
</button> </button>
<ul className='dropdown-menu'> <ul className="dropdown-menu">
<li> <li>
<Link <Link href="#" className="dropdown-item" onClick={(e) => {
href="#" e.preventDefault();
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light' handleEdit(room);
onClick={(e) => { }}>
e.preventDefault(); <Icon icon="lucide:edit" /> Edit
handleEdit(room);
}}
>
<Icon icon='lucide:edit' className='menu-icon' />
Edit
</Link> </Link>
</li> </li>
<li> <li>
<Link <Link href="#" className="dropdown-item" onClick={(e) => {
href="#" e.preventDefault();
className='dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light' setDeleteConfirm({ show: true, id: room.name });
onClick={(e) => { }}>
e.preventDefault(); <Icon icon="fluent:delete-24-regular" /> Delete
setDeleteConfirm({ show: true, id: room.name });
}}
>
<Icon icon='fluent:delete-24-regular' className='menu-icon' />
Delete
</Link> </Link>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div className="card-body text-center p-0"> <div className="card-body text-center p-0">
<h6 className="text-lg mt-0 mb-0">{room?.sidename}</h6> <h6 className="text-lg mt-0 mb-0">{room?.sidename}</h6>
<p className="mb-0 text-sm">{room?.description}</p> <p className="mb-0 text-sm">{room?.description}</p>
@ -246,130 +266,61 @@ const SidesPageInner = () => {
</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 <input type="text" className={`form-control ${errors.sidename ? "is-invalid" : ""}`} name="sidename" value={formData.sidename} onChange={handleChange} />
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>} {errors.sidename && <div className="invalid-feedback">{errors.sidename}</div>}
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Description</label> <label className="form-label">Description</label>
<textarea <textarea className={`form-control ${errors.description ? "is-invalid" : ""}`} name="description" value={formData.description} onChange={handleChange}></textarea>
className={`form-control ${errors.description ? "is-invalid" : ""}`}
name="description"
value={formData.description}
onChange={handleChange}
rows="3"
></textarea>
{errors.description && <div className="invalid-feedback">{errors.description}</div>} {errors.description && <div className="invalid-feedback">{errors.description}</div>}
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label className="form-label">Price</label> <label className="form-label">Price</label>
<input <input type="number" className={`form-control ${errors.price ? "is-invalid" : ""}`} name="price" value={formData.price} onChange={handleChange} />
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>} {errors.price && <div className="invalid-feedback">{errors.price}</div>}
</div> </div>
<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 on the left */} <input type="file" accept="image/*" className={`form-control ${errors.item_image ? "is-invalid" : ""}`} style={{ maxWidth: "350px" }} onChange={(e) => {
<input const file = e.target.files[0];
type="file" if (!file) return;
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"]; if (!file.type.startsWith("image/")) {
const maxFileSizeMB = 1; setErrors(prev => ({ ...prev, item_image: "Only image files are allowed." }));
return;
}
if (!allowedTypes.includes(file.type)) { if (file.size / (1024 * 1024) > 1) {
setErrors((prev) => ({ setErrors(prev => ({ ...prev, item_image: "Max size 1MB" }));
...prev, return;
item_image: "Only JPG, JPEG, and PNG files are allowed.", }
}));
return;
}
if (file.size / (1024 * 1024) > maxFileSizeMB) { const reader = new FileReader();
setErrors((prev) => ({ reader.onloadend = () => {
...prev, setFormData(prev => ({ ...prev, item_image: reader.result }));
item_image: "Max file size is 1MB.", setErrors(prev => ({ ...prev, item_image: null }));
})); };
return; reader.readAsDataURL(file);
} }} />
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 */}
{formData.item_image && ( {formData.item_image && (
<div className="position-relative" style={{ width: "50px", height: "50px" }}> <div style={{ position: "relative", width: "60px", height: "60px" }}>
<img <img src={formData.item_image} alt="preview" className="img-thumbnail" style={{ objectFit: "cover" }} />
src={formData.item_image} <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>
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> </div>
)} )}
</div> </div>
{errors.item_image && <div className="invalid-feedback d-block">{errors.item_image}</div>} {errors.item_image && <div className="invalid-feedback d-block">{errors.item_image}</div>}
</div> </div>
<div className="d-flex justify-content-end"> <div className="d-flex justify-content-end">
<button type="submit" className="btn btn-bg-theme"> <button type="submit" className="btn btn-bg-theme">{editMode ? "Update" : "Submit"}</button>
{editMode ? "Update" : "Submit"}
</button>
</div> </div>
</form> </form>
</div> </div>
@ -378,7 +329,7 @@ const SidesPageInner = () => {
</div> </div>
)} )}
{/* Delete Confirmation */} {/* Delete Modal */}
{deleteConfirm.show && ( {deleteConfirm.show && (
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}> <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-dialog modal-dialog-centered">
@ -388,10 +339,10 @@ const SidesPageInner = () => {
<h6 className="text-lg mb-0">Confirm Delete</h6> <h6 className="text-lg mb-0">Confirm Delete</h6>
<button type="button" className="btn-close" onClick={() => setDeleteConfirm({ show: false, id: null })}></button> <button type="button" className="btn-close" onClick={() => setDeleteConfirm({ show: false, id: null })}></button>
</div> </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"> <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-outline-danger" 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-danger" onClick={handleDelete}>Delete</button>
</div> </div>
</div> </div>
</div> </div>
@ -402,15 +353,13 @@ const SidesPageInner = () => {
); );
}; };
const SidesPage = () => { const SidesPage = () => (
return ( <MasterLayout>
<MasterLayout> <Breadcrumb title="Sides Management" />
<Breadcrumb title="Sides Management" /> <Suspense fallback={<PageLoader />}>
<Suspense fallback={<PageLoader />}> <SidesPageInner />
<SidesPageInner /> </Suspense>
</Suspense> </MasterLayout>
</MasterLayout> );
);
};
export default SidesPage; export default SidesPage;

View File

@ -392,7 +392,7 @@ const TableOrderInner = () => {
<div className="cursor-pointer" <div className="cursor-pointer"
onClick={() => handleTableClick(table)} onClick={() => handleTableClick(table)}
> >
<div style={{ position: 'relative', paddingTop: "20px" }}> <div style={{ position: 'relative', paddingTop: "20px", width: "120px" }}>
{ {
table?.totalcapacity <= 2 ? ( table?.totalcapacity <= 2 ? (
<img <img
@ -487,13 +487,13 @@ const TableOrderInner = () => {
} }
<h6 <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={{ style={{
pointerEvents: "none", pointerEvents: "none",
top: "55%", top: "55%",
left: "50%", left: "50%",
width: "60px", width: "50px",
height: "60px", height: "50px",
borderRadius: "50%", borderRadius: "50%",
}} }}

View File

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

View File

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