add extra group price

This commit is contained in:
2025-11-10 21:36:00 +03:00
parent 7df267af5a
commit dc18d84ac8
6 changed files with 36 additions and 39 deletions

View File

@@ -628,11 +628,7 @@ export const selectCartTotal = (state: RootState) =>
state.order.items.reduce( state.order.items.reduce(
// (total, item) => total + item.price * item.quantity, // (total, item) => total + item.price * item.quantity,
(total, item) => { (total, item) => {
const extrasPrice = (item.extras || []).reduce( const itemTotalPrice = item.price * item.quantity;
(extraTotal, extraItem) => extraTotal + extraItem.price,
0,
);
const itemTotalPrice = (item.price + extrasPrice) * item.quantity;
return total + itemTotalPrice; return total + itemTotalPrice;
}, },
0, 0,

View File

@@ -4,7 +4,7 @@ import ProText from "components/ProText";
import { Dispatch, SetStateAction } from "react"; import { Dispatch, SetStateAction } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { TheExtrasGroup } from "utils/types/appTypes"; import { Extra, TheExtrasGroup } from "utils/types/appTypes";
import styles from "../product.module.css"; import styles from "../product.module.css";
export default function ExtraGroups({ export default function ExtraGroups({
@@ -13,8 +13,8 @@ export default function ExtraGroups({
setSelectedExtrasByGroup, setSelectedExtrasByGroup,
}: { }: {
groupsList: TheExtrasGroup[]; groupsList: TheExtrasGroup[];
selectedExtrasByGroup: Record<number, string[]>; selectedExtrasByGroup: Extra[];
setSelectedExtrasByGroup: Dispatch<SetStateAction<Record<number, string[]>>>; setSelectedExtrasByGroup: Dispatch<SetStateAction<Extra[]>>;
}) { }) {
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -69,7 +69,10 @@ export default function ExtraGroups({
{group.force_limit_selection === 1 ? "Required" : "Optional"} {group.force_limit_selection === 1 ? "Required" : "Optional"}
</ProText> </ProText>
<ProText style={{ fontSize: "0.75rem", color: "#666" }}> <ProText style={{ fontSize: "0.75rem", color: "#666" }}>
({selectedExtrasByGroup[group.id]?.length || 0}/{group.limit}) (
{selectedExtrasByGroup.filter((extra) => extra.id === group.id)
.length || 0}
/{group.limit})
</ProText> </ProText>
</div> </div>
</div> </div>
@@ -94,7 +97,7 @@ export default function ExtraGroups({
label: isRTL ? extra.name : extra.nameAR, label: isRTL ? extra.name : extra.nameAR,
price: `+${extra.price}`, price: `+${extra.price}`,
}))} }))}
value={selectedExtrasByGroup[group.id] || []} value={selectedExtrasByGroup.map((extra) => extra.id.toString())}
onChange={(values: string[]) => { onChange={(values: string[]) => {
// Check if the new selection would exceed the limit // Check if the new selection would exceed the limit
if (values.length > group.limit) { if (values.length > group.limit) {
@@ -103,10 +106,9 @@ export default function ExtraGroups({
); );
return; return;
} }
setSelectedExtrasByGroup((prev) => ({ setSelectedExtrasByGroup(
...prev, group.extras.filter((o) => values?.includes(o.id.toString())),
[group.id]: values, );
}));
}} }}
/> />
</div> </div>

View File

@@ -7,23 +7,23 @@ import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppDispatch, useAppSelector } from "redux/hooks";
import { colors, ProBlack2 } from "ThemeConstants"; import { colors, ProBlack2 } from "ThemeConstants";
import { Product, Variant, Extra as ExtraType } from "utils/types/appTypes"; import { Extra, Product, Variant } from "utils/types/appTypes";
import styles from "../product.module.css"; import styles from "../product.module.css";
export default function ProductFooter({ export default function ProductFooter({
product, product,
isValid = true, isValid = true,
selectedVariant,
selectedExtras, selectedExtras,
selectedVariant,
selectedGroups, selectedGroups,
quantity, quantity,
onClose, onClose,
}: { }: {
product: Product; product: Product;
isValid?: boolean; isValid?: boolean;
selectedExtras: Extra[];
selectedVariant?: Variant; selectedVariant?: Variant;
selectedExtras: ExtraType[]; selectedGroups: Extra[];
selectedGroups: string[];
quantity: number; quantity: number;
onClose?: () => void; onClose?: () => void;
}) { }) {
@@ -60,7 +60,10 @@ export default function ProductFooter({
item: { item: {
id: product?.id, id: product?.id,
name: product?.name, name: product?.name,
price: selectedVariant?.price || product?.price, price:
(selectedVariant?.price || product?.price) +
selectedExtras.reduce((acc, extra) => acc + extra.price, 0),
// selectedGroups.reduce((acc, extra) => acc + extra.price, 0),
image: product?.image, image: product?.image,
description: product?.description, description: product?.description,
comment: specialRequest, comment: specialRequest,

View File

@@ -168,7 +168,7 @@ export default function Variants({
return { return {
value: value, value: value,
label: value, label: value,
price: variant ? `+${variant.price}` : "", price: variant ? `+${Number(variant.price).toFixed(2)}` : "",
}; };
})} })}
value={selectedVariants[index] || ""} value={selectedVariants[index] || ""}

View File

@@ -10,9 +10,9 @@ import ArabicPrice from "components/ArabicPrice";
import useBreakPoint from "hooks/useBreakPoint"; import useBreakPoint from "hooks/useBreakPoint";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { colors } from "ThemeConstants"; import { colors } from "ThemeConstants";
import { Product, Extra as ExtraType } from "utils/types/appTypes"; import { Extra, Product } from "utils/types/appTypes";
import BackButton from "../menu/components/BackButton"; import BackButton from "../menu/components/BackButton";
import Extra from "./components/Extra"; import ExtraComponent from "./components/Extra";
import ExtraGroups from "./components/ExtraGroups"; import ExtraGroups from "./components/ExtraGroups";
import ProductFooter from "./components/ProductFooter"; import ProductFooter from "./components/ProductFooter";
import Variants from "./components/Variants"; import Variants from "./components/Variants";
@@ -37,12 +37,12 @@ export default function ProductDetailPage({
>({}); >({});
// State for selected extras // State for selected extras
const [selectedExtras, setSelectedExtras] = useState<ExtraType[]>([]); const [selectedExtras, setSelectedExtras] = useState<Extra[]>([]);
// State for selected extras by group // State for selected extras by group
const [selectedExtrasByGroup, setSelectedExtrasByGroup] = useState< const [selectedExtrasByGroup, setSelectedExtrasByGroup] = useState<Extra[]>(
Record<number, string[]> [],
>({}); );
// Determine variant levels based on options array length // Determine variant levels based on options array length
const variantLevels = useMemo(() => { const variantLevels = useMemo(() => {
@@ -126,7 +126,9 @@ export default function ProductDetailPage({
const allRequiredExtraGroupsSatisfied = product.theExtrasGroups.every( const allRequiredExtraGroupsSatisfied = product.theExtrasGroups.every(
(group) => { (group) => {
if (group.force_limit_selection === 1) { if (group.force_limit_selection === 1) {
const selectedCount = selectedExtrasByGroup[group.id]?.length || 0; const selectedCount =
selectedExtrasByGroup.filter((extra) => extra.id === group.id)
.length || 0;
return selectedCount === group.limit; return selectedCount === group.limit;
} }
return true; // Optional groups are always satisfied return true; // Optional groups are always satisfied
@@ -277,7 +279,7 @@ export default function ProductDetailPage({
isValid={isValid} isValid={isValid}
selectedVariant={getFinalSelectedVariant()} selectedVariant={getFinalSelectedVariant()}
selectedExtras={selectedExtras} selectedExtras={selectedExtras}
selectedGroups={Object.values(selectedExtrasByGroup).flat()} selectedGroups={selectedExtrasByGroup}
quantity={quantity} quantity={quantity}
onClose={onClose} onClose={onClose}
/> />
@@ -300,8 +302,8 @@ export default function ProductDetailPage({
/> />
)} )}
{getExtras().length > 0 && ( {getExtras().length && product.theExtrasGroups.length === 0 && (
<Extra <ExtraComponent
extrasList={getExtras()} extrasList={getExtras()}
selectedExtras={selectedExtras} selectedExtras={selectedExtras}
setSelectedExtras={setSelectedExtras} setSelectedExtras={setSelectedExtras}
@@ -326,7 +328,7 @@ export default function ProductDetailPage({
isValid={isValid} isValid={isValid}
selectedVariant={getFinalSelectedVariant()} selectedVariant={getFinalSelectedVariant()}
selectedExtras={selectedExtras} selectedExtras={selectedExtras}
selectedGroups={Object.values(selectedExtrasByGroup).flat()} selectedGroups={selectedExtrasByGroup}
quantity={quantity} quantity={quantity}
/> />
)} )}

View File

@@ -323,7 +323,7 @@ export interface CartItem {
description: string; description: string;
variant?: string; variant?: string;
extras?: Extra[]; extras?: Extra[];
extrasgroup?: string[]; extrasgroup?: Extra[];
isHasLoyalty?: boolean; isHasLoyalty?: boolean;
no_of_stamps_give?: number; no_of_stamps_give?: number;
comment?: string; comment?: string;
@@ -547,7 +547,7 @@ export interface Product {
available: boolean; available: boolean;
currency: string; currency: string;
variants: Variant[]; variants: Variant[];
extras: Extra2[]; extras: Extra[];
categoryId?: number; // to be sent categoryId?: number; // to be sent
isHasExtras: boolean; isHasExtras: boolean;
isHasVarint: boolean; isHasVarint: boolean;
@@ -584,12 +584,6 @@ export interface Extra {
name: string; name: string;
} }
export interface Extra2 {
id: number;
price: number;
name: string;
}
export interface TheExtrasGroup { export interface TheExtrasGroup {
id: number; id: number;
name: string; name: string;