Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 60 additions & 20 deletions dashboard/src/layouts/components/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NavLink, useNavigate } from "react-router-dom";
import Container from "@/components/Shared/Container";
import getNavLinks from "@/navLinks";
import { useCartStore } from "@/stores/useCartStore";
import { isHotelAppInstalled } from "@/lib/utils";

const Footer = () => {
const [navLinks, setNavLinks] = useState([]);
Expand All @@ -26,33 +27,72 @@ const Footer = () => {
}
};

const handleHotelClick = async (e) => {
e.preventDefault();
try {
const installed = await isHotelAppInstalled();
if (installed) {
window.location.href = "/app/hotel-dashboard";
} else {
alert("Havano Hotel Management app is not installed.");
}
} catch (error) {
console.error("Error checking hotel app:", error);
alert("Unable to open Hotel Dashboard. Please contact your administrator.");
}
};

return (
<div>
<hr className="border border-primary" />
<Container>
<div className="py-4">
<div className="flex items-center justify-between">
{/* App Icon - Link to main Frappe app */}
<a
href="/app"
className="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-gray-100 transition-colors"
title="Go to App"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-gray-700 hover:text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
<div className="flex items-center gap-2">
{/* App Icon - Link to main Frappe app */}
<a
href="/app"
className="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-gray-100 transition-colors"
title="Go to App"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
</a>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-gray-700 hover:text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
</a>
{/* Hotel Icon - Link to Hotel Dashboard */}
<a
href="#"
onClick={handleHotelClick}
className="flex items-center justify-center w-10 h-10 rounded-lg hover:bg-gray-100 transition-colors"
title="Go to Hotel Dashboard"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 text-gray-700 hover:text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
/>
</svg>
</a>
</div>
{navLinks.map((link) => {
if (!link.active) {
return (
Expand Down
21 changes: 21 additions & 0 deletions dashboard/src/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ export async function getNumberOfItems(item_group = null) {
}
}

export async function isHotelAppInstalled() {
try {
const { message } = await call.get(
"havano_restaurant_pos.api.is_hotel_app_installed"
);

if (typeof message === "boolean") {
return message;
}

if (message && typeof message === "object" && "installed" in message) {
return Boolean(message.installed);
}

return false;
} catch (error) {
console.error("Error checking Havano Hotel app installation:", error);
return false;
}
}

export async function getNumberOfOrders(menuItem) {
if (!menuItem) {
console.warn("getNumberOfOrders called without a menu item identifier.");
Expand Down
114 changes: 98 additions & 16 deletions dashboard/src/pages/TableDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@ import {
} from "@/components/ui/table";
import { Textarea } from "@/components/ui/textarea";
import { Combobox } from "@/components/ui/combobox";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { db } from "@/lib/frappeClient";
import { formatCurrency, markTableAsPaid, getCustomers, getDefaultCustomer, processTablePayment } from "@/lib/utils";
import { formatCurrency, markTableAsPaid, getCustomers, getDefaultCustomer, processTablePayment, getCurrentUser } from "@/lib/utils";
import { useCartStore } from "@/stores/useCartStore";
import { useOrderStore } from "@/stores/useOrderStore";
import { useTableStore } from "@/stores/useTableStore";
import { useWaiterStore } from "@/stores/useWaiterStore";
import { ta } from "zod/v4/locales";

const TableDetails = () => {
Expand All @@ -53,6 +60,9 @@ const TableDetails = () => {
const [customers, setCustomers] = useState([]);
const [loadingCustomers, setLoadingCustomers] = useState(true);
const [isPaymentDialogOpen, setIsPaymentDialogOpen] = useState(false);
const [isWaiterNotConfiguredDialogOpen, setIsWaiterNotConfiguredDialogOpen] = useState(false);
const [waiters, setWaiters] = useState([]);
const [loadingWaiters, setLoadingWaiters] = useState(false);
const navigate = useNavigate();
const { id } = useParams();
const { register, setValue, watch } = useForm({
Expand All @@ -62,8 +72,6 @@ const TableDetails = () => {
remarks: "",
},
});
const { waiters, loadingWaiters, errorWaiters, fetchWaiters } =
useWaiterStore();

const {
tableOrders,
Expand All @@ -72,7 +80,6 @@ const TableDetails = () => {
fetchTableOrders,
} = useOrderStore();

console.log("tableOrders", tableOrders);
const {
tableDetails,
loadingTableDetails,
Expand All @@ -91,8 +98,7 @@ const TableDetails = () => {
useEffect(() => {
if (!id) return;
fetchTableOrders(id);
fetchWaiters();
}, [id, fetchTableOrders, fetchWaiters]);
}, [id, fetchTableOrders]);

useEffect(() => {
const fetchCustomersData = async () => {
Expand Down Expand Up @@ -156,10 +162,48 @@ const TableDetails = () => {
}, [tableDetails, customers, loadingCustomers, setValue]);

useEffect(() => {
if (tableDetails?.assigned_waiter) {
setValue("waiter", tableDetails.assigned_waiter);
} else {
setValue("waiter", "");
const setWaiterFromUser = async () => {
// Always get waiter from logged in user, regardless of table status
setLoadingWaiters(true);
try {
const currentUser = await getCurrentUser();
if (currentUser && currentUser.name) {
// Get waiter name from HA Waiter doctype where user matches logged in user
const waiterDocs = await db.getDocList("HA Waiter", {
fields: ["name", "user", "waiter_name"],
filters: {
user: currentUser.name
}
});
if (waiterDocs && waiterDocs.length > 0) {
// Set the waiter in the select list (only the waiter for logged in user)
setWaiters(waiterDocs);
// Use the name field from HA Waiter doctype (document name, not waiter_name)
// This matches the SelectItem value which uses waiter.name
const waiterName = waiterDocs[0].name;
setValue("waiter", waiterName, { shouldValidate: true });
} else {
// No waiter configured for this user
setWaiters([]);
setValue("waiter", "", { shouldValidate: true });
// Show popup message
setIsWaiterNotConfiguredDialogOpen(true);
}
}
} catch (err) {
console.error("Error getting waiter from user:", err);
setWaiters([]);
setValue("waiter", "", { shouldValidate: true });
// Show popup message on error
setIsWaiterNotConfiguredDialogOpen(true);
} finally {
setLoadingWaiters(false);
}
};

// Set waiter even if table is occupied
if (tableDetails) {
setWaiterFromUser();
}
}, [tableDetails, setValue]);

Expand Down Expand Up @@ -213,6 +257,27 @@ const TableDetails = () => {

const waiter = watch("waiter");
if (!waiter) {
// Check if user is configured as waiter
try {
const currentUser = await getCurrentUser();
if (currentUser && currentUser.name) {
const waiters = await db.getDocList("HA Waiter", {
fields: ["name", "user"],
filters: {
user: currentUser.name
}
});

if (!waiters || waiters.length === 0) {
// User is not configured as waiter, show popup
setIsWaiterNotConfiguredDialogOpen(true);
return;
}
}
} catch (err) {
console.error("Error checking waiter configuration:", err);
}

toast.error("Select a waiter before placing an order.");
return;
}
Expand Down Expand Up @@ -420,11 +485,11 @@ const TableDetails = () => {
<div className="space-y-4">
<Label>Waiter</Label>
<Select
value={watch("waiter")}
value={watch("waiter") || ""}
onValueChange={(value) =>
setValue("waiter", value, { shouldValidate: true })
}
disabled={loadingWaiters}
disabled={loadingWaiters || !!watch("waiter")}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select waiter" />
Expand All @@ -447,9 +512,6 @@ const TableDetails = () => {
))}
</SelectContent>
</Select>
{errorWaiters && (
<p className="text-sm text-red-500">{errorWaiters}</p>
)}
</div>
<div className="space-y-4">
<Label>Remarks</Label>
Expand Down Expand Up @@ -566,6 +628,26 @@ const TableDetails = () => {
transactionDoctype={null}
transactionName={null}
/>
<Dialog
open={isWaiterNotConfiguredDialogOpen}
onOpenChange={setIsWaiterNotConfiguredDialogOpen}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Waiter Not Configured</DialogTitle>
<DialogDescription>
No available waiter, please contact admin.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
onClick={() => setIsWaiterNotConfiguredDialogOpen(false)}
>
OK
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Container>
);
};
Expand Down
Loading
Loading