From ab5d0da47e4b2c84aa6310103794f01674bddd2a Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Wed, 29 Oct 2025 20:41:26 +0300 Subject: [PATCH] implement scheduled order type --- src/assets/locals/ar.json | 9 ++- src/assets/locals/en.json | 12 ++-- src/components/ProInputCard/ProInputCard.tsx | 4 +- src/features/order/orderSlice.ts | 20 +++++- src/index.css | 13 ++-- src/pages/cart/components/CarPlateCard.tsx | 21 +++--- .../components/CartMobileTabletLayout.tsx | 12 +++- src/pages/cart/components/CouponCard.tsx | 70 ++++++++++--------- src/pages/cart/components/DateCard.tsx | 56 +++++++++++++++ src/pages/cart/components/TableNumberCard.tsx | 3 + .../specialRequest/SpecialRequestCard.tsx | 43 +++++++----- src/pages/cart/page.tsx | 6 +- .../CategoriesList/CategoriesList.module.css | 1 - .../CategoriesList/CategoriesList.tsx | 1 - src/pages/menu/menu.module.css | 2 +- src/pages/menu/page.tsx | 5 +- src/pages/restaurant/RestaurantServices.tsx | 18 ++++- src/pages/restaurant/restaurant.module.css | 5 ++ 18 files changed, 219 insertions(+), 82 deletions(-) create mode 100644 src/pages/cart/components/DateCard.tsx diff --git a/src/assets/locals/ar.json b/src/assets/locals/ar.json index b12c3c5..df09f1d 100644 --- a/src/assets/locals/ar.json +++ b/src/assets/locals/ar.json @@ -73,7 +73,8 @@ "delivery": "التوصيل", "room": "الغرفة", "office": "المكتب", - "booking": "الحجز" + "booking": "الحجز", + "scheduledOrder": "الطلب المجدول" }, "home": { "title": "العنوان", @@ -84,7 +85,8 @@ "gift": "أرسل وجبة كهدية لشخص مميز.", "room": "خدمة الغرف لراحتك.", "office": "توصيل إلى مكتبك.", - "booking": "احجز طاولة مسبقاً." + "booking": "احجز طاولة مسبقاً.", + "scheduledOrder": "طلب مجدول" }, "promotion": { "title": "الترويجات", @@ -235,7 +237,8 @@ "noLoyaltyItemsInCart": "لا توجد عناصر ولاء في سلة المشتريات", "pleaseAddLoyaltyItems": "يرجى إضافة عناصر ولاء إلى سلة المشتريات لاستخدام نقاط الولاء", "loyaltyDiscountApplied": "تم تطبيق خصم الولاء: {{itemName}} (خصم {{amount}})", - "deliveryFee": "رسوم التوصيل" + "deliveryFee": "رسوم التوصيل", + "scheduledDate": "تاريخ الطلب المجدول" }, "checkout": { "title": "الدفع", diff --git a/src/assets/locals/en.json b/src/assets/locals/en.json index 9131527..fdf6d5a 100644 --- a/src/assets/locals/en.json +++ b/src/assets/locals/en.json @@ -73,7 +73,8 @@ "delivery": "Delivery", "noMenuItemsAvailable": "No menu items available", "restaurantCover": "Restaurant Cover", - "restaurantLogo": "Restaurant Logo" + "restaurantLogo": "Restaurant Logo", + "scheduledOrder": "Scheduled Order" }, "home": { "title": "title", @@ -100,7 +101,8 @@ "room": "Room service for your comfort.", "office": "Delivery to your office.", "booking": "Book a table in advance.", - "delivery": "Delivery" + "delivery": "Delivery", + "scheduledOrder": "Scheduled Order" }, "promotion": { "title": "Promotions", @@ -245,7 +247,8 @@ "noLoyaltyItemsInCart": "No loyalty items found in your cart", "pleaseAddLoyaltyItems": "Please add loyalty items to your cart to use loyalty points", "loyaltyDiscountApplied": "Loyalty discount applied: {{itemName}} ({{amount}} off)", - "deliveryFee": "Delivery Fee" + "deliveryFee": "Delivery Fee", + "scheduledDate": "Scheduled Date" }, "checkout": { "title": "Checkout", @@ -370,6 +373,7 @@ "delivery": "Delivery", "office": "To Office", "scheduled_order": "Scheduled", - "booking": "Booking" + "booking": "Booking", + "scheduledOrder": "Scheduled Order" } } diff --git a/src/components/ProInputCard/ProInputCard.tsx b/src/components/ProInputCard/ProInputCard.tsx index 56601f9..7de9dac 100644 --- a/src/components/ProInputCard/ProInputCard.tsx +++ b/src/components/ProInputCard/ProInputCard.tsx @@ -8,6 +8,7 @@ interface ProInputCardProps { title?: string | ReactNode; titleRight?: ReactNode; className?: string; + dividerStyle?: React.CSSProperties; } const ProInputCard: FunctionComponent = ({ @@ -15,6 +16,7 @@ const ProInputCard: FunctionComponent = ({ title, titleRight, className, + dividerStyle, }) => { return ( @@ -31,7 +33,7 @@ const ProInputCard: FunctionComponent = ({ {title && typeof title !== "string" && title}
{titleRight}
- + {children}
); diff --git a/src/features/order/orderSlice.ts b/src/features/order/orderSlice.ts index 264c75e..497b810 100644 --- a/src/features/order/orderSlice.ts +++ b/src/features/order/orderSlice.ts @@ -56,6 +56,7 @@ interface CartState { orderType: OrderType | ""; useLoyaltyPoints: boolean; loyaltyValidationError: string | null; + scheduledDate: string; } // localStorage keys @@ -79,6 +80,7 @@ export const CART_STORAGE_KEYS = { USE_LOYALTY_POINTS: "fascano_use_loyalty_points", LOYALTY_VALIDATION_ERROR: "fascano_loyalty_validation_error", RESTAURANT: "fascano_restaurant", + SCHEDULED_DATE: "fascano_scheduled_date", } as const; // Utility functions for localStorage @@ -127,7 +129,10 @@ const initialState: CartState = { ), phone: getFromLocalStorage(CART_STORAGE_KEYS.PHONE, ""), paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""), - orderType: getFromLocalStorage(CART_STORAGE_KEYS.ORDER_TYPE, "" as OrderType | ""), + orderType: getFromLocalStorage( + CART_STORAGE_KEYS.ORDER_TYPE, + "" as OrderType | "", + ), useLoyaltyPoints: getFromLocalStorage( CART_STORAGE_KEYS.USE_LOYALTY_POINTS, false, @@ -137,6 +142,7 @@ const initialState: CartState = { null, ), restaurant: getFromLocalStorage(CART_STORAGE_KEYS.RESTAURANT, { taxes: [] }), + scheduledDate: getFromLocalStorage(CART_STORAGE_KEYS.SCHEDULED_DATE, ""), }; const orderSlice = createSlice({ @@ -504,6 +510,17 @@ const orderSlice = createSlice({ ); } }, + updateScheduledDate(state, action: PayloadAction) { + state.scheduledDate = action.payload; + + // Sync to localStorage + if (typeof window !== "undefined") { + localStorage.setItem( + CART_STORAGE_KEYS.SCHEDULED_DATE, + JSON.stringify(state.scheduledDate), + ); + } + }, }, }); @@ -534,6 +551,7 @@ export const { clearLoyaltyValidationError, reset, updateRestaurant, + updateScheduledDate, } = orderSlice.actions; // Tax calculation helper functions diff --git a/src/index.css b/src/index.css index 055c039..1bbc15d 100644 --- a/src/index.css +++ b/src/index.css @@ -384,17 +384,22 @@ label { transition: background-color 5000s ease-in-out 0s !important; } -/* Style for the select component and its dropdown */ -:where(.ant-select .ant-select-selection-item) { +/* Styles scoped to orderTypeSelectContainer dropdown */ +.order-type-select-dropdown :where(.ant-select .ant-select-selection-item) { font-size: 12px !important; font-weight: 700 !important; text-align: center; } -.menu-select-container :where(.ant-select .ant-select-arrow) { +.order-type-select-dropdown :where(.ant-select .ant-select-arrow) { font-weight: 600 !important; color: black; } -.menu-select-container :where(.ant-select-dropdown .ant-select-item) { +.order-type-select-dropdown :where(.ant-select-dropdown .ant-select-item) { font-size: 12px !important; font-weight: 600 !important; } + +.order-type-select-dropdown :where(.ant-select-item-option) { + min-height: 30px !important; + padding: 5px 19px !important; +} diff --git a/src/pages/cart/components/CarPlateCard.tsx b/src/pages/cart/components/CarPlateCard.tsx index 4cc5cdf..51dac00 100644 --- a/src/pages/cart/components/CarPlateCard.tsx +++ b/src/pages/cart/components/CarPlateCard.tsx @@ -1,4 +1,4 @@ -import { Input } from "antd"; +import { Form, Input } from "antd"; import ProInputCard from "components/ProInputCard/ProInputCard.tsx"; import { useTranslation } from "react-i18next"; @@ -6,13 +6,18 @@ export default function CarPlateCard() { const { t } = useTranslation(); return ( <> - - + + + + ); diff --git a/src/pages/cart/components/CartMobileTabletLayout.tsx b/src/pages/cart/components/CartMobileTabletLayout.tsx index 48ad921..a1f1beb 100644 --- a/src/pages/cart/components/CartMobileTabletLayout.tsx +++ b/src/pages/cart/components/CartMobileTabletLayout.tsx @@ -25,12 +25,13 @@ import useBreakPoint from "hooks/useBreakPoint.ts"; import CarPlateCard from "pages/cart/components/CarPlateCard.tsx"; import CartFooter from "pages/cart/components/cartFooter/CartFooter.tsx"; import CouponCard from "pages/cart/components/CouponCard.tsx"; +import DateCard from "pages/cart/components/DateCard.tsx"; import RewardWaiterCard from "pages/cart/components/RewardWaiterCard.tsx"; import SpecialRequestCard from "pages/cart/components/specialRequest/SpecialRequestCard.tsx"; import TableNumberCard from "pages/cart/components/TableNumberCard.tsx"; import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard.tsx"; -import { useTranslation } from "react-i18next"; import { OrderType } from "pages/checkout/hooks/types"; +import { useTranslation } from "react-i18next"; interface CartMobileTabletLayoutProps { form: FormInstance; @@ -58,6 +59,7 @@ export default function CartMobileTabletLayout({ height: 120, }; }; + return ( <> {t("cart.title")} @@ -225,11 +227,15 @@ export default function CartMobileTabletLayout({ {/* Car Plate*/} - {orderType === OrderType.Pickup && } + {(orderType === OrderType.Pickup || + orderType === OrderType.ScheduledOrder) && } {/* Estimate Time */} {(orderType === OrderType.Delivery || - orderType === OrderType.Pickup) && } + orderType === OrderType.Pickup || + orderType === OrderType.ScheduledOrder) && } + + {orderType === OrderType.ScheduledOrder && } {/* Collection Method */} {orderType === OrderType.Pickup && ( diff --git a/src/pages/cart/components/CouponCard.tsx b/src/pages/cart/components/CouponCard.tsx index 613a1b0..08b8e44 100644 --- a/src/pages/cart/components/CouponCard.tsx +++ b/src/pages/cart/components/CouponCard.tsx @@ -1,20 +1,19 @@ +import { Button, Form, Input, message } from "antd"; import { CouponBottomSheet } from "components/CustomBottomSheet/CouponBottomSheet.tsx"; -import { useAppSelector, useAppDispatch } from "redux/hooks.ts"; -import { selectCart, updateCoupon } from "features/order/orderSlice.ts"; -import { useState } from "react"; -import { message, Input, Button } from "antd"; -import { useTranslation } from "react-i18next"; +import { CouponDialog } from "components/CustomBottomSheet/CouponDialog.tsx"; +import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx"; +import DonateIcon from "components/Icons/cart/DonateIcon.tsx"; import ProInputCard from "components/ProInputCard/ProInputCard.tsx"; import ProText from "components/ProText.tsx"; -import { colors } from "ThemeConstants.ts"; -import DonateIcon from "components/Icons/cart/DonateIcon.tsx"; -import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx"; -import styles from "pages/cart/cart.module.css"; -import { CouponDialog } from "components/CustomBottomSheet/CouponDialog.tsx"; +import { selectCart, updateCoupon } from "features/order/orderSlice.ts"; import useBreakPoint from "hooks/useBreakPoint.ts"; +import styles from "pages/cart/cart.module.css"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "redux/hooks.ts"; +import { colors } from "ThemeConstants.ts"; -type Props = {}; -export default function CouponCard({}: Props) { +export default function CouponCard() { const { t } = useTranslation(); const dispatch = useAppDispatch(); const { coupon } = useAppSelector(selectCart); @@ -57,27 +56,34 @@ export default function CouponCard({}: Props) { } + dividerStyle={{ margin: "5px 0 0 0" }} > - - {t("cart.apply")} - - - } - /> + + + {t("cart.apply")} + + + } + /> + {isDesktop ? ( + + + setIsOpen(true)} + readOnly + value={scheduledDate} + style={{ + cursor: "pointer", + height: 50, + fontSize: 14, + borderRadius: 888, + }} + /> + + + + setIsOpen(false)} + onDateSelect={(date) => { + const formattedDate = `${date.month}/${date.day}/${date.year}`; + dispatch(updateScheduledDate(formattedDate)); + form.setFieldValue("date", formattedDate); + }} + initialDate={new Date(1990, 0, 1)} + /> + + ); +} diff --git a/src/pages/cart/components/TableNumberCard.tsx b/src/pages/cart/components/TableNumberCard.tsx index f70ac45..4f886e0 100644 --- a/src/pages/cart/components/TableNumberCard.tsx +++ b/src/pages/cart/components/TableNumberCard.tsx @@ -26,12 +26,15 @@ export default function TableNumberCard() { setIsSpecialRequestOpen(true)} - > - {t("cart.editNote")} - - } - /> + + + setIsSpecialRequestOpen(true)} + > + {t("cart.editNote")} + + } + /> + {isDesktop ? ( { // Blur any focused element when component mounts @@ -28,7 +28,7 @@ export default function CartPage() { // Enhanced desktop layout if (isDesktop) { return ( -
+ ); @@ -36,7 +36,7 @@ export default function CartPage() { // Mobile/Tablet Layout (existing code) return ( -
+ ); diff --git a/src/pages/menu/components/CategoriesList/CategoriesList.module.css b/src/pages/menu/components/CategoriesList/CategoriesList.module.css index ff6f92b..5f60310 100644 --- a/src/pages/menu/components/CategoriesList/CategoriesList.module.css +++ b/src/pages/menu/components/CategoriesList/CategoriesList.module.css @@ -119,7 +119,6 @@ transform-origin: top center; gap: 0.5rem; padding: 0.5rem 1rem 1rem 1rem; - margin-bottom: 0.5rem; user-select: none; } diff --git a/src/pages/menu/components/CategoriesList/CategoriesList.tsx b/src/pages/menu/components/CategoriesList/CategoriesList.tsx index 458be03..032c938 100644 --- a/src/pages/menu/components/CategoriesList/CategoriesList.tsx +++ b/src/pages/menu/components/CategoriesList/CategoriesList.tsx @@ -166,7 +166,6 @@ export function CategoriesList({ categories }: CategoriesListProps) { className={`${styles.categoriesContainer} ${ isCategoriesSticky ? styles.categoriesSticky : "" }`} - style={!isCategoriesSticky ? { paddingTop: "1rem" } : {}} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} diff --git a/src/pages/menu/menu.module.css b/src/pages/menu/menu.module.css index f993c8a..48fe83f 100644 --- a/src/pages/menu/menu.module.css +++ b/src/pages/menu/menu.module.css @@ -669,4 +669,4 @@ .navButton { display: none !important; } -} +} \ No newline at end of file diff --git a/src/pages/menu/page.tsx b/src/pages/menu/page.tsx index d437a19..5e177e5 100644 --- a/src/pages/menu/page.tsx +++ b/src/pages/menu/page.tsx @@ -101,7 +101,7 @@ function MenuPage() {