10 Комити

Аутор SHA1 Порука Датум
  nadia efe33427ad feat module 1,2,4 : product type display based on menu пре 3 месеци
  nadia 061ae5c53d feat module 1,2,4 : product search display based on menu пре 3 месеци
  nadia ffd17a6095 feat module 1,2,4 : product suggestion display based on menu пре 3 месеци
  nadia 0a445adef8 feat module 1,2,4 : new product display based on menu пре 3 месеци
  nadia 4f8981f4cf feat module 1,2,4 : product list display based on menu пре 3 месеци
  nadia 7d75a194d0 feat module 1,2,4 : product history based on menu display пре 3 месеци
  nadia 3537112fd2 feat module 1,2,4 : change to dynamic navigation menu based on shopify collection - mobile view пре 3 месеци
  nadia 5a0f495409 feat module 1,2,4 : change to dynamic navigation menu based on shopify collection - navbar пре 3 месеци
  nadia 049eaf4c94 feat module 1,2,4 : change to dynamic navigation menu based on shopify collection пре 3 месеци
  nadia a9c00e1b32 feat module 1,2,4 : add new collection and metafields menu group in product query пре 3 месеци

+ 17
- 7
src/components/Navbar/Navbar.jsx Прегледај датотеку

@@ -29,7 +29,11 @@ import { Scrollbar, A11y, Navigation } from 'swiper/modules';
29 29
 
30 30
 import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
31 31
 import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
32
-import { NAV_MENU_STRUCTURE as SHARED_NAV_MENU_STRUCTURE } from "../../config/menuCollections";
32
+import {
33
+  NAV_MENU_STRUCTURE as SHARED_NAV_MENU_STRUCTURE,
34
+  buildNavMenuFromProducts,
35
+  normalizeTitle,
36
+} from "../../config/menuCollections";
33 37
 
34 38
 function calculateTotalQuantity(cartItems) {
35 39
   return cartItems.reduce((total, item) => total + item.quantity, 0);
@@ -84,8 +88,6 @@ const COLLECTION_PRIORITY_BY_TYPE = {
84 88
   ],
85 89
 };
86 90
 
87
-const normalizeTitle = (value = "") => value.trim().toUpperCase();
88
-
89 91
 const getProductTypeMenuLabel = (productType = "") => {
90 92
   return normalizeTitle(productType) === "BEAUTY" ? "Essentials" : productType;
91 93
 };
