refactor calculation code

- implement fee
- centralize the code
This commit is contained in:
2025-10-27 08:08:22 +03:00
parent 251e7a9369
commit cd49a3756f
13 changed files with 395 additions and 284 deletions

View File

@@ -159,7 +159,7 @@
"browseMenu": "تصفح القائمة", "browseMenu": "تصفح القائمة",
"paymentSummary": "ملخص الدفعة", "paymentSummary": "ملخص الدفعة",
"subtotal": "المجموع الفرعي", "subtotal": "المجموع الفرعي",
"tax": "الضريبة (10%)", "tax": "الضريبة",
"remove": "حذف", "remove": "حذف",
"proceedToCheckout": "المتابعة إلى الدفع", "proceedToCheckout": "المتابعة إلى الدفع",
"totalAmount": "المجموع الكلي", "totalAmount": "المجموع الكلي",
@@ -234,10 +234,12 @@
"useLoyaltyPoints": "استخدام نقاط الولاء", "useLoyaltyPoints": "استخدام نقاط الولاء",
"noLoyaltyItemsInCart": "لا توجد عناصر ولاء في سلة المشتريات", "noLoyaltyItemsInCart": "لا توجد عناصر ولاء في سلة المشتريات",
"pleaseAddLoyaltyItems": "يرجى إضافة عناصر ولاء إلى سلة المشتريات لاستخدام نقاط الولاء", "pleaseAddLoyaltyItems": "يرجى إضافة عناصر ولاء إلى سلة المشتريات لاستخدام نقاط الولاء",
"loyaltyDiscountApplied": "تم تطبيق خصم الولاء: {{itemName}} (خصم {{amount}})" "loyaltyDiscountApplied": "تم تطبيق خصم الولاء: {{itemName}} (خصم {{amount}})",
"deliveryFee": "رسوم التوصيل"
}, },
"checkout": { "checkout": {
"title": "الدفع", "title": "الدفع",
"cash": "كاش",
"creditDebitCard": "بطاقة ائتمان/ائتمان", "creditDebitCard": "بطاقة ائتمان/ائتمان",
"differentCard": "بطاقة اخرى", "differentCard": "بطاقة اخرى",
"fascanoWallet": "محفظة فاسكانو", "fascanoWallet": "محفظة فاسكانو",

View File

@@ -171,7 +171,7 @@
"browseMenu": "Browse Menu", "browseMenu": "Browse Menu",
"paymentSummary": "Payment Summary", "paymentSummary": "Payment Summary",
"subtotal": "Subtotal", "subtotal": "Subtotal",
"tax": "Tax (10%)", "tax": "Tax",
"remove": "Remove", "remove": "Remove",
"proceedToCheckout": "Proceed to Checkout", "proceedToCheckout": "Proceed to Checkout",
"basketTotal": "Basket Total", "basketTotal": "Basket Total",
@@ -244,10 +244,12 @@
"useLoyaltyPoints": "Use Loyalty Points", "useLoyaltyPoints": "Use Loyalty Points",
"noLoyaltyItemsInCart": "No loyalty items found in your cart", "noLoyaltyItemsInCart": "No loyalty items found in your cart",
"pleaseAddLoyaltyItems": "Please add loyalty items to your cart to use loyalty points", "pleaseAddLoyaltyItems": "Please add loyalty items to your cart to use loyalty points",
"loyaltyDiscountApplied": "Loyalty discount applied: {{itemName}} ({{amount}} off)" "loyaltyDiscountApplied": "Loyalty discount applied: {{itemName}} ({{amount}} off)",
"deliveryFee": "Delivery Fee"
}, },
"checkout": { "checkout": {
"title": "Checkout", "title": "Checkout",
"cash": "Cash",
"creditDebitCard": "Credit/Debit Card", "creditDebitCard": "Credit/Debit Card",
"differentCard": "Different Card", "differentCard": "Different Card",
"fascanoWallet": "Fascano Wallet", "fascanoWallet": "Fascano Wallet",

View File

@@ -9,6 +9,7 @@ interface ArabicPriceProps {
strong?: boolean; strong?: boolean;
type?: "secondary" | "success" | "warning" | "danger"; type?: "secondary" | "success" | "warning" | "danger";
className?: string; className?: string;
hideCurrency?: boolean;
} }
const ArabicPrice: React.FC<ArabicPriceProps> = ({ const ArabicPrice: React.FC<ArabicPriceProps> = ({
@@ -17,6 +18,7 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
strong = false, strong = false,
type, type,
className, className,
hideCurrency = false,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
@@ -32,12 +34,12 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
style={{ style={{
display: "inline-flex", display: "inline-flex",
alignItems: "baseline", alignItems: "baseline",
gap: "0.1em", gap: "0.2em",
lineHeight: 1, lineHeight: 1,
...style, ...style,
}} }}
> >
{isRTL ? ( {isRTL && !hideCurrency ? (
<> <>
<span <span
style={{ style={{
@@ -47,7 +49,6 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
> >
{formattedPrice} {formattedPrice}
</span> </span>
<span style={{margin: "0 0.1em"}} />
<span <span
style={{ style={{
fontSize: "0.9em", fontSize: "0.9em",
@@ -60,7 +61,7 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
{t("common.omanCurrency")} {t("common.omanCurrency")}
</span> </span>
</> </>
) : ( ) : !hideCurrency ? (
<> <>
<span <span
style={{ style={{
@@ -82,6 +83,8 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
{t("common.omanCurrency")} {t("common.omanCurrency")}
</span> </span>
</> </>
) : (
<>{formattedPrice}</>
)} )}
</ProText> </ProText>
); );

View File

@@ -3,11 +3,13 @@ import ArabicPrice from "components/ArabicPrice";
import { import {
selectCart, selectCart,
selectCartTotal, selectCartTotal,
selectCartTotalWithLoyaltyDiscount, selectGrandTotal,
selectHighestPricedLoyaltyItem, selectHighestPricedLoyaltyItem,
selectLoyaltyValidation, selectLoyaltyValidation,
selectTaxAmount,
updateUseLoyaltyPoints, updateUseLoyaltyPoints,
} from "features/order/orderSlice"; } from "features/order/orderSlice";
import { OrderType } from "pages/checkout/hooks/types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useGetRestaurantDetailsQuery } from "redux/api/others"; import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
@@ -18,19 +20,15 @@ import styles from "./OrderSummary.module.css";
export default function OrderSummary() { export default function OrderSummary() {
const { t } = useTranslation(); const { t } = useTranslation();
const { useLoyaltyPoints } = useAppSelector(selectCart); const { useLoyaltyPoints } = useAppSelector(selectCart);
const { data: restaurant } = useGetRestaurantDetailsQuery("595");
const { orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const subtotal = useAppSelector(selectCartTotal); const subtotal = useAppSelector(selectCartTotal);
const subtotalWithLoyaltyDiscount = useAppSelector(
selectCartTotalWithLoyaltyDiscount,
);
const loyaltyValidation = useAppSelector(selectLoyaltyValidation); const loyaltyValidation = useAppSelector(selectLoyaltyValidation);
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem); const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const taxAmount = useAppSelector(selectTaxAmount);
const grandTotal = useAppSelector(selectGrandTotal);
const tax = subtotalWithLoyaltyDiscount * 0.1; // 10% tax on discounted amount
const total = subtotalWithLoyaltyDiscount + tax;
const loyaltyDiscountAmount = subtotal - subtotalWithLoyaltyDiscount;
const { data: restaurant } = useGetRestaurantDetailsQuery("595");
const isHasLoyaltyGift = const isHasLoyaltyGift =
(restaurant?.loyalty_stamps ?? 0) - (restaurant?.loyalty_stamps ?? 0) -
(restaurant?.customer_loyalty_points ?? 0) <= (restaurant?.customer_loyalty_points ?? 0) <=
@@ -46,20 +44,24 @@ export default function OrderSummary() {
<ProText type="secondary">{t("cart.basketTotal")}</ProText> <ProText type="secondary">{t("cart.basketTotal")}</ProText>
<ArabicPrice price={subtotal} /> <ArabicPrice price={subtotal} />
</div> </div>
{orderType != OrderType.DineIn && <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.deliveryFee")}</ProText>
<ArabicPrice price={Number(restaurant?.delivery_fees || 0)} />
</div>}
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.discount")}</ProText> <ProText type="secondary">{t("cart.discount")}</ProText>
<ArabicPrice price={loyaltyDiscountAmount} /> <ArabicPrice price={highestLoyaltyItem?.price || 0} />
</div> </div>
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.riderTip")}</ProText> <ProText type="secondary">{t("cart.tax")}</ProText>
<ArabicPrice price={tax} /> <ArabicPrice price={taxAmount || 0} />
</div> </div>
<Divider className={styles.summaryDivider} /> <Divider className={styles.summaryDivider} />
<div className={`${styles.summaryRow} ${styles.totalRow}`}> <div className={`${styles.summaryRow} ${styles.totalRow}`}>
<ProText strong type="secondary"> <ProText strong type="secondary">
{t("cart.totalAmount")} {t("cart.totalAmount")}
</ProText> </ProText>
<ArabicPrice price={total} strong /> <ArabicPrice price={grandTotal} />
</div> </div>
</Space> </Space>
@@ -88,7 +90,7 @@ export default function OrderSummary() {
<div style={{ marginTop: 8, color: "green", fontSize: "12px" }}> <div style={{ marginTop: 8, color: "green", fontSize: "12px" }}>
{t("cart.loyaltyDiscountApplied", { {t("cart.loyaltyDiscountApplied", {
itemName: highestLoyaltyItem.name, itemName: highestLoyaltyItem.name,
amount: Math.round(loyaltyDiscountAmount).toFixed(2), amount: Math.round(highestLoyaltyItem.price || 0).toFixed(2),
})} })}
</div> </div>
)} )}

View File

@@ -3,7 +3,11 @@ import { Group } from "antd/es/radio";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon"; import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon";
import ProText from "components/ProText"; import ProText from "components/ProText";
import { selectCart, updatePaymentMethod } from "features/order/orderSlice"; import {
selectCart,
selectGrandTotal,
updatePaymentMethod,
} from "features/order/orderSlice";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
import { colors, ProGray1 } from "../../ThemeConstants"; import { colors, ProGray1 } from "../../ThemeConstants";
@@ -14,6 +18,7 @@ const PaymentMethods = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { paymentMethod, orderType } = useAppSelector(selectCart); const { paymentMethod, orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const grandTotal = useAppSelector(selectGrandTotal);
const options: { const options: {
label: string; label: string;
@@ -21,26 +26,35 @@ const PaymentMethods = () => {
price?: string; price?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
style?: React.CSSProperties; style?: React.CSSProperties;
hideCurrency?: boolean;
}[] = [ }[] = [
...(orderType !== "gift" ...(orderType !== "gift"
? [ ? [
{ {
label: t("checkout.creditDebitCard"), label: t("checkout.cash"),
value: "creditDebitCard", value: "cash",
price: t("checkout.expiresIn") + ":12/26", price: grandTotal.toString(),
style: {
color: colors.primary,
},
}, },
] ]
: []), : []),
{
label: t("checkout.creditDebitCard"),
value: "creditDebitCard",
price: t("checkout.expiresIn") + ":12/26",
hideCurrency: true,
},
{ {
label: t("checkout.differentCard"), label: t("checkout.differentCard"),
value: "differentCard", value: "differentCard",
icon: ( icon: (
<div className={styles.differentCardIcon}> <div className={styles.differentCardIcon}>
{" "}
<DifferentCardIcon /> <DifferentCardIcon />
</div> </div>
), ),
hideCurrency: true,
}, },
{ {
label: t("checkout.fascanoWallet"), label: t("checkout.fascanoWallet"),
@@ -108,6 +122,7 @@ const PaymentMethods = () => {
{!option.icon ? ( {!option.icon ? (
<ArabicPrice <ArabicPrice
price={option.price || 0} price={option.price || 0}
hideCurrency={option.hideCurrency}
style={{ style={{
fontSize: "0.75rem", fontSize: "0.75rem",
color: ProGray1, color: ProGray1,

View File

@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { OrderType } from "pages/checkout/hooks/types";
import { RootState } from "redux/store"; import { RootState } from "redux/store";
import { CartItem } from "utils/types/appTypes"; import { CartItem, RestaurantDetails, Tax } from "utils/types/appTypes";
interface LocationData { interface LocationData {
lat: number; lat: number;
@@ -35,8 +36,9 @@ export interface GiftDetailsType {
} }
interface CartState { interface CartState {
restaurant: Partial<RestaurantDetails>;
items: CartItem[]; items: CartItem[];
tmp: any; tmp: unknown;
specialRequest: string; specialRequest: string;
location: LocationData | null; location: LocationData | null;
roomDetails: RoomDetailsType | null; roomDetails: RoomDetailsType | null;
@@ -76,6 +78,7 @@ export const CART_STORAGE_KEYS = {
ORDER_TYPE: "fascano_order_type", ORDER_TYPE: "fascano_order_type",
USE_LOYALTY_POINTS: "fascano_use_loyalty_points", USE_LOYALTY_POINTS: "fascano_use_loyalty_points",
LOYALTY_VALIDATION_ERROR: "fascano_loyalty_validation_error", LOYALTY_VALIDATION_ERROR: "fascano_loyalty_validation_error",
RESTAURANT: "fascano_restaurant",
} as const; } as const;
// Utility functions for localStorage // Utility functions for localStorage
@@ -133,6 +136,7 @@ const initialState: CartState = {
CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR, CART_STORAGE_KEYS.LOYALTY_VALIDATION_ERROR,
null, null,
), ),
restaurant: getFromLocalStorage(CART_STORAGE_KEYS.RESTAURANT, { taxes: [] }),
}; };
const orderSlice = createSlice({ const orderSlice = createSlice({
@@ -142,6 +146,15 @@ const orderSlice = createSlice({
reset() { reset() {
return initialState; return initialState;
}, },
updateRestaurant(state, action: PayloadAction<Partial<RestaurantDetails>>) {
state.restaurant = action.payload;
if (typeof window !== "undefined") {
localStorage.setItem(
CART_STORAGE_KEYS.RESTAURANT,
JSON.stringify(state.restaurant),
);
}
},
addItem( addItem(
state, state,
action: PayloadAction<{ action: PayloadAction<{
@@ -520,8 +533,21 @@ export const {
validateLoyaltyPoints, validateLoyaltyPoints,
clearLoyaltyValidationError, clearLoyaltyValidationError,
reset, reset,
updateRestaurant,
} = orderSlice.actions; } = orderSlice.actions;
// Tax calculation helper functions
const calculateTaxAmount = (amount: number, tax: Tax): number => {
const percentage = parseFloat(tax.percentage);
return (percentage * amount) / 100;
};
const calculateTotalTax = (subtotal: number, taxes: Tax[]): number => {
return taxes
.filter((tax) => tax.is_active === 1)
.reduce((total, tax) => total + calculateTaxAmount(subtotal, tax), 0);
};
// Selectors // Selectors
export const selectCart = (state: RootState) => state.order; export const selectCart = (state: RootState) => state.order;
export const selectCartItems = (state: RootState) => state.order.items; export const selectCartItems = (state: RootState) => state.order.items;
@@ -549,18 +575,6 @@ export const selectHighestPricedLoyaltyItem = (state: RootState) => {
); );
}; };
export const selectCartTotalWithLoyaltyDiscount = (state: RootState) => {
const total = selectCartTotal(state);
const useLoyaltyPoints = state.order.useLoyaltyPoints;
const highestLoyaltyItem = selectHighestPricedLoyaltyItem(state);
if (useLoyaltyPoints && highestLoyaltyItem) {
return total - highestLoyaltyItem.price;
}
return total;
};
export const selectLoyaltyValidation = (state: RootState) => { export const selectLoyaltyValidation = (state: RootState) => {
const useLoyaltyPoints = state.order.useLoyaltyPoints; const useLoyaltyPoints = state.order.useLoyaltyPoints;
const loyaltyItems = selectLoyaltyItems(state); const loyaltyItems = selectLoyaltyItems(state);
@@ -576,4 +590,25 @@ export const selectLoyaltyValidation = (state: RootState) => {
}; };
}; };
// Tax selectors
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
export const selectTaxAmount = (state: RootState) => {
const subtotal = selectCartTotal(state);
const taxes = selectTaxes(state);
return calculateTotalTax(subtotal, taxes || []);
};
export const selectGrandTotal = (state: RootState) => {
const loyaltyDiscount = selectHighestPricedLoyaltyItem(state)?.price || 0;
const taxAmount = selectTaxAmount(state);
const subtotal = selectCartTotal(state);
const deliveryFee =
state.order.orderType != OrderType.DineIn
? Number(state.order.restaurant?.delivery_fees) || 0
: 0;
return subtotal + taxAmount - loyaltyDiscount + deliveryFee;
};
export default orderSlice.reducer; export default orderSlice.reducer;

View File

@@ -0,0 +1,18 @@
import { updateRestaurant } from "features/order/orderSlice";
import { useEffect } from "react";
import { useAppDispatch } from "redux/hooks";
import { RestaurantDetails } from "utils/types/appTypes";
/**
* Custom hook to automatically load restaurant into Redux store
* when restaurant data is available
*/
export const useRestaurant = (restaurant: RestaurantDetails | undefined) => {
const dispatch = useAppDispatch();
useEffect(() => {
if (restaurant) {
dispatch(updateRestaurant(restaurant));
}
}, [restaurant, dispatch]);
};

View File

@@ -1,228 +1,248 @@
import { Variant } from "pages/orders/types" import { Variant } from "pages/orders/types";
import { Extra3 } from "utils/types/appTypes" import { Extra3 } from "utils/types/appTypes";
export interface OrderDetails { export interface OrderDetails {
orderItems: OrderItem[] orderItems: OrderItem[];
order: Order order: Order;
status: Status[] status: Status[];
laststatus: Status2[] laststatus: Status2[];
restaurant: string restaurant: string;
restaurantAR: string restaurantAR: string;
restaurantID: number restaurantID: number;
global_currency: string global_currency: string;
local_currency: string local_currency: string;
address: string address: string;
phone: string phone: string;
restaurant_iimage: string restaurant_iimage: string;
itemsImagePrefixOld: string itemsImagePrefixOld: string;
itemsImagePrefix: string itemsImagePrefix: string;
} }
export interface OrderItem { export interface OrderItem {
id: number id: number;
is_loyalty_used: number is_loyalty_used: number;
no_of_stamps_give: number no_of_stamps_give: number;
isHasLoyalty: boolean isHasLoyalty: boolean;
is_vat_disabled: number is_vat_disabled: number;
name: string name: string;
price: number price: number;
qty: number qty: number;
variant_price: string variant_price: string;
image: string image: string;
imageName: string imageName: string;
variantName: string variantName: string;
variantLocalName?: string variantLocalName?: string;
extras: any[] extras: any[];
itemline: string itemline: string;
itemlineAR: string itemlineAR: string;
itemlineAREN: string itemlineAREN: string;
extrasgroups: any[] extrasgroups: any[];
itemComment: string itemComment: string;
variant?: Variant variant?: Variant;
itemExtras: any[] itemExtras: any[];
AvaiilableVariantExtras: Extra3[] AvaiilableVariantExtras: Extra3[];
isPrinted: number isPrinted: number;
category_id: number category_id: number;
pos_order_id: string pos_order_id: string;
updated_at: string updated_at: string;
created_at: string created_at: string;
old_qty: number old_qty: number;
new_qty: number new_qty: number;
last_printed_qty: number last_printed_qty: number;
deleted_qty: number deleted_qty: number;
discount_value: any discount_value: any;
discount_type_id: any discount_type_id: any;
original_price: number original_price: number;
pricing_method: string pricing_method: string;
is_already_paid: number is_already_paid: number;
hash_item: string hash_item: string;
} }
export interface Order { export interface Order {
id: number id: number;
pos_order_id: string pos_order_id: string;
created_at: string created_at: string;
updated_at: string updated_at: string;
table: string table: string;
phone: string phone: string;
user_name: string user_name: string;
restaurant_name: string restaurant_name: string;
lat: string lat: string;
lng: string lng: string;
restaurant_icon: string restaurant_icon: string;
location: any location: any;
status: string status: string;
status_id: number status_id: number;
delivery_method: number delivery_method: number;
orderItems: OrderItem2[] orderItems: OrderItem2[];
discount: string discount: string;
vat: number vat: number;
total_price: number total_price: number;
comment: string comment: string;
pickup_comments: any pickup_comments: any;
car_plate: string car_plate: string;
pickup_time: any pickup_time: any;
pickup_date: any pickup_date: any;
delivery_pickup_interval: any delivery_pickup_interval: any;
office_no: any office_no: any;
room_no: any room_no: any;
time_to_prepare: any time_to_prepare: any;
last_status_id: number last_status_id: number;
currency: string currency: string;
gift_id: any gift_id: any;
is_loyalty_used: number is_loyalty_used: number;
payment_status: string payment_status: string;
created_by: string created_by: string;
split_order_group_id: any split_order_group_id: any;
split_sequence: any split_sequence: any;
is_split_order: number is_split_order: number;
split_at: any split_at: any;
split_by_user_id: any split_by_user_id: any;
} }
export interface OrderItem2 { export interface OrderItem2 {
id: number id: number;
is_loyalty_used: number is_loyalty_used: number;
no_of_stamps_give: number no_of_stamps_give: number;
isHasLoyalty: boolean isHasLoyalty: boolean;
is_vat_disabled: number is_vat_disabled: number;
name: string name: string;
price: number price: number;
qty: number qty: number;
variant_price: string variant_price: string;
image: string image: string;
imageName: string imageName: string;
variantName: string variantName: string;
variantLocalName?: string variantLocalName?: string;
extras: any[] extras: any[];
itemline: string itemline: string;
itemlineAR: string itemlineAR: string;
itemlineAREN: string itemlineAREN: string;
extrasgroups: any[] extrasgroups: any[];
itemComment: string itemComment: string;
variant?: Variant2 variant?: Variant2;
itemExtras: any[] itemExtras: any[];
AvaiilableVariantExtras: AvaiilableVariantExtra2[] AvaiilableVariantExtras: AvaiilableVariantExtra2[];
isPrinted: number isPrinted: number;
category_id: number category_id: number;
pos_order_id: string pos_order_id: string;
updated_at: string updated_at: string;
created_at: string created_at: string;
old_qty: number old_qty: number;
new_qty: number new_qty: number;
last_printed_qty: number last_printed_qty: number;
deleted_qty: number deleted_qty: number;
discount_value: any discount_value: any;
discount_type_id: any discount_type_id: any;
original_price: number original_price: number;
pricing_method: string pricing_method: string;
is_already_paid: number is_already_paid: number;
hash_item: string hash_item: string;
} }
export interface Status { export interface Status {
id: number id: number;
name: string name: string;
alias: string alias: string;
pivot: Pivot5 pivot: Pivot5;
} }
export interface Pivot5 { export interface Pivot5 {
order_id: number order_id: number;
status_id: number status_id: number;
user_id: number user_id: number;
created_at: string created_at: string;
comment: string comment: string;
} }
export interface Status2 { export interface Status2 {
id: number id: number;
name: string name: string;
alias: string alias: string;
pivot: Pivot6 pivot: Pivot6;
} }
export interface Pivot6 { export interface Pivot6 {
order_id: number order_id: number;
status_id: number status_id: number;
user_id: number user_id: number;
created_at: string created_at: string;
comment: string comment: string;
} }
export interface Variant2 { export interface Variant2 {
id: number id: number;
price: number price: number;
options: string options: string;
local_name: any local_name: any;
optionsArray: OptionsArray2[] optionsArray: OptionsArray2[];
OptionsList: string OptionsList: string;
extras: Extra2[] extras: Extra2[];
} }
export interface OptionsArray2 { export interface OptionsArray2 {
id: number id: number;
name: string name: string;
} }
export interface Extra2 { export interface Extra2 {
id: number id: number;
item_id: number item_id: number;
price: number price: number;
name: string name: string;
nameAR: string nameAR: string;
created_at: string created_at: string;
updated_at: string updated_at: string;
deleted_at: any deleted_at: any;
extra_for_all_variants: number extra_for_all_variants: number;
is_custome: number is_custome: number;
is_available: number is_available: number;
modifier_id: any modifier_id: any;
pivot: Pivot3 pivot: Pivot3;
} }
export interface Pivot3 { export interface Pivot3 {
variant_id: number variant_id: number;
extra_id: number extra_id: number;
} }
export interface AvaiilableVariantExtra2 { export interface AvaiilableVariantExtra2 {
id: number id: number;
item_id: number item_id: number;
price: number price: number;
name: string name: string;
nameAR: string nameAR: string;
created_at: string created_at: string;
updated_at: string updated_at: string;
deleted_at: any deleted_at: any;
extra_for_all_variants: number extra_for_all_variants: number;
is_custome: number is_custome: number;
is_available: number is_available: number;
modifier_id: any modifier_id: any;
pivot: Pivot4 pivot: Pivot4;
} }
export interface Pivot4 { export interface Pivot4 {
variant_id: number variant_id: number;
extra_id: number extra_id: number;
} }
export enum DeliveryMethod {
DineIn = 3,
Delivery = 1,
Pickup = 2,
Gift = 10,
ScheduledOrder = 9,
ToRoom = 5,
ToOffice = 4,
}
export enum OrderType {
DineIn = "dine-in",
Delivery = "delivery",
Pickup = "pickup",
Gift = "gift",
ScheduledOrder = "scheduled_order",
ToRoom = "room",
ToOffice = "office",
}

View File

@@ -2,8 +2,8 @@ import { message } from "antd";
import { import {
clearCart, clearCart,
selectCart, selectCart,
selectCartTotalWithLoyaltyDiscount, selectGrandTotal,
selectHighestPricedLoyaltyItem, selectHighestPricedLoyaltyItem
} from "features/order/orderSlice"; } from "features/order/orderSlice";
import { useCallback } from "react"; import { useCallback } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -37,7 +37,7 @@ export default function useOrder() {
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem); const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const { useLoyaltyPoints } = useAppSelector(selectCart); const { useLoyaltyPoints } = useAppSelector(selectCart);
const orderPrice = useAppSelector(selectCartTotalWithLoyaltyDiscount); const orderPrice = useAppSelector(selectGrandTotal);
const [createOrder] = useCreateOrderMutation(); const [createOrder] = useCreateOrderMutation();

View File

@@ -7,6 +7,7 @@ import ProText from "components/ProText";
import ProTitle from "components/ProTitle"; import ProTitle from "components/ProTitle";
import { useScrollHandler } from "contexts/ScrollHandlerContext"; import { useScrollHandler } from "contexts/ScrollHandlerContext";
import useBreakPoint from "hooks/useBreakPoint"; import useBreakPoint from "hooks/useBreakPoint";
import { useRestaurant } from "hooks/useRestaurant";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { import {
@@ -44,6 +45,9 @@ function MenuPage() {
const { isMobile, isTablet, isDesktop } = useBreakPoint(); const { isMobile, isTablet, isDesktop } = useBreakPoint();
const isLoading = isLoadingRestaurant || isLoadingMenu; const isLoading = isLoadingRestaurant || isLoadingMenu;
// Automatically load restaurant taxes when restaurant data is available
useRestaurant(restaurant);
return ( return (
<> <>
<LocalStorageHandler restaurantID={restaurant?.restautantId || ""} /> <LocalStorageHandler restaurantID={restaurant?.restautantId || ""} />

View File

@@ -12,6 +12,7 @@ import RestaurantServices from "./RestaurantServices";
// Import the Client Component for localStorage handling // Import the Client Component for localStorage handling
import Ads1 from "components/Ads/Ads1"; import Ads1 from "components/Ads/Ads1";
import { Loader } from "components/Loader/Loader"; import { Loader } from "components/Loader/Loader";
import { useRestaurant } from "hooks/useRestaurant";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others"; import { useGetRestaurantDetailsQuery } from "redux/api/others";
import LocalStorageHandler from "../menu/components/LocalStorageHandler"; import LocalStorageHandler from "../menu/components/LocalStorageHandler";
@@ -19,12 +20,12 @@ import LocalStorageHandler from "../menu/components/LocalStorageHandler";
export default function RestaurantPage() { export default function RestaurantPage() {
const param = useParams(); const param = useParams();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { data: restaurant, isLoading } = useGetRestaurantDetailsQuery( const { data: restaurant, isLoading } = useGetRestaurantDetailsQuery("595", {
"595", skip: !param.id,
{ });
skip: !param.id,
}, // Automatically load restaurant taxes when restaurant data is available
); useRestaurant(restaurant);
if (isLoading) { if (isLoading) {
return <Loader />; return <Loader />;

View File

@@ -2,15 +2,20 @@ import ActionsButtons from "components/ActionsButtons/ActionsButtons";
import { selectCart, setTmp } from "features/order/orderSlice"; import { selectCart, setTmp } from "features/order/orderSlice";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
interface SplitBillTmp {
totalPeople?: number;
payFor?: number;
}
export default function PayForActions() { export default function PayForActions() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { tmp } = useAppSelector(selectCart); const { tmp } = useAppSelector(selectCart);
return ( return (
<ActionsButtons <ActionsButtons
quantity={tmp?.payFor || 1} quantity={(tmp as SplitBillTmp)?.payFor || 1}
setQuantity={(value) => dispatch(setTmp({ ...tmp, payFor: value }))} setQuantity={(value) => dispatch(setTmp({ ...(tmp as SplitBillTmp), payFor: value }))}
max={tmp?.totalPeople || 10} max={(tmp as SplitBillTmp)?.totalPeople || 10}
min={1} min={1}
/> />
); );

View File

@@ -1,23 +1,27 @@
import { Card, Divider, Space } from "antd"; import { Card, Divider, Space } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import ProText from "components/ProText"; import ProText from "components/ProText";
import { selectCart, selectCartTotal } from "features/order/orderSlice"; import {
selectCart,
selectGrandTotal
} from "features/order/orderSlice";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { ProGray1 } from "ThemeConstants"; import { ProGray1 } from "ThemeConstants";
import styles from "../SplitBillPage.module.css"; import styles from "../SplitBillPage.module.css";
interface SplitBillTmp {
totalPeople?: number;
payFor?: number;
}
export default function PaymentSummary() { export default function PaymentSummary() {
const { t } = useTranslation(); const { t } = useTranslation();
const { tmp } = useAppSelector(selectCart); const { tmp } = useAppSelector(selectCart);
const getTotal = useAppSelector(selectCartTotal); const total = useAppSelector(selectGrandTotal);
const subtotal = getTotal; const costPerPerson = total / ((tmp as SplitBillTmp)?.totalPeople || 1);
const tax = subtotal * 0.1; // 10% tax const remainingAmount = total - ((tmp as SplitBillTmp)?.payFor || 1) * costPerPerson;
const total = subtotal + tax;
const costPerPerson = total / (tmp?.totalPeople || 1);
const remainingAmount = total - (tmp?.payFor || 1) * costPerPerson;
return ( return (
<Card className={`${styles.orderSummary}`}> <Card className={`${styles.orderSummary}`}>