1226 lines
63 KiB
JavaScript
1226 lines
63 KiB
JavaScript
"use client"
|
|
import MasterLayout from "@/masterLayout/MasterLayout";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import client from "@auth";
|
|
import classNames from "classnames";
|
|
import Link from "next/link";
|
|
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 = () => {
|
|
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
|
|
const roomName = decodeURIComponent(searchParams.get("roomname") || "");
|
|
const FloorName = decodeURIComponent(searchParams.get("floor") || "");
|
|
|
|
const [tables, setTables] = useState([]);
|
|
const [showModalTable, setShowModalTable] = useState(false);
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [isEditMode, setIsEditMode] = useState(false);
|
|
const [selectedFloorId, setSelectedFloorId] = useState(null);
|
|
const [selectedTable, setSelectedTable] = useState(null);
|
|
const [seatCount, setSeatCount] = useState(1);
|
|
const [error, setError] = useState('');
|
|
const [loading, setLoading] = useState(true)
|
|
const [formData, setFormData] = useState({
|
|
tablename: "",
|
|
minimumoccupancy: "",
|
|
totalcapacity: "",
|
|
description: "",
|
|
enable: 0,
|
|
});
|
|
const [errors, setErrors] = useState({});
|
|
const [deleteConfirm, setDeleteConfirm] = useState({ show: false, id: null, isDeleted: null });
|
|
const [restaruntBranch, setRestaruntBranch] = useState("")
|
|
const [floorData, setFloorData] = useState(null);
|
|
const [roomData, setRoomData] = useState(null);
|
|
const [selectedFloor, setSelectedFloor] = useState("");
|
|
const [selectedRoom, setSelectedRoom] = useState("");
|
|
|
|
|
|
// Floor
|
|
const [showFloorModal, setShowFloorModal] = useState(false);
|
|
const [isFloorEditMode, setIsFloorEditMode] = useState(false);
|
|
// const [selectedFloorId, setSelectedFloorId] = useState(null);
|
|
const [floorFormData, setFloorFormData] = useState({
|
|
floorname: "",
|
|
description: "",
|
|
});
|
|
const [floorErrors, setFloorErrors] = useState({});
|
|
const [floorDeleteConfirm, setFloorDeleteConfirm] = useState({ show: false, id: null, isDeleted: null });
|
|
|
|
// Room
|
|
const [showRoomModal, setShowRoomModal] = useState(false);
|
|
const [editRoomMode, setEditRoomMode] = useState(false);
|
|
const [editingRoomId, setEditingRoomId] = useState(null);
|
|
const [roomFormData, setRoomFormData] = useState({
|
|
roomname: "",
|
|
description: "",
|
|
});
|
|
const [roomErrors, setRoomErrors] = useState({});
|
|
const [roomDeleteConfirm, setRoomDeleteConfirm] = useState({ show: false, id: null, isDeleted: null });
|
|
|
|
useEffect(() => {
|
|
const restarunt = localStorage.getItem("restaurantbranch")
|
|
setRestaruntBranch(restarunt)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const isLogin = JSON.parse(localStorage.getItem("isLogin"));
|
|
if (!isLogin) {
|
|
router.push(`/admin?restaurantbranch=${restaruntBranch}`);
|
|
}
|
|
}, [router]);
|
|
|
|
|
|
|
|
// Get floor data when branch is ready
|
|
useEffect(() => {
|
|
if (restaruntBranch) {
|
|
getFloor();
|
|
}
|
|
}, [restaruntBranch]);
|
|
|
|
const getFloor = async () => {
|
|
try {
|
|
const res = await client.get(
|
|
`/Dine360 Floor?fields=["*"]&limit_page_length=100&filters=[["restaurantbranch","=","${restaruntBranch}"]]`
|
|
);
|
|
const floors = res?.data?.data || [];
|
|
setFloorData(floors);
|
|
|
|
if (floors.length > 0) {
|
|
setSelectedFloor(floors[0].name);
|
|
setSelectedRoom(null);
|
|
getRoomData(floors[0].name);
|
|
} else {
|
|
setSelectedFloor(null);
|
|
setRoomData([]);
|
|
setTables([]);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching floor data:", error);
|
|
}
|
|
};
|
|
|
|
|
|
const getRoomData = async (floorName) => {
|
|
try {
|
|
const res = await client.get(
|
|
`/Dine360 Room?fields=["*"]&limit_page_length=100&filters=[["floor","=","${floorName}"]]`
|
|
);
|
|
const rooms = res?.data?.data || [];
|
|
setRoomData(rooms);
|
|
|
|
if (rooms.length > 0) {
|
|
setSelectedRoom(rooms[0].name);
|
|
getTableData(rooms[0].name);
|
|
} else {
|
|
setSelectedRoom(null); // clear selection if no rooms
|
|
setTables([]); // clear table data
|
|
}
|
|
} catch (error) {
|
|
console.error("Error fetching room data:", error);
|
|
}
|
|
};
|
|
|
|
|
|
const getTableData = async (roomName) => {
|
|
try {
|
|
setLoading(true)
|
|
const tableRes = await client.get(`/Dine360%20Table?fields=[\"*\"]&limit_page_length=100&filters=[["room","=","${roomName}"]]`);
|
|
console.log("tableRes", tableRes);
|
|
setTables(tableRes.data.data);
|
|
setLoading(false)
|
|
} catch (error) {
|
|
console.error("Error fetching data:", error);
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
|
|
// floor start
|
|
const handleFloorChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFloorFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const handleFloorSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
const newErrors = {};
|
|
Object.entries(floorFormData).forEach(([key, value]) => {
|
|
if (!value.trim()) newErrors[key] = `${key} is required`;
|
|
});
|
|
setFloorErrors(newErrors);
|
|
if (Object.keys(newErrors).length > 0) return;
|
|
|
|
const body = {
|
|
// ...(isFloorEditMode && { name: selectedFloorId }), // only adds `name` if editing
|
|
floorname: floorFormData?.floorname,
|
|
description: floorFormData?.description,
|
|
restaurantbranch: restaruntBranch
|
|
};
|
|
try {
|
|
if (isFloorEditMode) {
|
|
await client.put(`/Dine360 Floor/${selectedFloorId}`, body);
|
|
} else {
|
|
await client.post(`/Dine360 Floor`, body);
|
|
}
|
|
getFloor();
|
|
resetFloorForm();
|
|
} catch (error) {
|
|
if (
|
|
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
|
error?.response?.data?.message?.includes("Duplicate entry")
|
|
) {
|
|
alert("Floor with this name already exists. Please use a different name.");
|
|
} else if (
|
|
error?.response?.data?.exception?.includes("UniqueValidationError") ||
|
|
error?.response?.data?.message?.includes("UniqueValidationError entry")
|
|
) {
|
|
alert("Floor with this name already exists. Please use a different name.");
|
|
} else {
|
|
alert("An error occurred. Please try again.");
|
|
}
|
|
}
|
|
};
|
|
|
|
const resetFloorForm = () => {
|
|
setFloorFormData({
|
|
floorname: "", description: "",
|
|
});
|
|
setShowFloorModal(false);
|
|
setIsFloorEditMode(false);
|
|
setSelectedFloorId(null);
|
|
setFloorErrors({});
|
|
};
|
|
|
|
const handleFloorEdit = (floor) => {
|
|
setIsFloorEditMode(true);
|
|
setSelectedFloorId(floor.name);
|
|
setFloorFormData({
|
|
floorname: floor.floorname || "",
|
|
description: floor.description || "",
|
|
});
|
|
setShowFloorModal(true);
|
|
};
|
|
|
|
const handleFloorDelete = async () => {
|
|
try {
|
|
const body = {
|
|
isdeleted: floorDeleteConfirm?.isDeleted == 0 ? 1 : 0
|
|
}
|
|
await client.put(`/Dine360 Floor/${floorDeleteConfirm.id}`, body);
|
|
setFloorDeleteConfirm({ show: false, id: null, isDeleted: null });
|
|
getFloor();
|
|
} catch (error) {
|
|
if (
|
|
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
|
error?.response?.data?.message?.includes("Duplicate entry")
|
|
) {
|
|
alert("Floor with this name already exists. Please use a different name.");
|
|
} else if (error?.response?.data?.exception?.includes("LinkExistsError") ||
|
|
error?.response?.data?.message?.includes("LinkExistsError")) {
|
|
alert(" Cannot delete or cancel because Dine360 Floor three is linked with Dine360 Room ");
|
|
}
|
|
}
|
|
};
|
|
|
|
// floor end
|
|
|
|
|
|
// Room Start
|
|
const handleRoomChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setRoomFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const handleRoomSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
const newErrors = {};
|
|
Object.entries(roomFormData).forEach(([key, value]) => {
|
|
if (!value.trim()) newErrors[key] = `${key} is required`;
|
|
});
|
|
setRoomErrors(newErrors);
|
|
if (Object.keys(newErrors).length > 0) return;
|
|
|
|
const body = {
|
|
roomname: roomFormData.roomname,
|
|
description: roomFormData.description,
|
|
floor: selectedFloor,
|
|
restaurantbranch: restaruntBranch
|
|
};
|
|
|
|
try {
|
|
if (editRoomMode) {
|
|
await client.put(`/Dine360 Room/${editingRoomId}`, body);
|
|
} else {
|
|
await client.post(`/Dine360 Room`, body);
|
|
}
|
|
getRoomData(selectedFloor);
|
|
resetRoomForm();
|
|
} catch (error) {
|
|
console.error("❌ Submission error:", error);
|
|
}
|
|
};
|
|
|
|
const handleRoomEdit = (room) => {
|
|
setRoomFormData({
|
|
roomname: room.roomname || "",
|
|
description: room.description || "",
|
|
// branch: room.branch || "",
|
|
});
|
|
setEditingRoomId(room.name);
|
|
setEditRoomMode(true);
|
|
setShowRoomModal(true);
|
|
};
|
|
|
|
const handleRoomDelete = async () => {
|
|
try {
|
|
const body = {
|
|
isdeleted: roomDeleteConfirm?.isDeleted == 0 ? 1 : 0
|
|
}
|
|
await client.put(`/Dine360 Room/${roomDeleteConfirm?.id}`, body);
|
|
setRoomDeleteConfirm({ show: false, id: null, isDeleted: null });
|
|
getRoomData(selectedFloor);
|
|
} catch (error) {
|
|
if (
|
|
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
|
error?.response?.data?.message?.includes("Duplicate entry")
|
|
) {
|
|
alert("Floor with this name already exists. Please use a different name.");
|
|
} else if (error?.response?.data?.exception?.includes("LinkExistsError") ||
|
|
error?.response?.data?.message?.includes("LinkExistsError")) {
|
|
alert(" Cannot delete or cancel because Dine360 Floor three is linked with Dine360 Room fsrf65ahb5");
|
|
}
|
|
}
|
|
};
|
|
|
|
const resetRoomForm = () => {
|
|
setRoomFormData({ roomname: "", description: "", });
|
|
setRoomErrors({});
|
|
setEditRoomMode(false);
|
|
setEditingRoomId(null);
|
|
setShowRoomModal(false);
|
|
};
|
|
// Room End
|
|
|
|
console.log("selectedFloor", selectedFloor)
|
|
|
|
const handleTableClick = (table) => {
|
|
setSelectedTable(table);
|
|
setSeatCount(1);
|
|
setError('');
|
|
setShowModalTable(true);
|
|
};
|
|
|
|
const handleSeatChange = (e) => {
|
|
const value = parseInt(e.target.value);
|
|
if (value > selectedTable?.totalcapacity) {
|
|
setError(`Maximum capacity is ${selectedTable?.totalcapacity} seats`);
|
|
} else {
|
|
setError('');
|
|
}
|
|
setSeatCount(value);
|
|
};
|
|
|
|
const handleSeatSubmit = () => {
|
|
if (
|
|
selectedTable &&
|
|
seatCount > 0 &&
|
|
seatCount <= selectedTable?.totalcapacity
|
|
) {
|
|
const now = new Date();
|
|
|
|
const year = now.getFullYear();
|
|
const month = String(now.getMonth() + 1).padStart(2, '0'); // month is 0-based
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const hour = String(now.getHours()).padStart(2, '0');
|
|
const minute = String(now.getMinutes()).padStart(2, '0');
|
|
const second = String(now.getSeconds()).padStart(2, '0');
|
|
|
|
const formattedTime = `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
|
|
|
console.log(formattedTime);
|
|
|
|
// Push the route with formatted timestamp
|
|
router.push(
|
|
`/admin/pos/menu-items?restaurantbranch=${restaruntBranch}&table=${encodeURIComponent(selectedTable.name)}&seats=${seatCount}&time=${encodeURIComponent(formattedTime)}`
|
|
);
|
|
}
|
|
};
|
|
|
|
|
|
const handleChange = (e) => {
|
|
const { name, value } = e.target;
|
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
|
|
const newErrors = {};
|
|
Object.entries(formData).forEach(([key, value]) => {
|
|
if (!String(value).trim()) {
|
|
newErrors[key] = `${key} is required`;
|
|
}
|
|
});
|
|
setErrors(newErrors);
|
|
if (Object.keys(newErrors).length > 0) return;
|
|
|
|
|
|
const Body = {
|
|
tablename: formData?.tablename,
|
|
minimumoccupancy: formData?.minimumoccupancy,
|
|
totalcapacity: formData?.totalcapacity,
|
|
description: formData?.description,
|
|
room: selectedRoom,
|
|
floor: selectedFloor,
|
|
status: "Available",
|
|
restaurantbranch: restaruntBranch
|
|
}
|
|
|
|
try {
|
|
if (isEditMode) {
|
|
await client.put(`/Dine360 Table/${selectedFloorId}`, Body);
|
|
} else {
|
|
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 = {
|
|
name: 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(selectedRoom);
|
|
resetForm();
|
|
} catch (error) {
|
|
if (
|
|
error?.response?.data?.exception?.includes("DuplicateEntryError") ||
|
|
error?.response?.data?.message?.includes("Duplicate entry")
|
|
) {
|
|
alert("Floor with this name already exists. Please use a different name.");
|
|
} else {
|
|
alert("An error occurred. Please try again.");
|
|
}
|
|
}
|
|
};
|
|
|
|
const resetForm = () => {
|
|
setFormData({
|
|
tablename: "",
|
|
minimumoccupancy: "",
|
|
totalcapacity: "",
|
|
// branch: "",
|
|
description: "",
|
|
enable: 0,
|
|
});
|
|
setShowModal(false);
|
|
setIsEditMode(false);
|
|
setSelectedFloorId(null);
|
|
setErrors({});
|
|
};
|
|
|
|
const handleEdit = (table) => {
|
|
setIsEditMode(true);
|
|
setSelectedFloorId(table.name);
|
|
setFormData({
|
|
tablename: table.tablename || "",
|
|
minimumoccupancy: table.minimumoccupancy || "",
|
|
totalcapacity: table.totalcapacity || "",
|
|
description: table.description || "",
|
|
// branch: room.branch || "",
|
|
});
|
|
setShowModal(true);
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
const body = {
|
|
isdeleted: deleteConfirm?.isDeleted == 0 ? 1 : 0
|
|
}
|
|
await client.put(`/Dine360 Table/${deleteConfirm.id}`, body);
|
|
setDeleteConfirm({ show: false, id: null, isDeleted: null });
|
|
getTableData(selectedRoom);
|
|
} catch (error) {
|
|
console.error("Delete error:", error);
|
|
}
|
|
};
|
|
|
|
|
|
return (
|
|
<>
|
|
|
|
<div className="container " style={{ marginBottom: "100px" }}>
|
|
<div className="row mb-3" style={{ position: "sticky", top: "150px", zIndex: "1" }}>
|
|
{/* Floor & Room Wrapper Row */}
|
|
<div className="row mb-3">
|
|
{/* Floor Section */}
|
|
<div className="col-12 col-md-6 mb-3">
|
|
<div className="d-flex flex-wrap gap-2 align-items-start justify-content-center">
|
|
{/* Add Floor Card */}
|
|
<div
|
|
className="card shadow-sm bg-light border-0 text-center cursor-pointer d-flex align-items-center justify-content-center"
|
|
style={{ width: "60px", height: "40px", borderRadius: "12px" }}
|
|
onClick={() => setShowFloorModal(true)}
|
|
>
|
|
<Icon icon="lucide:plus" className="text-lg" />
|
|
</div>
|
|
|
|
{/* Floor Cards */}
|
|
{floorData?.map((floor) => (
|
|
<div
|
|
key={floor.name}
|
|
className={`card shadow-sm position-relative cursor-pointer border-2 px-2 py-1 ${selectedFloor === floor.name ? "bg-theme text-white" : "border-light"}`}
|
|
style={{ width: "auto", minWidth: "120px", maxWidth: "200px", borderRadius: "12px", height: "40px" }}
|
|
onClick={() => {
|
|
setSelectedFloor(floor.name);
|
|
getRoomData(floor.name);
|
|
}}
|
|
>
|
|
<div className="card-body px-3 py-1 d-flex gap-2 align-items-center justify-content-center" >
|
|
{
|
|
floor?.isdeleted == 1 && (<Icon icon="mdi:block-helper" style={{ color: "red" }} />)
|
|
}
|
|
|
|
<span
|
|
className="text-truncate text-sm fw-semibold w-100"
|
|
title={floor.floorname}
|
|
>
|
|
{floor.floorname}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Dropdown */}
|
|
<div className="position-absolute" style={{ top: "4px", right: "4px", zIndex: 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"
|
|
>
|
|
<Icon icon="entypo:dots-three-vertical" />
|
|
</button>
|
|
<ul className="dropdown-menu">
|
|
<li>
|
|
<Link className="dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900" href="#" onClick={(e) => {
|
|
e.preventDefault();
|
|
handleFloorEdit(floor);
|
|
}}>
|
|
<Icon icon="lucide:edit" /> Edit
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link className="dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900" href="#" onClick={(e) => {
|
|
e.preventDefault();
|
|
setFloorDeleteConfirm({ show: true, id: floor.name, isDeleted: floor.isdeleted });
|
|
}}>
|
|
{floor.isdeleted === 1 ? (
|
|
<>
|
|
<Icon icon="mdi:restore" /> Restore
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon icon="fluent:delete-24-regular" /> Delete
|
|
</>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Room Section */}
|
|
<div className="col-12 col-md-6">
|
|
<div className="d-flex flex-wrap gap-2 align-items-start justify-content-center">
|
|
{/* Add Room Card */}
|
|
<div
|
|
className="card shadow-sm bg-light border-0 text-center cursor-pointer d-flex align-items-center justify-content-center"
|
|
style={{ width: "60px", height: "40px", borderRadius: "12px" }}
|
|
onClick={() => setShowRoomModal(true)}
|
|
>
|
|
<div className="text-theme">
|
|
<Icon icon="lucide:plus" className="text-lg mb-1" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Room Cards */}
|
|
{roomData?.map((room) => (
|
|
<div
|
|
key={room.name}
|
|
className={`card shadow-sm position-relative cursor-pointer border-2 ${selectedRoom === room.name && "bg-theme text-white"}`}
|
|
style={{ width: "auto", minWidth: "120px", maxWidth: "200px", height: "40px", borderRadius: "12px" }}
|
|
onClick={() => {
|
|
setSelectedRoom(room.name);
|
|
getTableData(room.name);
|
|
}}
|
|
>
|
|
<div className="card-body px-3 py-1 d-flex gap-2 align-items-center">
|
|
{
|
|
room?.isdeleted == 1 && (<Icon icon="mdi:block-helper" style={{ color: "red" }} />)
|
|
}
|
|
<span
|
|
className="text-truncatew-100 text-sm fw-semibold"
|
|
title={room.roomname}
|
|
>
|
|
{room.roomname}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Dropdown */}
|
|
<div className="position-absolute" style={{ top: "3px", right: "3px", zIndex: 1 }}>
|
|
<div className="dropdown">
|
|
<button
|
|
className="btn px-1 py-1 d-flex align-items-center text-primary-light"
|
|
type="button"
|
|
data-bs-toggle="dropdown"
|
|
>
|
|
<Icon icon="entypo:dots-three-vertical" />
|
|
</button>
|
|
<ul className="dropdown-menu">
|
|
<li>
|
|
<Link className="dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900" href="#" onClick={(e) => {
|
|
e.preventDefault();
|
|
handleRoomEdit(room);
|
|
}}>
|
|
<Icon icon="lucide:edit" /> Edit
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<Link className="dropdown-item px-16 py-8 d-flex align-items-center gap-2 rounded text-secondary-light bg-hover-neutral-200 text-hover-neutral-900" href="#" onClick={(e) => {
|
|
e.preventDefault();
|
|
setRoomDeleteConfirm({ show: true, id: room.name, isDeleted: room.isdeleted });
|
|
}}>
|
|
{room.isdeleted === 1 ? (
|
|
<>
|
|
<Icon icon="mdi:restore" /> Restore
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon icon="fluent:delete-24-regular" /> Delete
|
|
</>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className='row gy-3 gx-3 '>
|
|
{/* <div className="d-flex justify-content-end align-items-center ">
|
|
<h6 className="mb-0">Table</h6>
|
|
<button
|
|
type="button"
|
|
className="btn btn-bg-theme radius-8 px-20 py-11"
|
|
onClick={() => setShowModal(true)}
|
|
>
|
|
Create
|
|
</button>
|
|
</div> */}
|
|
|
|
{loading ? (
|
|
<PageLoader />
|
|
) : tables.length === 0 ? (
|
|
<div className="col-xxl-2 col-lg-3 col-md-4 col-6 mb-4 d-flex justify-content-center align-items-center">
|
|
<div
|
|
className="card cursor-pointer shadow-sm bg-light border-0 d-flex align-items-center justify-content-center"
|
|
style={{ width: "150px", height: "150px", borderRadius: "12px" }}
|
|
onClick={() => setShowModal(true)}
|
|
>
|
|
<div className="text-center">
|
|
<div
|
|
className="d-flex align-items-center justify-content-center rounded-circle bg-theme bg-opacity-10 text-white mx-auto mb-2"
|
|
style={{ width: "48px", height: "48px" }}
|
|
>
|
|
<Icon icon="lucide:plus" className="text-xl" />
|
|
</div>
|
|
<small className="text-black">Add New</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
) : (
|
|
<>
|
|
<div className="col-xxl-2 col-lg-3 col-md-4 col-6 mb-4 d-flex justify-content-center align-items-center">
|
|
<div
|
|
className="card cursor-pointer shadow-sm bg-light border-0 d-flex align-items-center justify-content-center"
|
|
style={{ width: "150px", height: "150px", borderRadius: "12px" }}
|
|
onClick={() => setShowModal(true)}
|
|
>
|
|
<div className="text-center">
|
|
<div
|
|
className="d-flex align-items-center justify-content-center rounded-circle bg-theme bg-opacity-10 text-white mx-auto mb-2"
|
|
style={{ width: "48px", height: "48px" }}
|
|
>
|
|
<Icon icon="lucide:plus" className="text-xl" />
|
|
</div>
|
|
<small className="text-black">Add New</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{tables.map((table) => (
|
|
<div className="col-xxl-2 col-lg-3 col-md-4 col-6 mb-4 text-center" key={table.name}>
|
|
<div className="d-inline-block">
|
|
<div
|
|
className={` `} >
|
|
<div className=" p-0 ">
|
|
{/* Top-right action buttons */}
|
|
|
|
<div
|
|
// onClick={() => handleTableClick(table)}
|
|
>
|
|
<div style={{ position: 'relative', paddingTop: "20px", width: "150px" }}>
|
|
|
|
<img
|
|
src={`/assets/images/seat/${(() => {
|
|
if (table?.totalcapacity <= 2) return 'seating-24.png';
|
|
if (table?.totalcapacity <= 4) return 'seating-25.png';
|
|
if (table?.totalcapacity <= 6) return 'seating-26.png';
|
|
if (table?.totalcapacity <= 8) return 'seating-27.png';
|
|
if (table?.totalcapacity <= 10) return 'seating-28.png';
|
|
return 'default.png'; // fallback image
|
|
})()}`}
|
|
alt="table"
|
|
className={classNames(
|
|
'w-100 h-100 object-fit-cover radius-8',
|
|
{ 'grayscale': table.status === 'booked' }
|
|
)}
|
|
/>
|
|
{
|
|
table?.isdeleted == 1 && (<Icon icon="mdi:block-helper" style={{
|
|
color: "red", position: "absolute", top: "28%",
|
|
left: "15%", zIndex: 1
|
|
}} />)
|
|
}
|
|
|
|
<h6
|
|
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-500" : "bg-secondary-500"}`}
|
|
style={{
|
|
pointerEvents: "none",
|
|
top: "55%",
|
|
left: "50%",
|
|
width: "50px",
|
|
height: "50px",
|
|
borderRadius: "50%",
|
|
|
|
}}
|
|
>
|
|
{table?.tablename}
|
|
</h6>
|
|
<div className="position-absolute " style={{ zIndex: 1, top: "25%", right: "10%" }}>
|
|
|
|
<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 text-dark' />
|
|
</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(table)
|
|
}}
|
|
>
|
|
<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: table.name, isDeleted: table.isdeleted })
|
|
}}
|
|
>
|
|
{table.isdeleted === 1 ? (
|
|
<>
|
|
<Icon icon="mdi:restore" /> Restore
|
|
</>
|
|
) : (
|
|
<>
|
|
<Icon icon="fluent:delete-24-regular" /> Delete
|
|
</>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
</>
|
|
)
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
{/* floor create/update start */}
|
|
{showFloorModal && (
|
|
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
<div className="modal-header border-0 pb-0">
|
|
<h6 className="modal-title text-lg">
|
|
{isFloorEditMode ? "Edit Floor" : "Create Floor"}
|
|
</h6>
|
|
<button type="button" className="btn-close" onClick={resetFloorForm}></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<form onSubmit={handleFloorSubmit}>
|
|
<div className="mb-3">
|
|
<label className="form-label">Floor Name</label>
|
|
<input
|
|
type="text"
|
|
className={`form-control ${floorErrors.floorname ? "is-invalid" : ""}`}
|
|
name="floorname"
|
|
value={floorFormData.floorname}
|
|
onChange={handleFloorChange}
|
|
/>
|
|
{floorErrors.floorname && (
|
|
<div className="invalid-feedback">{floorErrors.floorname}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label">Description</label>
|
|
<textarea
|
|
className={`form-control ${floorErrors.description ? "is-invalid" : ""}`}
|
|
name="description"
|
|
value={floorFormData.description}
|
|
onChange={handleFloorChange}
|
|
rows="3"
|
|
></textarea>
|
|
{floorErrors.description && (
|
|
<div className="invalid-feedback">{floorErrors.description}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="d-flex justify-content-end">
|
|
<button type="submit" className="btn btn-bg-theme">
|
|
{isFloorEditMode ? "Update" : "Submit"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* Floor create/update end */}
|
|
{/* Floor Delete Start */}
|
|
{floorDeleteConfirm.show && (
|
|
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
|
|
<div className="modal-body">
|
|
<div className="d-flex justify-content-between mb-1">
|
|
<h6 className="text-lg mb-0">
|
|
{floorDeleteConfirm?.isDeleted === 0 ? "Confirm to Delete" : "Confirm to Restore"}
|
|
</h6>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={() => setFloorDeleteConfirm({ show: false, id: null, isDeleted: null })}
|
|
></button>
|
|
</div>
|
|
<p className="m-0">
|
|
{floorDeleteConfirm?.isDeleted === 0
|
|
? "Are you sure you want to set this floor as Delete?"
|
|
: "Are you sure you want to set this floor as Restore?"}
|
|
</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={() => setFloorDeleteConfirm({ show: false, id: null, isDeleted: null })}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleFloorDelete}>
|
|
{floorDeleteConfirm?.isDeleted === 0 ? "Delete" : "Restore"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* Floor Delete End */}
|
|
|
|
|
|
|
|
{/* Room start */}
|
|
|
|
{/* Modal */}
|
|
{showRoomModal && (
|
|
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
<div className="modal-header border-0 pb-0">
|
|
<h6 className="modal-title text-lg">{editRoomMode ? "Edit Room" : "Create Room"}</h6>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={resetRoomForm}
|
|
></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<form onSubmit={handleRoomSubmit}>
|
|
<div className="mb-3">
|
|
<label className="form-label">Room Name</label>
|
|
<input
|
|
type="text"
|
|
className={`form-control ${roomErrors.roomname ? "is-invalid" : ""}`}
|
|
name="roomname"
|
|
value={roomFormData.roomname}
|
|
onChange={handleRoomChange}
|
|
/>
|
|
{roomErrors.roomname && (
|
|
<div className="invalid-feedback">{roomErrors.roomname}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label">Description</label>
|
|
<textarea
|
|
className={`form-control ${roomErrors.description ? "is-invalid" : ""}`}
|
|
name="description"
|
|
value={roomFormData.description}
|
|
onChange={handleRoomChange}
|
|
rows="3"
|
|
></textarea>
|
|
{roomErrors.description && (
|
|
<div className="invalid-feedback">{roomErrors.description}</div>
|
|
)}
|
|
</div>
|
|
|
|
|
|
|
|
<div className="d-flex justify-content-end">
|
|
<button type="submit" className="btn btn-bg-theme">
|
|
{editRoomMode ? "Update" : "Submit"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Delete Confirmation Modal */}
|
|
{roomDeleteConfirm.show && (
|
|
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
|
|
<div className="modal-body">
|
|
<div className="d-flex justify-content-between mb-1">
|
|
<h6 className="text-lg mb-0">
|
|
{roomDeleteConfirm?.isDeleted === 0 ? "Confirm to Delete" : "Confirm to Restore"}
|
|
</h6>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={() => setRoomDeleteConfirm({ show: false, id: null, isDeleted: null })}
|
|
></button>
|
|
</div>
|
|
<p className="mb-0">
|
|
{roomDeleteConfirm?.isDeleted === 0
|
|
? "Are you sure you want to set this room as Delete?"
|
|
: "Are you sure you want to set this room as Restore?"}</p>
|
|
<div className="d-flex justify-content-end gap-2 mt-1">
|
|
<button
|
|
className="btn btn-outline-danger-600 px-14 py-6 text-sm"
|
|
onClick={() => setRoomDeleteConfirm({ show: false, id: null, isDeleted: null })}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleRoomDelete}>
|
|
{roomDeleteConfirm?.isDeleted === 0 ? "Delete" : "Restore"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{/* room end */}
|
|
|
|
{/* Seat Count Modal */}
|
|
{showModalTable && (
|
|
<div className="modal fade show" style={{ display: 'block', backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
<div className="modal-header border-0 pb-0">
|
|
<h6 className="modal-title text-lg">Enter Seat Count</h6>
|
|
<button type="button" className="btn-close" onClick={() => setShowModalTable(false)}></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<div className="mb-3">
|
|
<label htmlFor="seatCount" className="form-label">Number of Seats</label>
|
|
<input
|
|
type="number"
|
|
className={`form-control ${error ? 'is-invalid' : ''}`}
|
|
id="seatCount"
|
|
max={selectedTable?.totalcapacity}
|
|
value={seatCount || ''} // 👈 fallback if seatCount is undefined or NaN
|
|
onChange={handleSeatChange}
|
|
/>
|
|
|
|
{error ? (
|
|
<div className="invalid-feedback">
|
|
{error}
|
|
</div>
|
|
) : (
|
|
<small >
|
|
Maximum capacity: {selectedTable?.totalcapacity}
|
|
</small>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="modal-footer border-0 pt-0">
|
|
<button type="button" className="btn btn-sm btn-outline-danger" onClick={() => setShowModalTable(false)}>Cancel</button>
|
|
<button type="button" className="btn btn-sm btn-danger-500" onClick={handleSeatSubmit}>Proceed to Menu</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Modal */}
|
|
{showModal && (
|
|
<div className="modal fade show" style={{ display: "block", backgroundColor: "rgba(0,0,0,0.5)" }}>
|
|
<div className="modal-dialog modal-dialog-centered">
|
|
<div className="modal-content">
|
|
<div className="modal-header border-0 pb-0">
|
|
<h6 className="modal-title text-lg">{isEditMode ? "Edit Table" : "Create Table"}</h6>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={resetForm}
|
|
></button>
|
|
</div>
|
|
<div className="modal-body">
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-3">
|
|
<label className="form-label">Table Name</label>
|
|
<input
|
|
type="text"
|
|
className={`form-control ${errors.tablename ? "is-invalid" : ""}`}
|
|
name="tablename"
|
|
value={formData.tablename || ""}
|
|
onChange={handleChange}
|
|
/>
|
|
{errors.tablename && (
|
|
<div className="invalid-feedback">{errors.tablename}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label">Minimum Occupancy</label>
|
|
<input
|
|
type="number"
|
|
className={`form-control ${errors.minimumoccupancy ? "is-invalid" : ""}`}
|
|
name="minimumoccupancy"
|
|
value={formData.minimumoccupancy || ""}
|
|
onChange={handleChange}
|
|
/>
|
|
{errors.minimumoccupancy && (
|
|
<div className="invalid-feedback">{errors.minimumoccupancy}</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="mb-3">
|
|
<label className="form-label">Total Capacity</label>
|
|
<input
|
|
type="number"
|
|
className={`form-control ${errors.totalcapacity ? "is-invalid" : ""}`}
|
|
name="totalcapacity"
|
|
value={formData.totalcapacity || ""}
|
|
onChange={handleChange}
|
|
/>
|
|
{errors.totalcapacity && (
|
|
<div className="invalid-feedback">{errors.totalcapacity}</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>
|
|
{errors.description && (
|
|
<div className="invalid-feedback">{errors.description}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* <div className="mb-3">
|
|
<label className="form-label">Branch</label>
|
|
<select
|
|
className={`form-select ${errors.branch ? "is-invalid" : ""}`}
|
|
name="branch"
|
|
value={formData.branch}
|
|
onChange={handleChange}
|
|
>
|
|
<option value="">Select Branch</option>
|
|
{branchData.map((branch) => (
|
|
<option key={branch.name} value={branch.name}>
|
|
{branch.branch}
|
|
</option>
|
|
))}
|
|
</select>
|
|
{errors.branch && (
|
|
<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">
|
|
{isEditMode ? "Update" : "Submit"}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Delete Confirmation 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">
|
|
<div className="modal-content">
|
|
|
|
<div className="modal-body">
|
|
<div className="d-flex justify-content-between mb-1">
|
|
<h6 className="text-lg mb-0">
|
|
{deleteConfirm?.isDeleted === 0 ? "Confirm to Delete" : "Confirm to Restore"}
|
|
</h6>
|
|
<button
|
|
type="button"
|
|
className="btn-close"
|
|
onClick={() => setDeleteConfirm({ show: false, id: null, isDeleted: null })}
|
|
></button>
|
|
</div>
|
|
<p className="m-0">
|
|
{deleteConfirm?.isDeleted === 0
|
|
? "Are you sure you want to set this table as Delete?"
|
|
: "Are you sure you want to set this table as Restore?"}
|
|
</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, isDeleted: null })}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button className="btn btn-danger px-14 py-6 text-sm" onClick={handleDelete}>
|
|
{deleteConfirm?.isDeleted === 0 ? "Delete" : "Restore"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
|
|
const DineIn = (() => {
|
|
return (
|
|
<MasterLayout>
|
|
{/* Breadcrumb */}
|
|
<Breadcrumb title='Table' />
|
|
<Suspense fallback={<PageLoader />}>
|
|
<DineInInner />
|
|
</Suspense>
|
|
</MasterLayout>
|
|
)
|
|
})
|
|
export default DineIn;
|