@@ -192,6 +194,9 @@ const Navbar = () => {
192 194
   const [activeMenu, setActiveMenu] = useState(null);
193 195
   const [activeGroup, setActiveGroup] = useState(null);
194 196
   const [dropdownTopOffset, setDropdownTopOffset] = useState(52);
197
+  const dynamicNavMenu = products?.length > 0
198
+    ? buildNavMenuFromProducts(products)
199
+    : NAV_MENU_STRUCTURE;
195 200
 
196 201
   const [displayCollection, setDisplayCollection] = useState({
197 202
     productType: null,
@@ -394,15 +399,19 @@ const Navbar = () => {
394 399
 
395 400
   const findCollectionForMenuItem = (productType, item) => {
396 401
     const targetTitles = (item?.titles || []).map(normalizeTitle);
402
+    const targetHandles = (item?.handles || []).map(normalizeTitle);
397 403
 
398 404
     return products
399 405
       .filter((product) => product.productType === productType)
400 406
       .flatMap((product) => product.collections || [])
401
-      .find((collection) => targetTitles.includes(normalizeTitle(collection?.title || "")));
407
+      .find((collection) =>
408
+        targetTitles.includes(normalizeTitle(collection?.title || "")) ||
409
+        targetHandles.includes(normalizeTitle(collection?.handle || ""))
410
+      );
402 411
   }
403 412
 
404 413
   const getMenuCollectionTitles = (productType) => {
405
-    const menu = NAV_MENU_STRUCTURE.find((menuItem) => menuItem.productType === productType);
414
+    const menu = dynamicNavMenu.find((menuItem) => menuItem.productType === productType);
406 415
 
407 416
     if (!menu) return [];
408 417
 
@@ -545,7 +554,8 @@ const Navbar = () => {
545 554
               }}
546 555
             >
547 556
 
548
-              {NAV_MENU_STRUCTURE.map((menu) => (
557
+              {/* Navigation Menu Items */}
558
+              {dynamicNavMenu.map((menu) => (
549 559
                 <Box
550 560
                   key={menu.label}
551 561
                   ref={(node) => {
@@ -984,7 +994,7 @@ const Navbar = () => {
984 994
       </AppBar>
985 995
       {showSearch && <SearchProduct onClose={() => { setShowSearch(false) }} />}
986 996
 
987
-      <MobileNav open={openSideMenu} menu={NAV_MENU_STRUCTURE} products={products} infomenu={navItemCompanyInfo} onClose={() => { setOpenSideMenu(false) }} />
997
+      <MobileNav open={openSideMenu} menu={dynamicNavMenu} products={products} infomenu={navItemCompanyInfo} onClose={() => { setOpenSideMenu(false) }} />
988 998
       <SideCart open={openSideCart} onClose={() => { setOpenSideCart(false) }} />
989 999
 
990 1000
     </>

+ 5
- 1
src/components/Navbar/components/MobileNav/MobileNav.jsx Прегледај датотеку

@@ -30,11 +30,15 @@ const MobileNav = ({ open, onClose, menu = [], infomenu = [], products = [] }) =
30 30
 
31 31
   const findCollectionForMenuItem = (productType, item) => {
32 32
     const targetTitles = (item?.titles || []).map(normalizeTitle);
33
+    const targetHandles = (item?.handles || []).map(normalizeTitle);
33 34
 
34 35
     return products
35 36
       .filter((product) => product.productType === productType)
36 37
       .flatMap((product) => product.collections || [])
37
-      .find((collection) => targetTitles.includes(normalizeTitle(collection?.title || "")));
38
+      .find((collection) =>
39
+        targetTitles.includes(normalizeTitle(collection?.title || "")) ||
40
+        targetHandles.includes(normalizeTitle(collection?.handle || ""))
41
+      );
38 42
   }
39 43
 
40 44
   const getMenuCollectionTitles = (productType) => {

+ 4
- 14
src/components/ProductHistoryList/ProductHistoryList.jsx Прегледај датотеку

@@ -2,22 +2,12 @@ import { useState, useEffect } from 'react';
2 2
 import { Box, Typography, IconButton, Pagination, Button } from '@mui/material';
3 3
 import { useSelector, useDispatch } from 'react-redux';
4 4
 import defaultImage from "../../assets/images/default.png"
5
-import { getMenuCollectionTitlesByProductType } from '../../config/menuCollections';
5
+import { isProductInVisibleMenu } from '../../config/menuCollections';
6 6
 
7 7
 import Grid from '@mui/material/Grid2';
8 8
 
9
-const normalizeTitle = (title = "") => title.trim().toUpperCase();
10
-
11
-const allowedRecentlyViewedCollections = getMenuCollectionTitlesByProductType();
12
-
13
-const isProductAllowedInRecentlyViewed = (product) => {
14
-  const allowedCollections = allowedRecentlyViewedCollections[product?.productType] || [];
15
-  const normalizedAllowedCollections = allowedCollections.map(normalizeTitle);
16
-
17
-  return product?.collections?.some((collection) =>
18
-    normalizedAllowedCollections.includes(normalizeTitle(collection?.title))
19
-  );
20
-};
9
+// const allowedRecentlyViewedCollections = getMenuCollectionTitlesByProductType();
10
+// const isProductAllowedInRecentlyViewed = (product) => { ... };
21 11
 
22 12
 const ProductHistoryList = () => {
23 13
 
@@ -30,7 +20,7 @@ const ProductHistoryList = () => {
30 20
 
31 21
       const productHandleList = JSON.parse(localStorage.getItem('amber-product-history')) || []
32 22
       const selectedProducts = products.filter((product) => (
33
-        productHandleList.includes(product.handle) && isProductAllowedInRecentlyViewed(product)
23
+        productHandleList.includes(product.handle) && isProductInVisibleMenu(product, products)
34 24
       )) || []
35 25
 
36 26
       // Keep the old history key, but remove products that are no longer available through the menu.

+ 27
- 7
src/components/ProductList/ProductList.jsx Прегледај датотеку

@@ -16,6 +16,11 @@ import { fetchProducts } from "../../redux/slices/productSlice";
16 16
 import { useNavigate } from "react-router-dom";
17 17
 import defaultImage from "../../assets/images/default.png"
18 18
 import atomeLogo from "../../assets/images/atome.webp"
19
+import {
20
+  getAllowedCollectionTitleSet,
21
+  getRelatedProductTypes,
22
+  normalizeTitle,
23
+} from "../../config/menuCollections";
19 24
 
20 25
 //UTIL FUNCTION
21 26
 function getAllTags(data) {
@@ -127,36 +132,51 @@ const ProductList = ({ size = 99999 }) => {
127 132
     if (products?.length > 0) {
128 133
       let productType = sessionStorage.getItem("amber-select-product-type");
129 134
       const selectedCollectionTitles = getSelectedCollectionTitles();
135
+      const normalizedSelectedCollectionTitles = selectedCollectionTitles.map(normalizeTitle);
136
+      const allowedCollectionSet = getAllowedCollectionTitleSet(products, productType);
137
+      const relatedProductTypeSet = new Set(getRelatedProductTypes(productType));
138
+      const productMatchesSelectedType = (product) =>
139
+        relatedProductTypeSet.has(normalizeTitle(product?.productType));
140
+      const productHasVisibleCollection = (product) =>
141
+        product.collections?.some((data) =>
142
+          allowedCollectionSet.has(normalizeTitle(data?.title || ""))
143
+        );
130 144
 
131 145
       let newFilteredProducts = products.filter(
132
-        (product) => product.productType === productType
146
+        (product) => productMatchesSelectedType(product)
133 147
       );
134 148
 
135 149
       // Tags
136 150
       newFilteredProducts = newFilteredProducts.filter((product) => {
137 151
         if (tags == "all" || tags == "tag") {
138
-          return product.productType === productType;
152
+          return productMatchesSelectedType(product);
139 153
         } else {
140 154
           return (
141
-            product.productType === productType && product.tags.includes(tags)
155
+            productMatchesSelectedType(product) && product.tags.includes(tags)
142 156
           );
143 157
         }
144 158
       });
145 159
 
146 160
       // Collection
147 161
       newFilteredProducts = newFilteredProducts.filter((product) => {
162
+        // Product list now follows collections exposed by the Shopify collection
163
+        // custom.menu_group metafield. 
164
+        if (!productHasVisibleCollection(product)) return false;
165
+
148 166
         if (selectedCollectionTitles.length > 0) {
149 167
           return (
150
-            product.productType === productType &&
151
-            product.collections.some((data) => selectedCollectionTitles.includes(data?.title))
168
+            productMatchesSelectedType(product) &&
169
+            product.collections.some((data) =>
170
+              normalizedSelectedCollectionTitles.includes(normalizeTitle(data?.title || ""))
171
+            )
152 172
           );
153 173
         }
154 174
 
155 175
         if (collection == "all" || collection == "collection") {
156
-          return product.productType === productType;
176
+          return productMatchesSelectedType(product);
157 177
         } else {
158 178
           return (
159
-            product.productType === productType &&
179
+            productMatchesSelectedType(product) &&
160 180
             product.collections.some((data) => data?.title === collection)
161 181
           );
162 182
         }

+ 4
- 1
src/components/ProductSelected/ProductSelected.jsx Прегледај датотеку

@@ -11,6 +11,7 @@ import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
11 11
 import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
12 12
 import defaultImage from "../../assets/images/default.png"
13 13
 import atomeLogo from "../../assets/images/atome.webp"
14
+import { isProductInVisibleMenu } from "../../config/menuCollections";
14 15
 
15 16
 const ProductSelected = () => {
16 17
 
@@ -23,7 +24,9 @@ const ProductSelected = () => {
23 24
 
24 25
     if (products.length > 0) {
25 26
 
26
-      let selectedProducts = products.filter(({ selected }) => selected) || []
27
+      let selectedProducts = products.filter((product) => (
28
+        product.selected && isProductInVisibleMenu(product, products)
29
+      )) || []
27 30
 
28 31
       //sort
29 32
       selectedProducts = selectedProducts.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));

+ 4
- 15
src/components/ProductSuggestion/ProductSuggestion.jsx Прегледај датотеку

@@ -4,21 +4,10 @@ import { useSelector } from 'react-redux';
4 4
 import Grid from '@mui/material/Grid2';
5 5
 import { useNavigate } from "react-router-dom";
6 6
 import defaultImage from "../../assets/images/default.png"
7
-import { getMenuCollectionTitlesByProductType } from '../../config/menuCollections';
7
+import { isProductInVisibleMenu } from '../../config/menuCollections';
8 8
 
9
-const normalizeTitle = (title = "") => title.trim().toUpperCase();
10
-
11
-const allowedSuggestionCollections = getMenuCollectionTitlesByProductType();
12
-
13
-// Function to check if a product is allowed in the suggestion based on its collections
14
-const isProductAllowedInSuggestion = (product) => {
15
-  const allowedCollections = allowedSuggestionCollections[product?.productType] || [];
16
-  const normalizedAllowedCollections = allowedCollections.map(normalizeTitle);
17
-
18
-  return product?.collections?.some((collection) =>
19
-    normalizedAllowedCollections.includes(normalizeTitle(collection?.title))
20
-  );
21
-};
9
+// const allowedSuggestionCollections = getMenuCollectionTitlesByProductType();
10
+// const isProductAllowedInSuggestion = (product) => { ... };
22 11
 
23 12
 const ProductSuggestion = () => {
24 13
 
@@ -34,7 +23,7 @@ const ProductSuggestion = () => {
34 23
         return shuffled.slice(0, num);
35 24
       };
36 25
 
37
-      const visibleProducts = products.filter(isProductAllowedInSuggestion);
26
+      const visibleProducts = products.filter((product) => isProductInVisibleMenu(product, products));
38 27
       // const randomProducts = getRandomProducts(products, 4);
39 28
       const randomProducts = getRandomProducts(visibleProducts, 4); // Select 4 random visible products
40 29
       setSuggestProducts(randomProducts);

+ 3
- 3
src/components/ProductType/ProductType.jsx Прегледај датотеку

@@ -49,9 +49,9 @@ const ProductType = ({ title, img_url, clickable = true }) => {
49 49
           sessionStorage.setItem('amber-select-product-type', productTypeValue)
50 50
           sessionStorage.removeItem('amber-select-collection')
51 51
           sessionStorage.removeItem('amber-select-collections')
52
-          if (title?.trim()?.toUpperCase() === "ESSENTIALS") {
53
-            sessionStorage.setItem('amber-select-collections', JSON.stringify(["COSMETICS", "HAND & BODY LOTION"]))
54
-          }
52
+          // if (title?.trim()?.toUpperCase() === "ESSENTIALS") {
53
+          //   sessionStorage.setItem('amber-select-collections', JSON.stringify(["COSMETICS", "HAND & BODY LOTION"]))
54
+          // }
55 55
           window.location.href = '/products'
56 56
         } : undefined}
57 57
       >

+ 3
- 20
src/components/SearchProduct/SearchProduct.jsx Прегледај датотеку

@@ -11,7 +11,7 @@ import {
11 11
   CardMedia,
12 12
 } from "@mui/material";
13 13
 import { useSelector } from "react-redux";
14
-import { getMenuCollectionTitlesByProductType } from "../../config/menuCollections";
14
+import { isProductInVisibleMenu } from "../../config/menuCollections";
15 15
 
16 16
 function doesSubstringExistInArray(array, substring) {
17 17
     
@@ -23,23 +23,6 @@ function doesSubstringExistInArray(array, substring) {
23 23
     return array.some(element => element.toLowerCase().includes(lowerCaseSubstring));
24 24
 }
25 25
 
26
-// Define allowed search collections for each product type
27
-const allowedSearchCollectionsByProductType = getMenuCollectionTitlesByProductType();
28
-
29
-const normalizeTitle = (value = "") => value.trim().toUpperCase();
30
-
31
-const isProductAllowedInSearch = (product) => {
32
-  const allowedCollections = allowedSearchCollectionsByProductType[product?.productType];
33
-
34
-  if (!allowedCollections) return false;
35
-
36
-  const allowedCollectionSet = new Set(allowedCollections.map(normalizeTitle));
37
-
38
-  return product?.collections?.some((collection) =>
39
-    allowedCollectionSet.has(normalizeTitle(collection?.title || ""))
40
-  );
41
-}
42
-
43 26
 const SearchProduct = ({ onClose }) => {
44 27
   const products = useSelector((state) => state.products.products.data);
45 28
   const [searchedProducts, setSearchedProducts] = useState([]);
@@ -49,8 +32,8 @@ const SearchProduct = ({ onClose }) => {
49 32
 
50 33
     if(searchQuery?.length < 2) return
51 34
 
52
-      // let filteredProducts = products.filter((product) => {
53
-      let filteredProducts = products.filter(isProductAllowedInSearch).filter((product) => {
35
+      // let filteredProducts = products.filter(isProductAllowedInSearch).filter((product) => {
36
+      let filteredProducts = products.filter((product) => isProductInVisibleMenu(product, products)).filter((product) => {
54 37
 
55 38
       let title = product?.title?.toLowerCase();
56 39
       let search = searchQuery.toLowerCase();

+ 219
- 24
src/config/menuCollections.js Прегледај датотеку

@@ -1,3 +1,19 @@
1
+export const MENU_GROUPS = {
2
+  casual: {
3
+    label: "Casual",
4
+    productType: "Apparel",
5
+  },
6
+  traditional: {
7
+    label: "Traditional",
8
+    productType: "Apparel",
9
+  },
10
+  essentials: {
11
+    label: "Essentials",
12
+    productType: "BEAUTY",
13
+    productTypes: ["BEAUTY", "HOME"],
14
+  },
15
+};
16
+
1 17
 export const NAV_MENU_STRUCTURE = [
2 18
   {
3 19
     productType: "Apparel",
@@ -5,55 +21,234 @@ export const NAV_MENU_STRUCTURE = [
5 21
     groups: [
6 22
       {
7 23
         label: "Casual",
8
-        children: [
9
-          { label: "ND X Marii for Amber", titles: ["DENIM ND X MARII FOR AMBER", "ND X MARII FOR AMBER"] },
10
-          { label: "Somewhere Somehow Someone", titles: ["SOMEWHERE SOMEHOW SOMEONE"] },
11
-          { label: "Flower Power", titles: ["FLOWER POWER"] },
12
-        ],
24
+        menuGroup: "casual",
25
+        // children: [
26
+        //   { label: "ND X Marii for Amber", titles: ["DENIM ND X MARII FOR AMBER", "ND X MARII FOR AMBER"] },
27
+        //   { label: "Somewhere Somehow Someone", titles: ["SOMEWHERE SOMEHOW SOMEONE"] },
28
+        //   { label: "Flower Power", titles: ["FLOWER POWER"] },
29
+        // ],
30
+        children: [],
13 31
       },
14 32
       {
15 33
         label: "Traditional",
16
-        children: [
17
-          // label is the display name, titles are the collection names that will be matched to the product collections in shopify
18
-          { label: "Eid's Time For Love", titles: ["EID'S TIME FOR LOVE"] },
19
-          { label: "Raya Romantics", titles: ["RAYA ROMANTICS", "RAYA ROMANTICS COLLECTION 2025"] },
20
-          { label: "Atma Sari", titles: ["ATMA SARI"] },
21
-          { label: "Oasis abaya", titles: ["OASIS ABAYA COLLECTION", "MIRAGE COLLECTION"] },
22
-        ],
34
+        menuGroup: "traditional",
35
+        // children: [
36
+        //   { label: "Eid's Time For Love", titles: ["EID'S TIME FOR LOVE"] },
37
+        //   { label: "Raya Romantics", titles: ["RAYA ROMANTICS", "RAYA ROMANTICS COLLECTION 2025"] },
38
+        //   { label: "Atma Sari", titles: ["ATMA SARI"] },
39
+        //   { label: "Oasis abaya", titles: ["OASIS ABAYA COLLECTION", "MIRAGE COLLECTION"] },
40
+        // ],
41
+        children: [],
23 42
       },
24 43
     ],
25 44
   },
26 45
   {
27 46
     productType: "BEAUTY",
28 47
     label: "Essentials",
29
-    children: [
30
-      { label: "Cosmetics", titles: ["COSMETICS"] },
31
-      { label: "Hand & Body Lotion", titles: ["HAND & BODY LOTION"] },
32
-    ],
48
+    menuGroup: "essentials",
49
+    // children: [
50
+    //   { label: "Cosmetics", titles: ["COSMETICS"] },
51
+    //   { label: "Hand & Body Lotion", titles: ["HAND & BODY LOTION"] },
52
+    // ],
53
+    children: [],
33 54
   },
34 55
 ];
35 56
 
36
-export const getMenuCollectionTitlesByProductType = () => {
37
-  return NAV_MENU_STRUCTURE.reduce((allowedCollections, menu) => {
38
-    const titles = [];
57
+// Utility function to normalize product titles
58
+export const normalizeTitle = (value = "") => `${value || ""}`.trim().toUpperCase();
59
+
60
+// Utility function to normalize menu group names
61
+export const normalizeMenuGroup = (value = "") =>
62
+  `${value || ""}`.trim().toLowerCase().replace(/\s+/g, "_");
63
+
64
+// Utility function to check if a substring exists in an array of strings
65
+export const getCollectionMenuGroup = (collection = {}) => {
66
+  return normalizeMenuGroup(
67
+    collection?.menuGroup ||
68
+      collection?.metafield?.value ||
69
+      collection?.metafields?.menu_group?.value ||
70
+      collection?.metafields?.find?.(
71
+        (metafield) =>
72
+          metafield?.namespace === "custom" && metafield?.key === "menu_group"
73
+      )?.value
74
+  );
75
+};
76
+
77
+// Utility function to get the key for a collection, prioritizing handle over title
78
+const getCollectionKey = (collection = {}) =>
79
+  collection?.handle || normalizeTitle(collection?.title || "");
80
+
81
+// Utility function to check if a product type matches the allowed product types in a group configuration
82
+const getProductTypeMatches = (productType, groupConfig = {}) => {
83
+  const targetProductTypes = groupConfig.productTypes || [groupConfig.productType];
84
+
85
+  return targetProductTypes
86
+    .map(normalizeTitle)
87
+    .includes(normalizeTitle(productType));
88
+};
89
+
90
+// Utility function to get menu collections by product type
91
+export const getMenuCollectionsByProductType = (products = []) => {
92
+  return products.reduce((menuCollections, product) => {
93
+    if (!product?.productType || !Array.isArray(product?.collections)) {
94
+      return menuCollections;
95
+    }
96
+
97
+    product.collections.forEach((collection) => {
98
+      const menuGroup = getCollectionMenuGroup(collection);
99
+
100
+      if (!menuGroup) return;
101
+
102
+      const groupConfig = MENU_GROUPS[menuGroup];
103
+
104
+      if (!groupConfig) return;
105
+
106
+      if (!getProductTypeMatches(product.productType, groupConfig)) {
107
+        return;
108
+      }
109
+
110
+      const productType = groupConfig.productType;
111
+      const key = getCollectionKey(collection);
112
+
113
+      if (!menuCollections[productType]) {
114
+        menuCollections[productType] = {};
115
+      }
116
+
117
+      if (!menuCollections[productType][menuGroup]) {
118
+        menuCollections[productType][menuGroup] = new Map();
119
+      }
120
+
121
+      if (!menuCollections[productType][menuGroup].has(key)) {
122
+        menuCollections[productType][menuGroup].set(key, {
123
+          ...collection,
124
+          menuGroup,
125
+        });
126
+      }
127
+    });
128
+
129
+    return menuCollections;
130
+  }, {});
131
+};
132
+
133
+// Utility function to map a collection to a menu item structure
134
+const mapCollectionToMenuItem = (collection = {}) => ({
135
+  label: collection?.title || "",
136
+  titles: collection?.title ? [collection.title] : [],
137
+  handles: collection?.handle ? [collection.handle] : [],
138
+  collection,
139
+});
140
+
141
+// Utility function to build the navigation menu from products
142
+export const buildNavMenuFromProducts = (products = []) => {
143
+  const menuCollections = getMenuCollectionsByProductType(products);
144
+
145
+  return NAV_MENU_STRUCTURE.map((menu) => {
146
+    if (menu.groups) {
147
+      return {
148
+        ...menu,
149
+        groups: menu.groups
150
+          .map((group) => ({
151
+            ...group,
152
+            children: Array.from(
153
+              menuCollections?.[menu.productType]?.[group.menuGroup]?.values?.() || []
154
+            ).map(mapCollectionToMenuItem),
155
+          }))
156
+          .filter((group) => group.children.length > 0),
157
+      };
158
+    }
159
+
160
+    if (menu.menuGroup) {
161
+      return {
162
+        ...menu,
163
+        children: Array.from(
164
+          menuCollections?.[menu.productType]?.[menu.menuGroup]?.values?.() || []
165
+        ).map(mapCollectionToMenuItem),
166
+      };
167
+    }
168
+
169
+    return menu;
170
+  }).filter((menu) => {
171
+    if (menu.groups) return menu.groups.length > 0;
172
+    if (menu.menuGroup) return (menu.children || []).length > 0;
173
+    return true;
174
+  });
175
+};
176
+
177
+// Utility function to get menu collection titles by product type
178
+export const getMenuCollectionTitlesByProductType = (products = []) => {
179
+  const menuSource = products.length > 0 ? buildNavMenuFromProducts(products) : NAV_MENU_STRUCTURE;
180
+
181
+  return menuSource.reduce((allowedCollections, menu) => {
182
+    const addTitles = (productTypes = [], titles = []) => {
183
+      productTypes.filter(Boolean).forEach((productType) => {
184
+        allowedCollections[productType] = [
185
+          ...(allowedCollections[productType] || []),
186
+          ...titles,
187
+        ];
188
+      });
189
+    };
39 190
 
40 191
     if (menu.groups) {
41 192
       menu.groups.forEach((group) => {
42
-        group.children.forEach((item) => {
193
+        const titles = [];
194
+
195
+        (group.children || []).forEach((item) => {
43 196
           titles.push(...(item.titles || []));
44 197
         });
198
+
199
+        const groupConfig = MENU_GROUPS[group.menuGroup];
200
+        addTitles(
201
+          groupConfig?.productTypes || [groupConfig?.productType || menu.productType],
202
+          titles
203
+        );
45 204
       });
46 205
     }
47 206
 
48 207
     if (menu.children) {
208
+      const titles = [];
209
+
49 210
       menu.children.forEach((item) => {
50 211
         titles.push(...(item.titles || []));
51 212
       });
213
+
214
+      const groupConfig = MENU_GROUPS[menu.menuGroup];
215
+      addTitles(
216
+        groupConfig?.productTypes || [groupConfig?.productType || menu.productType],
217
+        titles
218
+      );
52 219
     }
53 220
 
54
-    return {
55
-      ...allowedCollections,
56
-      [menu.productType]: titles,
57
-    };
221
+    return allowedCollections;
58 222
   }, {});
59 223
 };
224
+
225
+// Utility function to get a set of allowed collection titles for a given product type
226
+export const getAllowedCollectionTitleSet = (products = [], productType = "") => {
227
+  const allowedCollections = getMenuCollectionTitlesByProductType(products)[productType] || [];
228
+
229
+  return new Set(allowedCollections.map(normalizeTitle));
230
+};
231
+
232
+// Utility function to get related product types based on a given product type
233
+export const getRelatedProductTypes = (productType = "") => {
234
+  const normalizedProductType = normalizeTitle(productType);
235
+  const relatedProductTypes = Object.values(MENU_GROUPS)
236
+    .filter((group) => normalizeTitle(group.productType) === normalizedProductType)
237
+    .flatMap((group) => group.productTypes || [group.productType])
238
+    .map(normalizeTitle);
239
+
240
+  if (relatedProductTypes.length === 0) return [normalizedProductType];
241
+
242
+  return [...new Set(relatedProductTypes)];
243
+};
244
+
245
+// Utility function to check if a product is in the visible menu based on its collections and product type
246
+export const isProductInVisibleMenu = (product, products = []) => {
247
+  const allowedCollectionSet = getAllowedCollectionTitleSet(products, product?.productType);
248
+
249
+  if (allowedCollectionSet.size === 0) return false;
250
+
251
+  return product?.collections?.some((collection) =>
252
+    allowedCollectionSet.has(normalizeTitle(collection?.title || ""))
253
+  );
254
+};

+ 14
- 0
src/services/ProductService.js Прегледај датотеку

@@ -53,10 +53,17 @@ const getProducts = async () => {
53 53
           collections(first: 10) {
54 54
             nodes {
55 55
               title
56
+              handle
56 57
               image {
57 58
                 url
58 59
               }
59 60
               updatedAt
61
+              metafield(namespace: "custom", key: "menu_group") {
62
+                namespace
63
+                key
64
+                type
65
+                value
66
+              }
60 67
             }
61 68
           }
62 69
           variants(first: 200) {
@@ -139,9 +146,16 @@ const getProduct = async (handle = "") => {
139 146
       collections(first: 10) {
140 147
         nodes {
141 148
           title
149
+          handle
142 150
           image {
143 151
             url
144 152
           }
153
+          metafield(namespace: "custom", key: "menu_group") {
154
+            namespace
155
+            key
156
+            type
157
+            value
158
+          }
145 159
         }
146 160
       }
147 161
       variants(first: 200) {

Loading…
Откажи
Сачувај