From 9d4621d0a493ece8da3f7c726aaea97a680803a4 Mon Sep 17 00:00:00 2001 From: Mohammed Al-yaseen Date: Sat, 18 Oct 2025 09:26:03 +0300 Subject: [PATCH] order page: integration --- src/assets/locals/ar.json | 3 +- src/assets/locals/en.json | 3 +- .../PaymentDetails/PaymentDetails.tsx | 13 +- .../PaymentMethods/PaymentMethods.tsx | 17 +- src/features/order/orderSlice.ts | 19 +- src/pages/checkout/hooks/types.ts | 228 ++++++++++++++++++ src/pages/checkout/hooks/useOrder.ts | 16 +- src/pages/checkout/page.tsx | 35 +-- src/pages/order/order.module.css | 8 +- src/pages/order/page.tsx | 43 ++-- src/redux/api/others.ts | 47 +++- src/routes/routes.tsx | 2 +- src/utils/constants.ts | 7 +- src/utils/types/appTypes.ts | 64 ----- 14 files changed, 376 insertions(+), 129 deletions(-) create mode 100644 src/pages/checkout/hooks/types.ts diff --git a/src/assets/locals/ar.json b/src/assets/locals/ar.json index f9ff1fe..73e9ee5 100644 --- a/src/assets/locals/ar.json +++ b/src/assets/locals/ar.json @@ -335,6 +335,7 @@ "cancelOrder": "إلغاء الطلب", "areYouSureYouWantToCancelThisOrder?": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟", "keepOrder": "الاحتفاظ بالطلب", - "thisActionCannotBeUndone": "هذا الإجراء لا يمكن التراجع عنه." + "thisActionCannotBeUndone": "هذا الإجراء لا يمكن التراجع عنه.", + "createOrderFailed": "فشل إنشاء الطلب" } } diff --git a/src/assets/locals/en.json b/src/assets/locals/en.json index ae1934e..11771e3 100644 --- a/src/assets/locals/en.json +++ b/src/assets/locals/en.json @@ -346,6 +346,7 @@ "cancelOrder": "Cancel Order", "areYouSureYouWantToCancelThisOrder?": "Are you sure you want to cancel this order?", "keepOrder": "Keep Order", - "thisActionCannotBeUndone": "This action cannot be undone." + "thisActionCannotBeUndone": "This action cannot be undone.", + "createOrderFailed": "Create Order Failed" } } diff --git a/src/components/PaymentDetails/PaymentDetails.tsx b/src/components/PaymentDetails/PaymentDetails.tsx index ebdb93a..6e1febf 100644 --- a/src/components/PaymentDetails/PaymentDetails.tsx +++ b/src/components/PaymentDetails/PaymentDetails.tsx @@ -1,20 +1,13 @@ import { Card, Divider, Space } from "antd"; import ArabicPrice from "components/ArabicPrice"; -import { selectCartTotal } from "features/order/orderSlice"; +import { Order } from "pages/checkout/hooks/types"; import { useTranslation } from "react-i18next"; -import { useAppSelector } from "redux/hooks"; import ProText from "../ProText"; import ProTitle from "../ProTitle"; import styles from "./PaymentDetails.module.css"; -export default function PaymentDetails() { +export default function PaymentDetails({ order }: { order?: Order }) { const { t } = useTranslation(); - const total = useAppSelector(selectCartTotal); - const { isRTL } = useAppSelector((state) => state.locale); - - const subtotal = total; - const tax = subtotal * 0.1; // 10% tax - const finalTotal = subtotal + tax; return ( <> @@ -26,7 +19,7 @@ export default function PaymentDetails() {
{t("cart.totalAmount")}
diff --git a/src/components/PaymentMethods/PaymentMethods.tsx b/src/components/PaymentMethods/PaymentMethods.tsx index 5e0dfed..e321c9c 100644 --- a/src/components/PaymentMethods/PaymentMethods.tsx +++ b/src/components/PaymentMethods/PaymentMethods.tsx @@ -3,17 +3,18 @@ import { Group } from "antd/es/radio"; import ArabicPrice from "components/ArabicPrice"; import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon"; import ProText from "components/ProText"; +import { selectCart, updatePaymentMethod } from "features/order/orderSlice"; import { useTranslation } from "react-i18next"; +import { useAppDispatch, useAppSelector } from "redux/hooks"; import { colors, ProGray1 } from "../../ThemeConstants"; import ProInputCard from "../ProInputCard/ProInputCard"; import styles from "./PaymentMethods.module.css"; -interface PaymentMethodsProps { - onPaymentSelect?: () => void; -} -const PaymentMethods = ({ onPaymentSelect, ...props }: PaymentMethodsProps) => { +const PaymentMethods = () => { const { t } = useTranslation(); + const { paymentMethod } = useAppSelector(selectCart); + const dispatch = useAppDispatch(); const options: { label: string; @@ -48,6 +49,10 @@ const PaymentMethods = ({ onPaymentSelect, ...props }: PaymentMethodsProps) => { }, ]; + const onPaymentSelect = (value: string) => { + dispatch(updatePaymentMethod(value)); + }; + return ( { rules={[ { required: true, message: t("checkout.pleaseSelectPaymentMethod") }, ]} + initialValue={paymentMethod} > @@ -71,7 +76,7 @@ const PaymentMethods = ({ onPaymentSelect, ...props }: PaymentMethodsProps) => { onPaymentSelect(option.value)} style={{ height: 50, borderRadius: 888, diff --git a/src/features/order/orderSlice.ts b/src/features/order/orderSlice.ts index ea9e502..3db9a16 100644 --- a/src/features/order/orderSlice.ts +++ b/src/features/order/orderSlice.ts @@ -49,10 +49,11 @@ interface CartState { estimateTimeTime: string; collectionMethod: string; phone: string; + paymentMethod: string; } // localStorage keys -const CART_STORAGE_KEYS = { +export const CART_STORAGE_KEYS = { ITEMS: 'fascano_cart_items', SPECIAL_REQUEST: 'fascano_special_request', COUPON: 'fascano_coupon', @@ -67,6 +68,7 @@ const CART_STORAGE_KEYS = { ESTIMATE_TIME_TIME: 'fascano_estimate_time_time', COLLECTION_METHOD: 'fascano_collection_method', PHONE: 'fascano_phone', + PAYMENT_METHOD: 'fascano_payment_method', } as const; // Utility functions for localStorage @@ -98,6 +100,7 @@ const initialState: CartState = { estimateTimeTime: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_TIME_TIME, ""), collectionMethod: getFromLocalStorage(CART_STORAGE_KEYS.COLLECTION_METHOD, ""), phone: getFromLocalStorage(CART_STORAGE_KEYS.PHONE, ""), + paymentMethod: getFromLocalStorage(CART_STORAGE_KEYS.PAYMENT_METHOD, ""), }; const orderSlice = createSlice({ @@ -146,6 +149,7 @@ const orderSlice = createSlice({ clearCart(state) { state.items = []; state.specialRequest = ""; + state.phone = ""; state.coupon = ""; state.tip = ""; state.tables = []; @@ -157,7 +161,7 @@ const orderSlice = createSlice({ state.estimateTimeDate = new Date(); state.estimateTimeTime = ""; state.collectionMethod = ""; - + state.paymentMethod = ""; // Clear all cart data from localStorage if (typeof window !== 'undefined') { Object.values(CART_STORAGE_KEYS).forEach(key => { @@ -198,7 +202,7 @@ const orderSlice = createSlice({ } }, updateTables(state, action: PayloadAction) { - state.tables = [...state.tables, ...action.payload]; + state.tables = action.payload; // Sync to localStorage if (typeof window !== 'undefined') { @@ -284,6 +288,14 @@ const orderSlice = createSlice({ localStorage.setItem(CART_STORAGE_KEYS.PHONE, JSON.stringify(state.phone)); } }, + updatePaymentMethod(state, action: PayloadAction) { + state.paymentMethod = action.payload; + + // Sync to localStorage + if (typeof window !== 'undefined') { + localStorage.setItem(CART_STORAGE_KEYS.PAYMENT_METHOD, JSON.stringify(state.paymentMethod)); + } + }, }, }); @@ -307,6 +319,7 @@ export const { updateEstimateTime, updateCollectionMethod, updatePhone, + updatePaymentMethod, reset, } = orderSlice.actions; diff --git a/src/pages/checkout/hooks/types.ts b/src/pages/checkout/hooks/types.ts new file mode 100644 index 0000000..30efe65 --- /dev/null +++ b/src/pages/checkout/hooks/types.ts @@ -0,0 +1,228 @@ +import { Variant } from "pages/orders/types" +import { Extra3 } from "utils/types/appTypes" + +export interface OrderDetails { + orderItems: OrderItem[] + order: Order + status: Status[] + laststatus: Status2[] + restaurant: string + restaurantAR: string + restaurantID: number + global_currency: string + local_currency: string + address: string + phone: string + restaurant_iimage: string + itemsImagePrefixOld: string + itemsImagePrefix: string + } + + export interface OrderItem { + id: number + is_loyalty_used: number + no_of_stamps_give: number + isHasLoyalty: boolean + is_vat_disabled: number + name: string + price: number + qty: number + variant_price: string + image: string + imageName: string + variantName: string + variantLocalName?: string + extras: any[] + itemline: string + itemlineAR: string + itemlineAREN: string + extrasgroups: any[] + itemComment: string + variant?: Variant + itemExtras: any[] + AvaiilableVariantExtras: Extra3[] + isPrinted: number + category_id: number + pos_order_id: string + updated_at: string + created_at: string + old_qty: number + new_qty: number + last_printed_qty: number + deleted_qty: number + discount_value: any + discount_type_id: any + original_price: number + pricing_method: string + is_already_paid: number + hash_item: string + } + + export interface Order { + id: number + pos_order_id: string + created_at: string + updated_at: string + table: string + phone: string + user_name: string + restaurant_name: string + lat: string + lng: string + restaurant_icon: string + location: any + status: string + status_id: number + delivery_method: number + orderItems: OrderItem2[] + discount: string + vat: number + total_price: number + comment: string + pickup_comments: any + car_plate: string + pickup_time: any + pickup_date: any + delivery_pickup_interval: any + office_no: any + room_no: any + time_to_prepare: any + last_status_id: number + currency: string + gift_id: any + is_loyalty_used: number + payment_status: string + created_by: string + split_order_group_id: any + split_sequence: any + is_split_order: number + split_at: any + split_by_user_id: any + } + + export interface OrderItem2 { + id: number + is_loyalty_used: number + no_of_stamps_give: number + isHasLoyalty: boolean + is_vat_disabled: number + name: string + price: number + qty: number + variant_price: string + image: string + imageName: string + variantName: string + variantLocalName?: string + extras: any[] + itemline: string + itemlineAR: string + itemlineAREN: string + extrasgroups: any[] + itemComment: string + variant?: Variant2 + itemExtras: any[] + AvaiilableVariantExtras: AvaiilableVariantExtra2[] + isPrinted: number + category_id: number + pos_order_id: string + updated_at: string + created_at: string + old_qty: number + new_qty: number + last_printed_qty: number + deleted_qty: number + discount_value: any + discount_type_id: any + original_price: number + pricing_method: string + is_already_paid: number + hash_item: string + } + + export interface Status { + id: number + name: string + alias: string + pivot: Pivot5 + } + + export interface Pivot5 { + order_id: number + status_id: number + user_id: number + created_at: string + comment: string + } + + export interface Status2 { + id: number + name: string + alias: string + pivot: Pivot6 + } + + export interface Pivot6 { + order_id: number + status_id: number + user_id: number + created_at: string + comment: string + } + + export interface Variant2 { + id: number + price: number + options: string + local_name: any + optionsArray: OptionsArray2[] + OptionsList: string + extras: Extra2[] + } + + export interface OptionsArray2 { + id: number + name: string + } + + export interface Extra2 { + id: number + item_id: number + price: number + name: string + nameAR: string + created_at: string + updated_at: string + deleted_at: any + extra_for_all_variants: number + is_custome: number + is_available: number + modifier_id: any + pivot: Pivot3 + } + + export interface Pivot3 { + variant_id: number + extra_id: number + } + + export interface AvaiilableVariantExtra2 { + id: number + item_id: number + price: number + name: string + nameAR: string + created_at: string + updated_at: string + deleted_at: any + extra_for_all_variants: number + is_custome: number + is_available: number + modifier_id: any + pivot: Pivot4 + } + + export interface Pivot4 { + variant_id: number + extra_id: number + } \ No newline at end of file diff --git a/src/pages/checkout/hooks/useOrder.ts b/src/pages/checkout/hooks/useOrder.ts index e05305e..80fc054 100644 --- a/src/pages/checkout/hooks/useOrder.ts +++ b/src/pages/checkout/hooks/useOrder.ts @@ -1,5 +1,7 @@ +import { message } from "antd"; import { clearCart, selectCart } from "features/order/orderSlice"; import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; import { useNavigate, useParams } from "react-router-dom"; import { useCreateOrderMutation } from "redux/api/others"; import { useAppDispatch, useAppSelector } from "redux/hooks"; @@ -8,6 +10,7 @@ import { Customer } from "../../otp/types"; export default function useOrder() { const dispatch = useAppDispatch(); const router = useNavigate(); + const { t } = useTranslation(); const { id } = useParams(); const restaurantID = localStorage.getItem("restaurantID"); const { mobilenumber, user_uuid } = JSON.parse( @@ -59,11 +62,15 @@ export default function useOrder() { useWallet: 0, tip, }) - .then(() => { - dispatch(clearCart()); - router(`/${id}/order`); + .then((res: any) => { + if (res.error) + message.error(res.error.data.message || t("order.createOrderFailed")); + else { + dispatch(clearCart()); + router(`/${id}/order/${res.data.result.orderID}`); + } }) - .catch((error) => { + .catch((error: any) => { console.error("Create Order failed:", error); }); }, [ @@ -80,6 +87,7 @@ export default function useOrder() { user_uuid, estimateTime, tip, + t, dispatch, router, ]); diff --git a/src/pages/checkout/page.tsx b/src/pages/checkout/page.tsx index d0b05f4..98a5dce 100644 --- a/src/pages/checkout/page.tsx +++ b/src/pages/checkout/page.tsx @@ -2,7 +2,9 @@ import { Form } from "antd"; import OrderSummary from "components/OrderSummary/OrderSummary"; import PaymentMethods from "components/PaymentMethods/PaymentMethods"; import ProHeader from "components/ProHeader/ProHeader"; +import { selectCart } from "features/order/orderSlice"; import { useTranslation } from "react-i18next"; +import { useAppSelector } from "redux/hooks"; import styles from "../address/address.module.css"; import { AddressSummary } from "./components/AddressSummary"; import BriefMenu from "./components/BriefMenu"; @@ -15,23 +17,28 @@ import PhoneCard from "./components/phoneCard"; export default function CheckoutPage() { const { t } = useTranslation(); const [form] = Form.useForm(); - + const { phone } = useAppSelector(selectCart); return ( <> -
- {t("checkout.title")} -
- - - - - - - - -
+ + {t("checkout.title")} +
+ + + + + + + + +
- + ); diff --git a/src/pages/order/order.module.css b/src/pages/order/order.module.css index e81c889..b88f8c1 100644 --- a/src/pages/order/order.module.css +++ b/src/pages/order/order.module.css @@ -2,6 +2,12 @@ padding: 16px !important; } +.profileImage { + border-radius: 50%; + width: 50px; + height: 50px; +} + /* Enhanced responsive order summary */ @media (min-width: 769px) and (max-width: 1024px) { .orderSummary { @@ -9,7 +15,7 @@ } } -.fascanoIcon { + .fascanoIcon { position: relative; top: 3px; } diff --git a/src/pages/order/page.tsx b/src/pages/order/page.tsx index 5599aca..9a5e82f 100644 --- a/src/pages/order/page.tsx +++ b/src/pages/order/page.tsx @@ -1,7 +1,6 @@ -import { Button, Card, Divider } from "antd"; +import { Button, Card, Divider, Image } from "antd"; import Ads2 from "components/Ads/Ads2"; import { CancelOrderBottomSheet } from "components/CustomBottomSheet/CancelOrderBottomSheet"; -import Fascano50X50Icon from "components/Icons/fascano/Fascano50X50Icon"; import LocationIcon from "components/Icons/LocationIcon"; import InvoiceIcon from "components/Icons/order/InvoiceIcon"; import TimeIcon from "components/Icons/order/TimeIcon"; @@ -12,15 +11,28 @@ import ProInputCard from "components/ProInputCard/ProInputCard"; import ProText from "components/ProText"; import ProTitle from "components/ProTitle"; import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import { useGetOrderDetailsQuery } from "redux/api/others"; +import { useAppSelector } from "redux/hooks"; import Stepper from "./components/Stepper"; import styles from "./order.module.css"; export default function OrderPage() { const { t } = useTranslation(); + const { orderId } = useParams(); + const { isRTL } = useAppSelector((state) => state.locale); - // const subtotal = getTotal(); - // const tax = subtotal * 0.1; // 10% tax - // const total = subtotal + tax; + const { data: orderDetails } = useGetOrderDetailsQuery( + { + orderID: orderId || "", + restaurantID: localStorage.getItem("restaurantID") || "", + }, + { + skip: !orderId, + }, + ); + + console.log(orderDetails); return ( <> @@ -54,7 +66,12 @@ export default function OrderPage() { backgroundColor: "rgba(255, 183, 0, 0.08)", }} > - +
@@ -63,7 +80,7 @@ export default function OrderPage() {
{" "} - {t("order.muscat")} + {isRTL ? orderDetails?.restaurantAR : orderDetails?.restaurant}
@@ -84,11 +101,11 @@ export default function OrderPage() {
- #A54363 + #{orderDetails?.order.id} - ordered :- Today - 13:55 PM + ordered :- Today - {orderDetails?.status[0]?.pivot?.created_at.split(" ")[1]} PM
@@ -117,11 +134,7 @@ export default function OrderPage() {
- {[ - { id: 1, name: "Lazord Grill - half kilo" }, - { id: 2, name: "Lazord Grill - half kilo" }, - { id: 3, name: "Lazord Grill - half kilo" }, - ].map((item, index) => ( + {orderDetails?.orderItems.map((item, index) => (
- +
diff --git a/src/redux/api/others.ts b/src/redux/api/others.ts index 9007847..5825e51 100644 --- a/src/redux/api/others.ts +++ b/src/redux/api/others.ts @@ -1,10 +1,13 @@ import { - CREATE_ORDER_URL, - ORDERS_URL, - PRODUCTS_AND_CATEGORIES_URL, - RESTAURANT_DETAILS_URL, + CREATE_ORDER_URL, + ORDER_DETAILS_URL, + ORDERS_URL, + PRODUCTS_AND_CATEGORIES_URL, + RESTAURANT_DETAILS_URL, + TABLES_URL, } from "utils/constants"; +import { OrderDetails } from "pages/checkout/hooks/types"; import menuParser from "pages/menu/helper"; import { RestaurantDetails } from "utils/types/appTypes"; import { baseApi } from "./apiSlice"; @@ -43,6 +46,40 @@ export const branchApi = baseApi.injectEndpoints({ }), invalidatesTags: ["Orders"], }), + getTables: builder.query< + any, + { restaurantID: string; tableType: string } + >({ + query: ({ + restaurantID, + tableType, + }: { + restaurantID: string; + tableType: string; + }) => ({ + url: TABLES_URL, + method: "POST", + body: { + restaurantID, + tableType, + }, + }), + transformResponse: (response: any) => { + return response.result.data; + }, + }), + getOrderDetails: builder.query({ + query: ({ orderID, restaurantID }: { orderID: string; restaurantID: string }) => ({ + url: `${ORDER_DETAILS_URL}/${orderID}`, + method: "POST", + body: { + restaurantID, + }, + }), + transformResponse: (response: any) => { + return response.result; + }, + }), }), }); export const { @@ -50,4 +87,6 @@ export const { useGetMenuQuery, useCreateOrderMutation, useGetOrdersQuery, + useGetTablesQuery, + useGetOrderDetailsQuery, } = branchApi; diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index c385043..def79dc 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -142,7 +142,7 @@ export const router = createHashRouter([ }, { - path: "/:id/order", + path: "/:id/order/:orderId", element: } />, errorElement: , }, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 5fb51f6..eb5990e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -95,12 +95,9 @@ export const RESTAURANT_DETAILS_URL = `${BASE_URL}restaurant/selectLanguage/`; export const PRODUCTS_AND_CATEGORIES_URL = `${BASE_URL}getRestaurantItems/`; export const PRODUCT_DETAILS_URL = `${BASE_URL}getOptionsForItem/`; export const ORDERS_URL = `${BASE_URL}customer_orders`; +export const ORDER_DETAILS_URL = `${BASE_URL}restaurant/getOneOrderForWebmenu`; export const CREATE_ORDER_URL = `${BASE_URL}create_order_webmenu`; +export const TABLES_URL = `${BASE_URL}restaurant/getTables`; export const LOGIN_URL = `${API_BASE_URL}login`; export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`; export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`; -export const USERS_DATA_URL = `${API_BASE_URL}users`; -export const TABLE_HEADERS = `${API_BASE_URL}tableheader`; -export const PERMISSIONS_DATA_URL = `${API_BASE_URL}permissions`; -export const SINGED_USER_INFO_URL = `${API_BASE_URL}users/authenticated`; -export const ROLES_DATA_URL = `${API_BASE_URL}roles`; diff --git a/src/utils/types/appTypes.ts b/src/utils/types/appTypes.ts index 1bc92da..b6bc477 100644 --- a/src/utils/types/appTypes.ts +++ b/src/utils/types/appTypes.ts @@ -1,9 +1,5 @@ // zero value may be considered an undefined ... donn't use in enums import { ColumnType } from "antd/es/table"; -import { PermissionType } from "modules/system/rolesManagement/types"; -import { TaxType } from "modules/system/taxes/types"; -import { QrCode } from "pages/pos/orders/components/order-panel/types"; -import { ItemType, OrderType } from "pages/pos/orders/types"; export enum AlignType { "left" = "left", @@ -148,25 +144,6 @@ export type LoginUserType = { password?: string; }; -export type UserType = { - id?: number; - first_name?: string; - last_name?: string; - password?: string; - phone_number?: string; - salary?: any; - employment_date?: any; - email?: string; - username?: string; - personal_image?: any; - identity_card_front?: any; - identity_card_back?: any; - driving_license_front?: any; - driving_license_back?: any; - roles?: RolesType[]; - menus: string[]; -}; - export enum Mode { Edit = 1, New = 2, @@ -297,33 +274,6 @@ export enum TableStatus { requestPayment = "PENDING_INVOICE", } -export interface ReservationType { - id: number; - start_datetime: string; - end_datetime: any; - seats_reserved: number; - orders: OrderType[]; - invoice: InvoiceType; -} - -export interface InvoiceType { - id: number; - number: number; - uuid: string; - sub_total: number; - grand_total: number; - discount_amount: number; - taxes_total: number; - print_times: number; - start_datetime: string; - end_datetime: string; - printed_at: string; - remaining_amount: number; - qr_code: QrCode; - taxes: TaxType[]; - items: ItemType[]; -} - export interface RegisterType { id: number; opening_balance: number; @@ -339,12 +289,6 @@ export interface SessionPayload { total_expenses?: number; } -export interface RolesType { - id: number; - name: string; - permissions: PermissionType[]; -} - export enum Roles { SuperUser = "super-admin", Admin = "admin", @@ -399,14 +343,6 @@ export interface User { role: "admin" | "user"; } -export interface Order { - id: string; - userId: string; - items: CartItem[]; - total: number; - status: "pending" | "confirmed" | "completed" | "cancelled"; - createdAt: string; -} export type Locale = "en" | "ar"; export type Theme = "light" | "dark";