({
+ value: room.name,
+ name: room.name,
+ label: `Room ${room.room_number}${room.guest_name ? ` - ${room.guest_name}` : ""}`,
+ room_number: room.room_number,
+ customer: room.customer,
+ customer_name: room.customer_name,
+ }))}
+ value={selectedRoom?.name || ""}
+ onValueChange={(value) => {
+ if (value) {
+ const room = rooms.find((r) => r.name === value);
+ if (room) {
+ setSelectedRoom(room);
+ if (room.customer) {
+ setCustomer(room.customer);
+ }
+ }
+ } else {
+ setSelectedRoom(null);
+ }
+ }}
+ placeholder={loadingRooms ? "Loading..." : "Select room"}
+ searchPlaceholder="Search rooms..."
+ disabled={loadingRooms}
+ className="w-[200px]"
+ onOpenChange={(open) => {
+ if (open) {
+ fetchRooms();
+ }
+ if (!open && target === "menu") {
+ requestAnimationFrame(() => {
+ searchInputRef.current?.focus();
+ });
+ }
+ }}
+ />
+ )}
{
className="w-[200px] focus:outline-none focus:ring-0 focus:border-transparent"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && filteredItems.length > 0) {
+ e.preventDefault();
+ e.stopPropagation();
+ const index = Math.min(currentIndex, filteredItems.length - 1);
+ if (index < 0) return;
+ const item = filteredItems[index];
+ addToCart({
+ name: item.name,
+ item_name: item.item_name,
+ custom_menu_category: item.custom_menu_category,
+ quantity: 1,
+ price: item.standard_rate ?? item.price ?? 0,
+ standard_rate: item.standard_rate ?? item.price ?? 0,
+ remark: "",
+ });
+ }
+ }}
onBlur={(e) => {
// if (target !== "menu") return;
@@ -343,7 +428,7 @@ useEffect(() => {
open={shiftDialogOpen}
type={shiftType}
onOpenChange={setShiftDialogOpen}
- onShiftAction={(action, msg) => console.log(action, msg)}
+ // onShiftAction={(action, msg) => console.log(action, msg)}
/>
>
);
diff --git a/dashboard/src/components/MenuPage/MenuItemCard.jsx b/dashboard/src/components/MenuPage/MenuItemCard.jsx
index 8023a93..0cda1d4 100644
--- a/dashboard/src/components/MenuPage/MenuItemCard.jsx
+++ b/dashboard/src/components/MenuPage/MenuItemCard.jsx
@@ -1,7 +1,7 @@
import { useState, useMemo } from "react";
import { cn } from "@/lib/utils";
import { useCartStore } from "@/stores/useCartStore";
-import { checkStock, negativeStock, getItemUoms } from "@/lib/utils";
+import { checkStock, getItemUoms, negativeStock } from "@/lib/utils";
import { toast } from "sonner";
import { useMenuContext } from "@/contexts/MenuContext";
@@ -12,8 +12,9 @@ const MenuItemCard = ({ item, index }) => {
const [showUomModal, setShowUomModal] = useState(false);
const [pendingItem, setPendingItem] = useState(null);
const [dynamicUoms, setDynamicUoms] = useState([]);
+ const [addingItemName, setAddingItemName] = useState(null);
- const { currentIndex, setCurrentIndex, target, setTarget } = useMenuContext();
+ const { currentIndex, setCurrentIndex, target, setTarget, allowNegativeStock } = useMenuContext();
const addToCart = useCartStore((state) => state.addToCart);
const cartItems = useCartStore((state) => state.cart || []);
@@ -34,53 +35,83 @@ const MenuItemCard = ({ item, index }) => {
const isActive = currentIndex === index && target === "menu";
const handleAddToCart = async () => {
- const stockData = await checkStock(item.name);
- const allowNegative = await negativeStock();
-
- if (!allowNegative && stockData?.stock <= 0) {
- toast.error("Error", { description: `No stock available for ${item.item_name}` });
- return;
- }
-
- const existingCartItem = cartItems.find((ci) => ci.name === item.name);
-
- if (existingCartItem) {
- // Already in cart → increment quantity
- addToCart({
- ...existingCartItem,
- quantity: existingCartItem.quantity + 1,
+ // Prevent double-add: only one add-in-progress per item at a time
+ if (addingItemName === item.name) return;
+ setAddingItemName(item.name);
+
+ try {
+ // can_use_negative_stock is loaded once on menu open (MenuContext). Only check stock when negative not allowed.
+ if (allowNegativeStock === false) {
+ const stockData = await checkStock(item.name);
+ if (stockData?.stock <= 0) {
+ toast.error("Error", { description: `No stock available for ${item.item_name}` });
+ return;
+ }
+ } else if (allowNegativeStock === null) {
+ // Not yet loaded: one-time fallback so we don't add when stock is 0
+ const [stockData, allowNegative] = await Promise.all([
+ checkStock(item.name),
+ negativeStock(),
+ ]);
+ if (!allowNegative && stockData?.stock <= 0) {
+ toast.error("Error", { description: `No stock available for ${item.item_name}` });
+ return;
+ }
+ }
+ // allowNegativeStock === true: skip stock check entirely
+
+ // Use fresh cart from store in case another add completed while we awaited
+ const currentCart = useCartStore.getState().cart || [];
+ const existingCartItem = currentCart.find((ci) => ci.name === item.name);
+
+ if (existingCartItem) {
+ addToCart({
+ ...existingCartItem,
+ quantity: existingCartItem.quantity + 1,
+ });
+ return;
+ }
+
+ // Not in cart → fetch UOMs (single call)
+ const fetchedUoms = await getItemUoms(item.name);
+
+ if (!fetchedUoms || fetchedUoms.length === 0) {
+ toast.error(`No UOMs found for ${item.item_name}`);
+ return;
+ }
+
+ // Re-check cart after fetch; user might have added same item via another click
+ const cartAfterFetch = useCartStore.getState().cart || [];
+ const existingNow = cartAfterFetch.find((ci) => ci.name === item.name);
+ if (existingNow) {
+ addToCart({ ...existingNow, quantity: existingNow.quantity + 1 });
+ return;
+ }
+
+ if (fetchedUoms.length === 1) {
+ addToCart({
+ name: item.name,
+ item_name: item.item_name,
+ custom_menu_category: item.custom_menu_category,
+ quantity: 1,
+ uom: fetchedUoms[0],
+ price: item.standard_rate ?? item.price ?? 0,
+ standard_rate: item.standard_rate ?? item.price ?? 0,
+ remark: "No stock override",
+ });
+ return;
+ }
+
+ setPendingItem(item);
+ setDynamicUoms(fetchedUoms);
+ setShowUomModal(true);
+ } catch (err) {
+ toast.error("Could not add item", {
+ description: err?.message || "Please try again.",
});
- return;
- }
-
- // Not in cart → fetch UOMs dynamically
- const fetchedUoms = await getItemUoms(item.name);
- console.log("fetched uoms", fetchedUoms);
-
- if (!fetchedUoms || fetchedUoms.length === 0) {
- toast.error(`No UOMs found for ${item.item_name}`);
- return;
+ } finally {
+ setAddingItemName(null);
}
-
- if (fetchedUoms.length === 1) {
- // Only one UOM → add directly
- addToCart({
- name: item.name,
- item_name: item.item_name,
- custom_menu_category: item.custom_menu_category,
- quantity: 1,
- uom: fetchedUoms[0],
- price: item.standard_rate ?? item.price ?? 0,
- standard_rate: item.standard_rate ?? item.price ?? 0,
- remark: "No stock override",
- });
- return;
- }
-
- // Multiple UOMs → show modal
- setPendingItem(item);
- setDynamicUoms(fetchedUoms);
- setShowUomModal(true);
};
@@ -118,7 +149,8 @@ const handleAddToCart = async () => {
}}
className={cn(
"menu-item cursor-pointer rounded-lg border shadow-sm transition transform hover:shadow-md hover:scale-[1.02] active:scale-[0.98] active:bg-gray-50 py-2 gap-2",
- isActive && "border-primary bg-primary/10"
+ isActive && "border-primary bg-primary/10",
+ addingItemName === item.name && "pointer-events-none opacity-70"
)}
>
diff --git a/dashboard/src/components/MenuPage/MultiCurrencyDialog.jsx b/dashboard/src/components/MenuPage/MultiCurrencyDialog.jsx
index 4c41dbf..8c15a1b 100644
--- a/dashboard/src/components/MenuPage/MultiCurrencyDialog.jsx
+++ b/dashboard/src/components/MenuPage/MultiCurrencyDialog.jsx
@@ -49,7 +49,7 @@ export default function MultiCurrencyDialog({
const itemsToUse = useMemo(() => {
const items = cartItems && cartItems.length > 0 ? cartItems : cartStoreItems;
if (!items || items.length === 0) {
- console.warn("MultiCurrencyDialog: No cart items found", { cartItems, cartStoreItems });
+ // console.warn("MultiCurrencyDialog: No cart items found", { cartItems, cartStoreItems });
return [];
}
// Ensure items are in the correct format for the API
@@ -62,7 +62,7 @@ export default function MultiCurrencyDialog({
price: item.price || item.rate || item.standard_rate || 0,
rate: item.rate || item.price || item.standard_rate || 0,
}));
- console.log("MultiCurrencyDialog: Formatted cart items", formattedItems);
+ // console.log("MultiCurrencyDialog: Formatted cart items", formattedItems);
return formattedItems;
}, [cartItems, cartStoreItems]);
@@ -471,7 +471,7 @@ export default function MultiCurrencyDialog({
cartItems: itemsToUse,
orderPayload: orderPayload,
});
- console.log("still muilti",paymentData);
+ // console.log("still muilti",paymentData);
} catch (err) {
// Log error but don't block user (payment already shown as successful)
console.error("Payment processing error (background):", err);
diff --git a/dashboard/src/components/MenuPage/PaymentDialog.jsx b/dashboard/src/components/MenuPage/PaymentDialog.jsx
index c84ea3b..ab9841c 100644
--- a/dashboard/src/components/MenuPage/PaymentDialog.jsx
+++ b/dashboard/src/components/MenuPage/PaymentDialog.jsx
@@ -50,7 +50,7 @@ export default function PaymentDialog({
// Get HA POS Settings document (Single doctype)
const settingsResponse = await call.get("havano_restaurant_pos.api.get_ha_pos_settings");
const doc = settingsResponse?.message?.data;
- console.log("HA POS Settings document:", doc);
+ // console.log("HA POS Settings document:", doc);
let methods = [];
@@ -134,7 +134,7 @@ export default function PaymentDialog({
const paymentStatus = useMemo(() => {
const paid = sumPayments();
const diff = paid - total;
- console.log(diff);
+ // console.log(diff);
return {
paid,
diff,
@@ -191,7 +191,7 @@ export default function PaymentDialog({
// Only fetch invoice JSON if needed (optimized: conditional)
try {
// const invoiceJson = await get_invoice_json(transactionName);
- console.log("Invoice JSON returned from backend MULTIPLE");
+ // console.log("Invoice JSON returned from backend MULTIPLE");
window.open(`/api/method/havano_restaurant_pos.api.download_invoice_json?name=${transactionName}`, "_blank");
// Convert JSON to string
@@ -258,7 +258,7 @@ export default function PaymentDialog({
);
if (res && res.success) {
- console.log("Table payment processed:", res.sales_invoice);
+ // console.log("Table payment processed:", res.sales_invoice);
// Print invoice if available
if (res.sales_invoice) {
window.open(`/api/method/havano_restaurant_pos.api.download_invoice_json?name=${res.sales_invoice}`, "_blank");
@@ -291,7 +291,7 @@ export default function PaymentDialog({
- console.log("direct-----------------DD--------",cartItems)
+ // console.log("direct-----------------DD--------",cartItems)
// For Dine In orders, no invoice is created, so skip invoice download
if (res.dine_in_only) {
@@ -299,7 +299,7 @@ export default function PaymentDialog({
// No invoice to download for Dine In orders
} else if (res.sales_invoice) {
try {
- console.log("Payment successful bro:", res.sales_invoice);
+ // console.log("Payment successful bro:", res.sales_invoice);
window.open(
`/api/method/havano_restaurant_pos.api.download_invoice_json?name=${res.sales_invoice}&receipt_type=${selectedReceipt}`,
"_blank")
diff --git a/dashboard/src/components/ui/MultiCurrencyDialog.jsx b/dashboard/src/components/ui/MultiCurrencyDialog.jsx
index 40f5b40..7a60dfe 100644
--- a/dashboard/src/components/ui/MultiCurrencyDialog.jsx
+++ b/dashboard/src/components/ui/MultiCurrencyDialog.jsx
@@ -52,7 +52,6 @@ export default function MultiCurrencyDialog({
const itemsToUse = useMemo(() => {
const items = cartItems && cartItems.length > 0 ? cartItems : cartStoreItems;
if (!items || items.length === 0) {
- console.warn("MultiCurrencyDialog: No cart items found", { cartItems, cartStoreItems });
return [];
}
// Ensure items are in the correct format for the API
@@ -65,7 +64,7 @@ export default function MultiCurrencyDialog({
price: item.price || item.rate || item.standard_rate || 0,
rate: item.rate || item.price || item.standard_rate || 0,
}));
- console.log("MultiCurrencyDialog: Formatted cart items", formattedItems);
+ // console.log("MultiCurrencyDialog: Formatted cart items", formattedItems);
return formattedItems;
}, [cartItems, cartStoreItems]);
@@ -88,9 +87,9 @@ export default function MultiCurrencyDialog({
try {
const shiftPayments = await fetchUserShiftPayments();
setExpectedPayments(shiftPayments); // ✅ THIS WAS THE BUG
- console.log("Fetched user shift payments:", shiftPayments);
+ // console.log("Fetched user shift payments:", shiftPayments);
} catch (err) {
- console.error("Error fetching user shift payments:", err);
+ // console.error("Error fetching user shift payments:", err);
setExpectedPayments({});
}
};
@@ -195,7 +194,7 @@ export default function MultiCurrencyDialog({
setActiveCurrency(firstMethod.key);
}
} catch (error) {
- console.error("Error fetching payment method rates:", error);
+ // console.error("Error fetching payment method rates:", error);
// Fall back to default exchange rates
if (defaultExchangeRates && Object.keys(defaultExchangeRates).length > 0) {
const fallbackMethods = Object.keys(defaultExchangeRates).map(currency => ({
@@ -387,15 +386,15 @@ const getVariance = (paid, key) => {
};
});
- console.log("=== Modal Payment Table Data ===");
- console.table(tableData);
+ // console.log("=== Modal Payment Table Data ===");
+ // console.table(tableData);
// Call the wrapper function
try {
await updateUserShiftPayments(tableData);
toast.success("Shift payments updated successfully!");
onOpenChange(false)
} catch (err) {
- console.error("Error updating shift payments:", err);
+ // console.error("Error updating shift payments:", err);
toast.error("Failed to update shift payments!");
}
window.location.href = "/dashboard";
diff --git a/dashboard/src/components/ui/ShiftDialog.jsx b/dashboard/src/components/ui/ShiftDialog.jsx
index 6734df8..828877f 100644
--- a/dashboard/src/components/ui/ShiftDialog.jsx
+++ b/dashboard/src/components/ui/ShiftDialog.jsx
@@ -45,7 +45,7 @@ const ShiftDialog = ({ open, type, onOpenChange, onShiftAction, cartItems }) =>
open={openPayment}
onOpenChange={setOpenPayment}
onPaid={(data) => {
- console.log("Payment done:", data);
+ // console.log("Payment done:", data);
setOpenPayment(false);
onOpenChange(false); // close shift dialog too
}}
@@ -66,8 +66,8 @@ const ShiftDialog = ({ open, type, onOpenChange, onShiftAction, cartItems }) =>
} else if (btn.action === "open") {
try {
const data = await openShift();
- console.log("Shift opened:", data);
- onShiftAction("open", data.message);
+ // console.log("Shift opened:", data);
+ typeof onShiftAction === "function" && onShiftAction("open", data.message);
onOpenChange(false);
window.location.href = "/dashboard";
} catch (err) {
@@ -78,7 +78,7 @@ const ShiftDialog = ({ open, type, onOpenChange, onShiftAction, cartItems }) =>
setOpenPayment(true);
} else {
onOpenChange(false);
- onShiftAction(btn.action, `${btn.label} clicked`);
+ typeof onShiftAction === "function" && onShiftAction(btn.action, `${btn.label} clicked`);
}
}}
className={`px-4 py-2 rounded font-bold ${
diff --git a/dashboard/src/contexts/MenuContext.jsx b/dashboard/src/contexts/MenuContext.jsx
index bb0f367..ed6abcc 100644
--- a/dashboard/src/contexts/MenuContext.jsx
+++ b/dashboard/src/contexts/MenuContext.jsx
@@ -1,6 +1,7 @@
import { createContext, useContext, useState, useEffect, useMemo, useRef } from "react";
import { useCartStore } from "@/stores/useCartStore";
import { useMenuStore } from "@/stores/useMenuStore";
+import { negativeStock } from "@/lib/utils";
import {
useCustomers,
useTransactionTypes,
@@ -17,8 +18,21 @@ export function MenuProvider({ children }) {
const [searchTerm, setSearchTerm] = useState("");
const [target, setTarget] = useState("menu");
const [selectedAgent, setSelectedAgent] = useState(null);
+ const [allowNegativeStock, setAllowNegativeStock] = useState(null);
const { menuItems, fetchMenuItems, menuCategories, fetchMenuCategories } = useMenuStore();
+ useEffect(() => {
+ let cancelled = false;
+ negativeStock()
+ .then((value) => {
+ if (!cancelled) setAllowNegativeStock(Boolean(value));
+ })
+ .catch(() => {
+ if (!cancelled) setAllowNegativeStock(false);
+ });
+ return () => { cancelled = true; };
+ }, []);
+
const menuGridRef = useRef(null);
@@ -139,6 +153,7 @@ export function MenuProvider({ children }) {
selectedAgent,
setSelectedAgent,
menuGridRef,
+ allowNegativeStock,
}}
>
{children}
diff --git a/dashboard/src/hooks/index.js b/dashboard/src/hooks/index.js
index 2322dfb..89f5702 100644
--- a/dashboard/src/hooks/index.js
+++ b/dashboard/src/hooks/index.js
@@ -11,4 +11,5 @@ export {default as useAgents} from './useAgents';
export {default as useCreateProductBundle} from './useCreateProductBundle';
export {default as useItemPreparationRemark} from './useItemPreparationRemark';
export {default as useCurrencyExchange} from './useCurrencyExchange';
-export {default as useMultiCurrencyPayment} from './useMultiCurrencyPayment';
\ No newline at end of file
+export {default as useMultiCurrencyPayment} from './useMultiCurrencyPayment';
+export {default as useRooms} from './useRooms';
\ No newline at end of file
diff --git a/dashboard/src/hooks/useMenuNavigation.js b/dashboard/src/hooks/useMenuNavigation.js
index 0bfa780..ade7762 100644
--- a/dashboard/src/hooks/useMenuNavigation.js
+++ b/dashboard/src/hooks/useMenuNavigation.js
@@ -86,6 +86,8 @@ export default function useMenuNavigation({ NUMBER_OF_COLUMNS, items, target, se
}
case "Enter":
+ event.preventDefault();
+ event.stopPropagation();
handleSelectItem(currentIndex);
break;
diff --git a/dashboard/src/hooks/useRooms.js b/dashboard/src/hooks/useRooms.js
new file mode 100644
index 0000000..954fe47
--- /dev/null
+++ b/dashboard/src/hooks/useRooms.js
@@ -0,0 +1,34 @@
+import { useState, useEffect } from "react";
+import { call } from "@/lib/frappeClient";
+
+export default function useRooms() {
+ const [rooms, setRooms] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const fetchRooms = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await call.get("havano_restaurant_pos.api.get_booked_rooms");
+ if (response.message && response.message.success) {
+ setRooms(response.message.rooms || []);
+ } else {
+ setError(response.message?.message || "Failed to fetch rooms");
+ setRooms([]);
+ }
+ } catch (err) {
+ console.error("Error fetching rooms:", err);
+ setError(err?.message || "Failed to fetch rooms");
+ setRooms([]);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchRooms();
+ }, []);
+
+ return { rooms, loading, error, fetchRooms };
+}
diff --git a/dashboard/src/lib/utils.js b/dashboard/src/lib/utils.js
index 947e1d9..61b4fe0 100644
--- a/dashboard/src/lib/utils.js
+++ b/dashboard/src/lib/utils.js
@@ -362,6 +362,25 @@ export async function isRestaurantMode() {
}
+export async function isRoomDirectBookingsEnabled() {
+ try {
+ const { message } = await db.getSingleValue(
+ "HA POS Settings",
+ "enable_room_direct_bookings"
+ );
+ if (message && typeof message === "object") {
+ return Boolean(message.enable_room_direct_bookings);
+ }
+ if (typeof message === "string") {
+ return Boolean(Number(message));
+ }
+ return Boolean(message);
+ } catch (err) {
+ console.error("Error fetching HA POS Settings.enable_room_direct_bookings:", err);
+ return false;
+ }
+}
+
/**
* Get user transaction type mappings from HA POS Setting
* Returns array of transaction types available for the current user
@@ -689,7 +708,8 @@ export async function getUserSettings() {
}
export async function negativeStock() {
return attemptWithRetries(async () => {
- const { message } = await call.post("havano_restaurant_pos.api.can_use_negative_stock");
+ // Use GET to avoid CSRF requirement (read-only check)
+ const { message } = await call.get("havano_restaurant_pos.api.can_use_negative_stock");
return message;
}, "Can Use Negative Stock");
}
diff --git a/dashboard/src/pages/Home.jsx b/dashboard/src/pages/Home.jsx
index 5a3e306..68921b4 100644
--- a/dashboard/src/pages/Home.jsx
+++ b/dashboard/src/pages/Home.jsx
@@ -23,7 +23,7 @@ import { useMenuStore } from "@/stores/useMenuStore";
const Home = () => {
const navigate = useNavigate();
- const { startNewTakeAwayOrder, addToCart, clearCart } = useCartStore();
+ const { startNewTakeAwayOrder, addToCart } = useCartStore();
const { menuItems, fetchMenuItems } = useMenuStore();
const [userName, setUserName] = useState(null);
const [popularItems, setPopularItems] = useState([]);
@@ -54,7 +54,7 @@ const Home = () => {
const loadRestaurantMode = async () => {
try {
const enabled = await isRestaurantMode();
- console.log("enabled", enabled);
+ // console.log("enabled", enabled);
if (!cancelled) setRestModeEnabled(Boolean(enabled));
} catch (err) {
console.error("Failed to load Restaurant Mode:", err);
@@ -114,7 +114,6 @@ const Home = () => {
}
startNewTakeAwayOrder();
- clearCart();
addToCart({
...item,
quantity: 1,
@@ -149,9 +148,9 @@ const Home = () => {
size="lg"
onClick={() => {
if (!restModeEnabled) {
- console.info(
- "[HA POS] Dine In clicked, but Restaurant Mode is disabled in HA POS Settings."
- );
+ // console.info(
+ // "[HA POS] Dine In clicked, but Restaurant Mode is disabled in HA POS Settings."
+ // );
return;
}
navigate("/tables");
@@ -163,7 +162,7 @@ const Home = () => {
}
className={!restModeEnabled ? "opacity-50 cursor-not-allowed" : ""}
>
- DINE IN
+ TABLES
{/* Middle content: buttons + menu */}
-
+
{/* Left: Dine In / Take Away */}
diff --git a/dashboard/src/pages/TableDetails.jsx b/dashboard/src/pages/TableDetails.jsx
index 29eecc2..e196176 100644
--- a/dashboard/src/pages/TableDetails.jsx
+++ b/dashboard/src/pages/TableDetails.jsx
@@ -46,10 +46,11 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { db } from "@/lib/frappeClient";
-import { formatCurrency, markTableAsPaid, getCustomers, getDefaultCustomer, processTablePayment, getCurrentUser } from "@/lib/utils";
+import { formatCurrency, markTableAsPaid, getCustomers, getDefaultCustomer, processTablePayment, getCurrentUser, isRoomDirectBookingsEnabled } from "@/lib/utils";
import { useCartStore } from "@/stores/useCartStore";
import { useOrderStore } from "@/stores/useOrderStore";
import { useTableStore } from "@/stores/useTableStore";
+import { useRooms } from "@/hooks";
import { ta } from "zod/v4/locales";
const TableDetails = () => {
@@ -63,6 +64,8 @@ const TableDetails = () => {
const [isWaiterNotConfiguredDialogOpen, setIsWaiterNotConfiguredDialogOpen] = useState(false);
const [waiters, setWaiters] = useState([]);
const [loadingWaiters, setLoadingWaiters] = useState(false);
+ const [roomBookingsEnabled, setRoomBookingsEnabled] = useState(false);
+ const [selectedRoom, setSelectedRoom] = useState(null);
const navigate = useNavigate();
const { id } = useParams();
const { register, setValue, watch } = useForm({
@@ -89,6 +92,7 @@ const TableDetails = () => {
} = useTableStore();
const { startTableOrder, loadCartFromOrder, clearCart } = useCartStore();
+ const { rooms, loading: loadingRooms, fetchRooms } = useRooms();
useEffect(() => {
if (!id) return;
@@ -119,6 +123,20 @@ const TableDetails = () => {
fetchCustomersData();
}, []);
+ useEffect(() => {
+ const checkRoomBookings = async () => {
+ const enabled = await isRoomDirectBookingsEnabled();
+ setRoomBookingsEnabled(Boolean(enabled));
+ };
+ checkRoomBookings();
+ }, []);
+
+ useEffect(() => {
+ if (selectedRoom && selectedRoom.customer) {
+ setValue("customerName", selectedRoom.customer, { shouldValidate: true });
+ }
+ }, [selectedRoom, setValue]);
+
useEffect(() => {
const setDefaultCustomer = async () => {
if (tableDetails?.customer_name) {
@@ -479,9 +497,59 @@ const TableDetails = () => {
};
refreshCustomers();
setValue("customerName", newCustomer.value, { shouldValidate: true });
+ // Clear room selection when customer is manually changed
+ if (selectedRoom) {
+ setSelectedRoom(null);
+ }
+ }}
+ onValueChange={(value) => {
+ setValue("customerName", value, { shouldValidate: true });
+ // Clear room selection when customer is manually changed
+ if (value && selectedRoom) {
+ setSelectedRoom(null);
+ }
}}
/>
+ {roomBookingsEnabled && (
+
+
+ ({
+ value: room.name,
+ name: room.name,
+ label: `Room ${room.room_number}${room.guest_name ? ` - ${room.guest_name}` : ""}`,
+ room_number: room.room_number,
+ customer: room.customer,
+ customer_name: room.customer_name,
+ }))}
+ value={selectedRoom?.name || ""}
+ onValueChange={(value) => {
+ if (value) {
+ const room = rooms.find((r) => r.name === value);
+ if (room) {
+ setSelectedRoom(room);
+ if (room.customer) {
+ setValue("customerName", room.customer, { shouldValidate: true });
+ }
+ }
+ } else {
+ setSelectedRoom(null);
+ }
+ }}
+ placeholder={loadingRooms ? "Loading..." : "Select room"}
+ searchPlaceholder="Search rooms..."
+ disabled={loadingRooms}
+ className="w-full"
+ onOpenChange={(open) => {
+ if (open) {
+ fetchRooms();
+ }
+ }}
+ />
+
+ )}