Cart: fix extras group

This commit is contained in:
2025-11-11 17:02:19 +03:00
parent 42a70affe2
commit 3d3262e5ad
6 changed files with 166 additions and 130 deletions

View File

@@ -1,119 +0,0 @@
import { Divider, message } from "antd";
import ProCheckboxGroups from "components/ProCheckboxGroups/ProCheckboxGroups";
import ProText from "components/ProText";
import { Dispatch, SetStateAction } from "react";
import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks";
import { Extra, TheExtrasGroup } from "utils/types/appTypes";
import styles from "../product.module.css";
export default function ExtraGroups({
groupsList,
selectedExtrasByGroup,
setSelectedExtrasByGroup,
}: {
groupsList: TheExtrasGroup[];
selectedExtrasByGroup: Extra[];
setSelectedExtrasByGroup: Dispatch<SetStateAction<Extra[]>>;
}) {
const { isRTL } = useAppSelector((state) => state.locale);
const { t } = useTranslation();
return (
<>
{groupsList.length > 0 && (
<div>
<Divider style={{ margin: "0 0 16px 0" }} />
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<ProText style={{ fontSize: "1.25rem" }}>
{t("menu.youMightAlsoLike")}
</ProText>
<ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.optional")}
</ProText>
</div>
{/* <ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.choose1")}
</ProText> */}
</div>
)}
{groupsList?.map((group: TheExtrasGroup) => (
<div key={group.id}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<ProText strong style={{ fontSize: "1rem" }}>
{isRTL ? group.nameAR : group.name}
</ProText>
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<ProText style={{ fontSize: "0.75rem", color: "#666" }}>
{group.force_limit_selection === 1 ? "Required" : "Optional"}
</ProText>
<ProText style={{ fontSize: "0.75rem", color: "#666" }}>
(
{selectedExtrasByGroup.filter((extra) => extra.id === group.id)
.length || 0}
/{group.limit})
</ProText>
</div>
</div>
{group.force_limit_selection === 1 && (
<ProText
style={{
fontSize: "0.75rem",
color: "red",
marginBottom: "8px",
}}
>
* You must select exactly {group.limit} option
{group.limit > 1 ? "s" : ""}
</ProText>
)}
<div className={styles.productContainer}>
<ProCheckboxGroups
options={group.extras.map((extra: any) => ({
value: extra.id.toString(),
label: isRTL ? extra.name : extra.nameAR,
price: `+${extra.price}`,
}))}
value={selectedExtrasByGroup.map((extra) => extra.id.toString())}
onChange={(values: string[]) => {
// Check if the new selection would exceed the limit
if (values.length > group.limit) {
message.error(
`You can only select up to ${group.limit} option${group.limit > 1 ? "s" : ""} from this group.`,
);
return;
}
setSelectedExtrasByGroup(
group.extras.filter((o) => values?.includes(o.id.toString())),
);
}}
/>
</div>
</div>
))}
</>
);
}

View File

@@ -0,0 +1,65 @@
import { Divider } from "antd";
import ProText from "components/ProText";
import { Dispatch, SetStateAction } from "react";
import { useTranslation } from "react-i18next";
import { TheExtrasGroup } from "utils/types/appTypes";
import ExtrasGroup from "pages/product/components/ExtrasGroup.tsx";
export default function ExtraGroupsContainer({
groupsList,
selectedExtrasByGroup,
setSelectedExtrasByGroup,
}: {
groupsList: TheExtrasGroup[];
selectedExtrasByGroup: Record<number, string[]>;
setSelectedExtrasByGroup: Dispatch<SetStateAction<Record<number, string[]>>>;
}) {
const { t } = useTranslation();
return (
<>
{groupsList.length > 0 && (
<div>
<Divider style={{ margin: "0 0 16px 0" }} />
<div
style={{
display: "flex",
justifyContent: "space-between",
}}
>
<ProText style={{ fontSize: "1.25rem" }}>
{t("menu.youMightAlsoLike")}
</ProText>
<ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.optional")}
</ProText>
</div>
{/* <ProText strong style={{ fontSize: "0.75rem" }}>
{t("menu.choose1")}
</ProText> */}
</div>
)}
{groupsList?.map((group: TheExtrasGroup) => (
<ExtrasGroup
group={group}
selectedExtras={selectedExtrasByGroup[group.id]}
onChange={(values) => {
setSelectedExtrasByGroup({
...selectedExtrasByGroup,
[group.id]:
group.extras
.filter((o) => values?.includes(o.id.toString()))
.map((e) => e.id.toString()) || [],
});
}}
/>
))}
</>
);
}

View File

