Files
web-menu-react-version-/src/pages/order/page.tsx

733 lines
23 KiB
TypeScript

import {
Button,
Card,
Divider,
Flex,
Image,
Layout,
Progress,
Tooltip,
} from "antd";
import { CancelOrderBottomSheet } from "components/CustomBottomSheet/CancelOrderBottomSheet";
import LocationIcon from "components/Icons/LocationIcon";
import InvoiceIcon from "components/Icons/order/InvoiceIcon";
import TimeIcon from "components/Icons/order/TimeIcon";
import OrderDishIcon from "components/Icons/OrderDishIcon";
import PaymentDetails from "components/PaymentDetails/PaymentDetails";
import ProHeader from "components/ProHeader/ProHeader";
import ProText from "components/ProText";
import ProTitle from "components/ProTitle";
import dayjs from "dayjs";
import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import {
useGetOrderDetailsQuery,
useGetRestaurantDetailsQuery,
} from "redux/api/others";
import { useAppSelector } from "redux/hooks";
import Stepper from "./components/Stepper";
import styles from "./order.module.css";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import { RateBottomSheet } from "components/CustomBottomSheet/RateBottomSheet";
import BriefMenuCard from "pages/checkout/components/BriefMenuCard";
import { QRBottomSheet } from "pages/pay/components/splitBill/QRBottomSheet";
import NewRateIcon from "components/Icons/order/NewRateIcon";
import NoteIcon from "components/Icons/NoteIcon";
import SuccessIcon from "components/Icons/SuccessIcon";
import { SplitBillParticipantsBottomSheet } from "./components/SplitBillParticipantsBottomSheet";
import { OrderType } from "pages/checkout/hooks/types";
export default function OrderPage() {
const { t } = useTranslation();
const { orderId } = useParams();
const navigate = useNavigate();
const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant, orderType } = useAppSelector((state) => state.order);
const hasRefetchedRef = useRef(false);
const [isOpen, setIsOpen] = useState(false);
const [isRateOrderOpen, setIsRateOrderOpen] = useState(false);
// Track order details in state to trigger re-evaluation of polling
const [lastOrderDetails, setLastOrderDetails] = useState<any>(null);
// Compute polling interval based on order status
const pollingInterval = useMemo(() => {
const orderDetailsToCheck = lastOrderDetails;
if (orderDetailsToCheck?.status) {
const hasClosedOrCanceled = orderDetailsToCheck.status.some(
(status: any) =>
status?.alias === "closed" ||
status?.alias === "canceled_by_customer",
);
if (hasClosedOrCanceled) {
return 0; // Stop polling
}
}
return 10000; // Continue polling
}, [lastOrderDetails]);
const { data: orderDetails } = useGetOrderDetailsQuery(
{
orderID: orderId || "",
restaurantID: localStorage.getItem("restaurantID") || "",
},
{
skip: !orderId,
// return it t0 60000 after finish testing
// Stop polling if order is closed or canceled
pollingInterval,
refetchOnMountOrArgChange: true,
},
);
// Update state when orderDetails changes to trigger polling re-evaluation
useEffect(() => {
if (orderDetails) {
setLastOrderDetails(orderDetails);
}
}, [orderDetails]);
const hasClosedStatus = orderDetails?.status?.some(
(status) => status?.alias === "closed",
);
const hasCanceledByCustomerStatus = orderDetails?.status?.some(
(status) => status?.alias === "canceled_by_customer",
);
const [
isSplitBillParticipantsBottomSheetOpen,
setIsSplitBillParticipantsBottomSheetOpen,
] = useState(false);
// Get restaurant subdomain for refetching
const restaurantSubdomain = restaurant?.subdomain;
const { refetch: refetchRestaurantDetails } = useGetRestaurantDetailsQuery(
restaurantSubdomain || "",
{
skip: !restaurantSubdomain,
},
);
// Reset refetch flag when orderId changes
useEffect(() => {
hasRefetchedRef.current = false;
}, [orderId]);
// Refetch restaurant details when order status has alias "closed"
useEffect(() => {
if (orderDetails?.status && !hasRefetchedRef.current) {
if (
(hasClosedStatus || hasCanceledByCustomerStatus) &&
restaurantSubdomain
) {
refetchRestaurantDetails();
hasRefetchedRef.current = true;
}
}
}, [orderDetails?.status, restaurantSubdomain, refetchRestaurantDetails]);
// Check if order is in progress (check last status alias)
const lastStatus = orderDetails?.status?.[orderDetails.status.length - 1];
const isInProgress = lastStatus?.alias === "accepted_by_restaurant";
// Calculate timer and progress
const [remainingSeconds, setRemainingSeconds] = useState<number>(0);
const [progressPercent, setProgressPercent] = useState<number>(0);
useEffect(() => {
if (
!isInProgress ||
!orderDetails?.order?.time_to_prepare ||
!orderDetails?.order?.created_at
) {
return;
}
const updateTimer = () => {
const orderCreatedAt = dayjs(orderDetails.order.created_at);
const now = dayjs();
const elapsedSeconds = now.diff(orderCreatedAt, "second");
// time_to_prepare is in minutes, convert to seconds
const totalSeconds =
(Number(orderDetails.order.time_to_prepare) || 0) * 60;
const remaining = Math.max(0, totalSeconds - elapsedSeconds);
setRemainingSeconds(remaining);
// Calculate progress as remaining time percentage (starts at 100%, decreases to 0%)
const percent =
totalSeconds > 0
? Math.min(100, Math.max(0, (remaining / totalSeconds) * 100))
: 100;
setProgressPercent(percent);
};
updateTimer();
const interval = setInterval(updateTimer, 1000);
return () => clearInterval(interval);
}, [
isInProgress,
orderDetails?.order?.time_to_prepare,
orderDetails?.status,
]);
// Format remaining time with Min and Sec labels
const formatTimer = (totalSeconds: number) => {
const mins = Math.floor(totalSeconds / 60);
const secs = Math.floor(totalSeconds % 60);
return (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{mins.toString().padStart(2, "0")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{t("order.min")}
</ProText>
</div>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
:
</ProText>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 2,
}}
>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 28,
lineHeight: "100%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{secs.toString().padStart(2, "0")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#CC9300",
}}
>
{t("order.sec")}
</ProText>
</div>
</div>
);
};
return (
<>
<Layout>
<ProHeader customRoute={`/${restaurant?.subdomain}`}>
{t("order.title")}
</ProHeader>
<Layout.Content className={styles.orderContent}>
<Card className={styles.orderCard}>
<div
style={{
display: "flex",
flexDirection: "row",
gap: "1rem",
backgroundColor: "#f5f5f6",
borderRadius: "12px",
padding: 16,
}}
>
<Button type="text" shape="circle">
<Image
src={orderDetails?.restaurant_iimage}
className={styles.profileImage}
width={40}
height={40}
preview={false}
/>
</Button>
<div>
<ProText
style={{
fontFamily: "Bold/Caption Bold",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
fontWeight: "bold",
color: "#434E5C",
}}
>
{t("order.yourOrderFromFascanoRestaurant")}
</ProText>
<br />
<ProText type="secondary">
<LocationIcon className={styles.locationIcon} />{" "}
{isRTL
? orderDetails?.restaurantAR
: orderDetails?.restaurant}
</ProText>
</div>
</div>
{isInProgress ? (
<Flex gap="small" wrap justify="center" style={{ marginTop: 16 }}>
<Tooltip title="3 done / 3 in progress / 4 to do">
<div
style={{
transform: "scaleX(-1)",
}}
>
<Progress
percent={progressPercent}
format={() => (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 4,
transform: "scaleX(-1)",
}}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
}}
>
{t("order.remainingTime")}
</ProText>
{formatTimer(remainingSeconds)}
</div>
)}
strokeColor="#FFB700"
size={200}
type="dashboard"
/>
</div>
</Tooltip>
</Flex>
) : (
<div style={{ margin: "56px 0" }}>
<OrderDishIcon className={styles.orderDishIcon} />
</div>
)}
<div>
<ProTitle
level={5}
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
marginBottom: "0.75rem",
color: "#333333",
}}
>
{t("order.inProgressOrder")} (1)
</ProTitle>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 8,
}}
>
<InvoiceIcon className={styles.invoiceIcon} />
<ProText
type="secondary"
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
#{orderDetails?.order.id}
</ProText>
<TimeIcon className={styles.timeIcon} />
<ProText
type="secondary"
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
ordered :- Today -{" "}
{dayjs(orderDetails?.status[0]?.pivot?.created_at).format(
"h:mm A",
)}
</ProText>
</div>
<Divider style={{ margin: "12px 0" }} />
<Stepper statuses={orderDetails?.status} />
</div>
{!hasClosedStatus && orderType === OrderType.DineIn && (
<div className={styles.orderNotes}>
<NoteIcon className={styles.noteIcon} />
<div
style={{ display: "flex", flexDirection: "column", gap: 4 }}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t(
"order.aStaffMemberWillCollectTheCashFromYouAtYourTable",
)}
</ProText>
<ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#3D3B4A",
}}
>
{t("order.callWaiter")}
</ProText>
</div>
</div>
)}
{hasClosedStatus && orderType === OrderType.DineIn && (
<div className={styles.orderNotesClosed}>
<SuccessIcon className={styles.noteIcon} />
<ProText
style={{
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
color: "#3D3B4A",
}}
>
{t("order.cashPaymentConfirmed")}
</ProText>
</div>
)}
</Card>
{/* <Ads2 /> */}
{/* <ProInputCard
title={
<div style={{ marginBottom: 7 }}>
<ProText style={{ fontSize: "1rem" }}>
{t("order.yourOrderFrom")}
</ProText>
<br />
<ProText type="secondary">
<LocationIcon className={styles.locationIcon} />{" "}
{t("order.muscat")}
</ProText>
</div>
}
>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
{orderDetails?.orderItems.map((item, index) => (
<div key={item.id}>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
gap: "1rem",
}}
>
<Button
type="text"
shape="circle"
style={{
backgroundColor: "rgba(255, 183, 0, 0.08)",
}}
>
{index + 1}X
</Button>
<div>
<ProText
style={{ fontSize: "1rem", position: "relative", top: 8 }}
>
{item.name}
</ProText>
</div>
</div>
</div>
))}
</div>
</ProInputCard> */}
<BriefMenuCard />
<PaymentDetails order={orderDetails?.order} />
{/* inviteToBill */}
{/* {!hasClosedStatus && (
<ProInputCard
title={
<>
<div
style={{
display: "flex",
flexDirection: "column",
marginBottom: 9,
gap: 2,
}}
>
<ProText
style={{
fontSize: "18px",
fontWeight: 500,
fontStyle: "Medium",
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("order.whosPaidTheirShareSplitBill")}
</ProText>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("order.seeWhoPaidAndHowMuch")}
</ProText>
</div>
</>
}
>
<div
className={styles.inviteToBill}
onClick={() => setIsSplitBillParticipantsBottomSheetOpen(true)}
>
<Button
shape="circle"
iconPlacement="start"
size="small"
className={styles.addButton}
style={{
background: "#F9F9FA",
width: 28,
height: 28,
border: "none",
color: "#FFB700",
}}
>
<ProText style={{ color: "#1F1C2E" }}> 1 </ProText>
</Button>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 4,
width: "100%",
}}
>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("order.personHasPaid")}
</ProText>
</div>
{isRTL ? (
<BackIcon iconSize={24} />
) : (
<NextIcon iconSize={24} />
)}
</div>
<Button
type="text"
style={{
height: 48,
width: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 8,
border: "1px solid #C0BFC4",
borderRadius: 888,
marginTop: 16,
}}
onClick={() => setIsOpen(true)}
>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("order.inviteOthersToBill")}
</ProText>
</Button>
</ProInputCard>
)} */}
<QRBottomSheet isOpen={isOpen} onClose={() => setIsOpen(false)} />
{hasClosedStatus && (
<Card
className={styles.backToHomePage}
onClick={() => setIsRateOrderOpen(true)}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div>
<NewRateIcon className={styles.newRateIcon} />
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
color: "#595764",
position: "relative",
margin: "0 8px",
top: -12,
}}
>
{t("order.rateOrder")}
</ProText>
</div>
{isRTL ? (
<BackIcon className={styles.serviceIcon} />
) : (
<NextIcon className={styles.serviceIcon} />
)}
</div>
</Card>
)}
<RateBottomSheet
isOpen={isRateOrderOpen}
onClose={() => setIsRateOrderOpen(false)}
/>
{!hasClosedStatus && !hasCanceledByCustomerStatus && (
<CancelOrderBottomSheet />
)}
</Layout.Content>
{(hasClosedStatus || hasCanceledByCustomerStatus) && (
<Layout.Footer className={styles.checkoutButtonContainer}>
<Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={() => {
navigate(`/${restaurant?.subdomain}/menu`);
}}
>
{t("order.newOrder")}
</Button>
</Layout.Footer>
)}
</Layout>
<SplitBillParticipantsBottomSheet
isOpen={isSplitBillParticipantsBottomSheetOpen}
onClose={() => setIsSplitBillParticipantsBottomSheetOpen(false)}
/>
</>
);
}