@@ -0,0 +1,87 @@
import ProText from "components/ProText.tsx";
import styles from "pages/product/product.module.css";
import ProCheckboxGroups from "components/ProCheckboxGroups/ProCheckboxGroups.tsx";
import { message } from "antd";
import { TheExtrasGroup } from "utils/types/appTypes.ts";
import { useAppSelector } from "redux/hooks.ts";
type Props = {
group: TheExtrasGroup;
selectedExtras: Array<string>;
onChange: (values: Array<string>) => void;
};
export default function ExtrasGroup({
group,
selectedExtras,
onChange,
}: Props) {
const { isRTL } = useAppSelector((state) => state.locale);
return (
<div>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<ProText strong style={{ fontSize: "1rem" }}>
{isRTL ? group.nameAR : group.name}
</ProText>
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<ProText style={{ fontSize: "0.75rem", color: "#666" }}>
{group.force_limit_selection === 1 ? "Required" : "Optional"}
</ProText>
<ProText style={{ fontSize: "0.75rem", color: "#666" }}>
({selectedExtras?.length || 0}/{group.limit})
</ProText>
</div>
</div>
{group.force_limit_selection === 1 && (
<ProText
style={{
fontSize: "0.75rem",
color: "red",
marginBottom: "8px",
}}
>
* You must select exactly {group.limit} option
{group.limit > 1 ? "s" : ""}
</ProText>
)}
<div className={styles.productContainer}>
<ProCheckboxGroups
options={group.extras.map((extra: any) => ({
value: extra.id.toString(),
label: isRTL ? extra.name : extra.nameAR,
price: `+${extra.price}`,
}))}
value={selectedExtras}
onChange={(values: string[]) => {
// Check if the new selection would exceed the limit
if (values.length > group.limit) {
message.error(
`You can only select up to ${group.limit} option${group.limit > 1 ? "s" : ""} from this group.`,
);
return;
}
onChange(
group.extras
.filter((o) => values?.includes(o.id.toString()))
.map((e) => e.id.toString()) || [],
);
}}
/>
</div>
</div>
);
}

View File

@@ -23,7 +23,7 @@ export default function ProductFooter({
isValid?: boolean; isValid?: boolean;
selectedExtras: Extra[]; selectedExtras: Extra[];
selectedVariant?: Variant; selectedVariant?: Variant;
selectedGroups: Extra[]; selectedGroups: Record<number, string[]>;
quantity: number; quantity: number;
onClose?: () => void; onClose?: () => void;
}) { }) {
@@ -69,7 +69,11 @@ export default function ProductFooter({
comment: specialRequest, comment: specialRequest,
variant: selectedVariant, variant: selectedVariant,
extras: selectedExtras, extras: selectedExtras,
extrasgroup: selectedGroups, extrasgroupnew: Object.keys(selectedGroups).map((key) => ({
groupid: key,
extrasid: selectedGroups[Number(key)],
})),
extrasgroup: [""],
isHasLoyalty: product?.isHasLoyalty, isHasLoyalty: product?.isHasLoyalty,
no_of_stamps_give: product?.no_of_stamps_give, no_of_stamps_give: product?.no_of_stamps_give,
}, },

View File

@@ -13,7 +13,7 @@ import { colors } from "ThemeConstants";
import { Extra, Product } from "utils/types/appTypes"; import { Extra, Product } from "utils/types/appTypes";
import BackButton from "../menu/components/BackButton"; import BackButton from "../menu/components/BackButton";
import ExtraComponent from "./components/Extra"; import ExtraComponent from "./components/Extra";
import ExtraGroups from "./components/ExtraGroups"; import ExtraGroupsContainer from "pages/product/components/ExtraGroupsContainer.tsx";
import ProductFooter from "./components/ProductFooter"; import ProductFooter from "./components/ProductFooter";
import Variants from "./components/Variants"; import Variants from "./components/Variants";
import styles from "./product.module.css"; import styles from "./product.module.css";
@@ -40,9 +40,9 @@ export default function ProductDetailPage({
const [selectedExtras, setSelectedExtras] = useState<Extra[]>([]); const [selectedExtras, setSelectedExtras] = useState<Extra[]>([]);
// State for selected extras by group // State for selected extras by group
const [selectedExtrasByGroup, setSelectedExtrasByGroup] = useState<Extra[]>( const [selectedExtrasByGroup, setSelectedExtrasByGroup] = useState<
[], Record<number, Array<string>>
); >([]);
// Determine variant levels based on options array length // Determine variant levels based on options array length
const variantLevels = useMemo(() => { const variantLevels = useMemo(() => {
@@ -126,9 +126,7 @@ 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 = const selectedCount = selectedExtrasByGroup[group.id]?.length || 0;
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
@@ -311,7 +309,7 @@ export default function ProductDetailPage({
)} )}
{product.theExtrasGroups.length > 0 && ( {product.theExtrasGroups.length > 0 && (
<ExtraGroups <ExtraGroupsContainer
groupsList={product.theExtrasGroups} groupsList={product.theExtrasGroups}
selectedExtrasByGroup={selectedExtrasByGroup} selectedExtrasByGroup={selectedExtrasByGroup}
setSelectedExtrasByGroup={setSelectedExtrasByGroup} setSelectedExtrasByGroup={setSelectedExtrasByGroup}

View File

@@ -323,7 +323,8 @@ export interface CartItem {
description: string; description: string;
variant?: Variant; variant?: Variant;
extras?: Extra[]; extras?: Extra[];
extrasgroup?: Extra[]; extrasgroupnew?: Array<{ groupid: string; extrasid: Array<string> }>;
extrasgroup?: Array<string>;
isHasLoyalty?: boolean; isHasLoyalty?: boolean;
no_of_stamps_give?: number; no_of_stamps_give?: number;
comment?: string; comment?: string;