Browse Source

sort by category

master
azri 4 weeks ago
parent
commit
a91415699d

+ 5
- 1
src/components/CollectionList/CollectionList.jsx View File

54
                 <Typography variant="h4" gutterBottom>
54
                 <Typography variant="h4" gutterBottom>
55
                   {name}
55
                   {name}
56
                 </Typography>
56
                 </Typography>
57
-                <Button onClick={()=>{ window.location.href = "/products" }} variant="contained" color="primary">
57
+                <Button onClick={()=>{ 
58
+                  sessionStorage.removeItem('amber-select-category')
59
+                  sessionStorage.setItem('amber-select-collection', JSON.stringify({id, name, src}))
60
+                  window.location.href = "/products" 
61
+                }} variant="contained" color="primary">
58
                   SHOP NOW
62
                   SHOP NOW
59
                 </Button>
63
                 </Button>
60
               </Box>
64
               </Box>

+ 0
- 2
src/components/Filter/Filter.jsx View File

44
         show:1,
44
         show:1,
45
     })
45
     })
46
 
46
 
47
-
48
-
49
     const handleChange = (event) => {
47
     const handleChange = (event) => {
50
         setInput({...input, [event.target.name]:event.target.value });
48
         setInput({...input, [event.target.name]:event.target.value });
51
     };
49
     };

+ 8
- 5
src/components/Navbar/Navbar.jsx View File

137
       >
137
       >
138
 
138
 
139
         {/* Conditionally render the Header */}
139
         {/* Conditionally render the Header */}
140
-        {showHeader && <Header />}
140
+        {/* {showHeader && <Header />} */}
141
 
141
 
142
         <Toolbar sx={{
142
         <Toolbar sx={{
143
           px: {
143
           px: {
205
             {navItem.map(({ title, link }) => (<Button
205
             {navItem.map(({ title, link }) => (<Button
206
               color="inherit"
206
               color="inherit"
207
               onClick={() => {
207
               onClick={() => {
208
+
209
+                sessionStorage.setItem('amber-select-product-type',title.toLocaleLowerCase())
208
                 window.location.href = link;
210
                 window.location.href = link;
211
+              
209
               }}
212
               }}
210
             >
213
             >
211
               {title}
214
               {title}
216
           {/* Right Section: Search and Profile */}
219
           {/* Right Section: Search and Profile */}
217
           <Box sx={{ display: "flex", alignItems: "center", gap: 1.2, ml: "auto" }}>
220
           <Box sx={{ display: "flex", alignItems: "center", gap: 1.2, ml: "auto" }}>
218
 
221
 
219
-            <FormControl
222
+            {/* <FormControl
220
               sx={{
223
               sx={{
221
                 display: {
224
                 display: {
222
                   xs: "none",
225
                   xs: "none",
241
                 <LanguageSelectItem value="Malay">Malay</LanguageSelectItem>
244
                 <LanguageSelectItem value="Malay">Malay</LanguageSelectItem>
242
                 <LanguageSelectItem value="Chinese">Chinese</LanguageSelectItem>
245
                 <LanguageSelectItem value="Chinese">Chinese</LanguageSelectItem>
243
               </LanguageSelect>
246
               </LanguageSelect>
244
-            </FormControl>
247
+            </FormControl> */}
245
 
248
 
246
             <IconButton color="inherit">
249
             <IconButton color="inherit">
247
               <SearchIcon sx={{ color: "white" }} />
250
               <SearchIcon sx={{ color: "white" }} />
251
               <LocalMallIcon color="action" sx={{ color: "white" }} />
254
               <LocalMallIcon color="action" sx={{ color: "white" }} />
252
             </Badge>
255
             </Badge>
253
 
256
 
254
-            <IconButton color="inherit">
257
+            {/* <IconButton color="inherit">
255
               <AccountCircleIcon sx={{ color: "white" }} />
258
               <AccountCircleIcon sx={{ color: "white" }} />
256
-            </IconButton>
259
+            </IconButton> */}
257
           </Box>
260
           </Box>
258
 
261
 
259
         </Toolbar>
262
         </Toolbar>

+ 179
- 29
src/components/ProductList/ProductList.jsx View File

1
-import React, { useEffect } from 'react';
2
-import { Box, Typography, Button } from '@mui/material';
1
+import { useEffect, useState } from 'react';
2
+import { Box, Typography, Button, FormControl, Select, MenuItem, InputBase } from '@mui/material';
3
 import Grid from '@mui/material/Grid2';
3
 import Grid from '@mui/material/Grid2';
4
-
4
+import { styled } from "@mui/material";
5
 //REDUX
5
 //REDUX
6
 import { useSelector, useDispatch } from 'react-redux';
6
 import { useSelector, useDispatch } from 'react-redux';
7
-import { fetchProducts } from '../../redux/slices/productSlice';
7
+import { fetchProducts, fetchProductsByCollection } from '../../redux/slices/productSlice';
8
+
9
+//UTIL FUNCTION
10
+function getAllTags(data) {
11
+  const products = data || [];
12
+  const allTags = products.flatMap(product => product.tags);
13
+  const uniqueTags = [...new Set(allTags)];
14
+  return uniqueTags;
15
+}
16
+
17
+const BootstrapInput = styled(InputBase)(({ theme }) => ({
18
+  'label + &': {
19
+    marginTop: theme.spacing(3),
20
+  },
21
+  '& .MuiInputBase-input': {
22
+    position: 'relative',
23
+    backgroundColor: "#2E2E2E",
24
+    border: '1px solid #ced4da',
25
+    color: "#FFF",
26
+    fontSize: 13,
27
+    padding: '5px 0',
28
+    paddingRight: '50px !important',
29
+    paddingLeft: "10px",
30
+    transition: theme.transitions.create(['border-color', 'box-shadow']),
31
+    '&:focus': {
32
+      borderRadius: 4,
33
+      borderColor: '#80bdff',
34
+      boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
35
+    },
36
+  },
37
+  '& .MuiSvgIcon-root': {
38
+    color: "#FFF !important"
39
+  },
40
+}));
8
 
41
 
9
 const ProductList = ({ size = 99999 }) => {
42
 const ProductList = ({ size = 99999 }) => {
10
 
43
 
11
-  const products = useSelector((state) => state.products.products.data)
44
+  const products = useSelector((state) => state.products.products.data) // only used as referenced
45
+  const [filteredProducts, setFilteredProducts] = useState([]) // this one is the actual data to be rendered
46
+  const [categoryFilterOption, setCategoryFilterOption] = useState([])
12
   const dispatch = useDispatch();
47
   const dispatch = useDispatch();
13
 
48
 
49
+  //filter
50
+  const [category, setCategory] = useState('all');
51
+  const [sort, setSort] = useState('all')
52
+
14
   useEffect(() => {
53
   useEffect(() => {
15
 
54
 
16
-    dispatch(fetchProducts())
55
+    // if user come from select collection
56
+    if (sessionStorage.getItem('amber-select-collection')) {
57
+
58
+      let { id } = JSON.parse(sessionStorage.getItem('amber-select-collection'))
59
+      dispatch(fetchProductsByCollection({ collectionId: id }))
60
+
61
+    } else if (sessionStorage.getItem('amber-select-category')) {
62
+
63
+    }
17
 
64
 
18
   }, [])
65
   }, [])
19
 
66
 
20
   useEffect(() => {
67
   useEffect(() => {
21
-    if(products) console.log(products)
68
+
69
+    let productType = sessionStorage.getItem('amber-select-product-type')
70
+    let newFilteredProducts = products.filter(
71
+      (product) => product.productType === productType
72
+    );
73
+
74
+    const categoryList = getAllTags(newFilteredProducts) // yes we use tags as category, while I was coding this, this is the only way to implement category
75
+
76
+    setFilteredProducts(newFilteredProducts)
77
+    setCategoryFilterOption(categoryList)
78
+
22
   }, [products])
79
   }, [products])
23
 
80
 
81
+
82
+  useEffect(() => {
83
+
84
+    if (products?.length > 0) {
85
+
86
+      let productType = sessionStorage.getItem('amber-select-product-type')
87
+
88
+      let newFilteredProducts = products.filter(
89
+        (product) => {
90
+
91
+          if(category == 'all'){
92
+            return product.productType === productType
93
+          } else {
94
+            return product.productType === productType && product.tags.includes(category)
95
+          }
96
+
97
+        }
98
+      );
99
+
100
+      setFilteredProducts(newFilteredProducts)
101
+
102
+    }
103
+
104
+  }, [category])
105
+
106
+
107
+
108
+  useEffect(() => {
109
+    if (products) console.log(products)
110
+  }, [products])
111
+
112
+
113
+
114
+
115
+  const handleChange = (event) => {
116
+    //setInput({ ...input, [event.target.name]: event.target.value });
117
+  };
118
+
24
   const renderProduct = (id, img_url, title, collection, price, currency, extra_desc) => {
119
   const renderProduct = (id, img_url, title, collection, price, currency, extra_desc) => {
25
 
120
 
26
     return (
121
     return (
41
               width: '100%',
136
               width: '100%',
42
               aspectRatio: '3 / 4',
137
               aspectRatio: '3 / 4',
43
               objectFit: 'cover',
138
               objectFit: 'cover',
44
-              objectPosition:'top center'
139
+              objectPosition: 'top center'
45
             }}
140
             }}
46
           />
141
           />
47
 
142
 
70
   }
165
   }
71
 
166
 
72
   return (
167
   return (
73
-    <Box sx={{ mb: 5 }}>
74
-      <Grid container spacing={1} columns={12}>
75
-        {products.map((product, index) => {
76
-          
77
-          let {id, title, compareAtPriceRange, images, collections} = product
78
-          let price = compareAtPriceRange.maxVariantPrice.amount
79
-          let currency = compareAtPriceRange.maxVariantPrice.currencyCode
80
-          let img_url = images.nodes[0].url
81
-          let collection_name = collections.nodes[0]?.title
82
-          
83
-          // ID
84
-          const parts = id.split('/');
85
-          let prodID = parts[parts.length - 1];
86
-
87
-
88
-          if (index < size) {
89
-            return renderProduct(prodID, img_url, title, collection_name, price, currency, "" )
90
-          }
168
+    <>
169
+      {/* FILTER */}
170
+      <Box
171
+        sx={{
172
+          display: "flex",
173
+          justifyContent: "space-between",
174
+          alignItems: "center",
175
+          backgroundColor: "background.black",
176
+          color: "white",
177
+          px: 2, // Add padding around the box
178
+          my: 4
179
+        }}
180
+      >
181
+        {/* Left Side: Page Title */}
182
+        <Typography variant="body2">
183
+          {`${filteredProducts.length} Item`}
184
+        </Typography>
185
+
186
+        {/* Right Side: Option Inputs */}
187
+        <Box sx={{ display: "flex", gap: 2 }}>
188
+
189
+          <FormControl sx={{ m: 1, display: "flex", flexDirection: "row" }} variant="standard">
190
+            <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Filter By Category</Typography>
191
+            <Select
192
+              value={category}
193
+              onChange={(event) => {
194
+                setCategory(event.target.value);
195
+              }}
196
+              input={<BootstrapInput />}
197
+              name="type"
198
+            >
199
+              <MenuItem value={'all'}>All</MenuItem>
200
+              {categoryFilterOption?.map((data) => (<MenuItem value={data}>{data}</MenuItem>))}
201
+            </Select>
202
+          </FormControl>
203
+
204
+          <FormControl sx={{ m: 1, display: { xs: "none", sm: "none", md: "flex" }, flexDirection: "row" }} variant="standard">
205
+            <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Sort By</Typography>
206
+            <Select
207
+              value={sort}
208
+              onChange={handleChange}
209
+              input={<BootstrapInput />}
210
+              name="sort"
211
+            >
212
+              <MenuItem defaultValue value={''}>All</MenuItem>
213
+              <MenuItem defaultValue value={'all'}>Newest</MenuItem>
214
+
215
+            </Select>
216
+          </FormControl>
91
 
217
 
92
-        })}
93
-      </Grid>
94
-    </Box>
218
+        </Box>
219
+      </Box>
220
+
221
+      {/* LIST */}
222
+      <Box sx={{ mb: 5 }}>
223
+        <Grid container spacing={1} columns={12}>
224
+          {filteredProducts.map((product, index) => {
225
+
226
+            let { id, title, compareAtPriceRange, images, collections } = product
227
+            let price = compareAtPriceRange.maxVariantPrice.amount
228
+            let currency = compareAtPriceRange.maxVariantPrice.currencyCode
229
+            let img_url = images.nodes[0].url
230
+            let collection_name = collections.nodes[0]?.title
231
+
232
+            // ID
233
+            const parts = id.split('/');
234
+            let prodID = parts[parts.length - 1];
235
+
236
+
237
+            if (index < size) {
238
+              return renderProduct(prodID, img_url, title, collection_name, price, currency, "")
239
+            }
240
+
241
+          })}
242
+        </Grid>
243
+      </Box>
244
+    </>
95
   );
245
   );
96
 };
246
 };
97
 
247
 

+ 0
- 7
src/pages/Collection.jsx View File

14
 const Collection = () => {
14
 const Collection = () => {
15
 
15
 
16
   let { pid } = useParams();
16
   let { pid } = useParams();
17
-  const dispatch = useDispatch();
18
-
19
-  useEffect(()=>{
20
-
21
-   //dispatch(fetchProducts(250, 'created_at',  `product_type:${pid}`))
22
-
23
-  }, [])
24
 
17
 
25
   return (
18
   return (
26
     <>
19
     <>

+ 25
- 3
src/pages/Products/index.jsx View File

1
-import React from 'react'
1
+import {useEffect, useState} from 'react'
2
 import PageTitle from '../../components/PageTitle'
2
 import PageTitle from '../../components/PageTitle'
3
 import Filter from '../../components/Filter'
3
 import Filter from '../../components/Filter'
4
 import ProductList from '../../components/ProductList'
4
 import ProductList from '../../components/ProductList'
7
 import Feature from '../../components/Feature'
7
 import Feature from '../../components/Feature'
8
 
8
 
9
 const Products = () => {
9
 const Products = () => {
10
+
11
+  const [title, setTitle] = useState('')
12
+  const [image, setImage] = useState('')
13
+
14
+  useEffect(()=>{
15
+    
16
+    // if user come from select collection
17
+    if(sessionStorage.getItem('amber-select-collection')){
18
+
19
+      let { id, name, src } = JSON.parse(sessionStorage.getItem('amber-select-collection'))
20
+      setTitle(name)
21
+      setImage(src)
22
+
23
+    }else if(sessionStorage.getItem('amber-select-category')){
24
+
25
+      let categoryData = JSON.parse(sessionStorage.getItem('amber-select-category'))
26
+
27
+    }else{
28
+      window.location.href = '/'
29
+    }
30
+
31
+  },[])
32
+
10
   return (
33
   return (
11
     <>
34
     <>
12
-      <PageTitle />
35
+      <PageTitle title={title} image={image} />
13
       <Box sx={{
36
       <Box sx={{
14
         px: {
37
         px: {
15
           xs: 2,
38
           xs: 2,
22
           lg: 10
45
           lg: 10
23
         }
46
         }
24
       }}>
47
       }}>
25
-        <Filter />
26
         <ProductList />
48
         <ProductList />
27
         <SocialMedia />
49
         <SocialMedia />
28
         <Feature />
50
         <Feature />

+ 23
- 0
src/redux/slices/productSlice.js View File

41
   }
41
   }
42
 )
42
 )
43
 
43
 
44
+export const fetchProductsByCollection = createAsyncThunk(
45
+  'product/fetchProductsByCollection',
46
+  async ({collectionId}) => {
47
+    
48
+    const response = await ProductService.getProductsByCollection(collectionId)
49
+    return response
50
+
51
+  }
52
+)
53
+
44
 export const fetchProductsHistory = createAsyncThunk(
54
 export const fetchProductsHistory = createAsyncThunk(
45
   'product/fetchProductsHistory',
55
   'product/fetchProductsHistory',
46
   async (productIDList) => {
56
   async (productIDList) => {
83
         state.product.data = {};
93
         state.product.data = {};
84
         state.product.error = action.error.message;
94
         state.product.error = action.error.message;
85
       })
95
       })
96
+      .addCase(fetchProductsByCollection.pending, (state) => {
97
+        state.products.status = 'loading';
98
+        state.products.error = null;
99
+      })
100
+      .addCase(fetchProductsByCollection.fulfilled, (state, action) => {
101
+        state.products.status = 'succeeded';
102
+        state.products.data = action.payload
103
+      })
104
+      .addCase(fetchProductsByCollection.rejected, (state, action) => {
105
+        state.products.status = 'failed';
106
+        state.products.data = [];
107
+        state.products.error = action.error.message;
108
+      })
86
       .addCase(fetchProductsHistory.pending, (state) => {
109
       .addCase(fetchProductsHistory.pending, (state) => {
87
         state.productsHistory.status = 'loading';
110
         state.productsHistory.status = 'loading';
88
         state.productsHistory.error = null;
111
         state.productsHistory.error = null;

+ 46
- 2
src/services/ProductService.js View File

65
     },
65
     },
66
   });
66
   });
67
 
67
 
68
-  debugger
69
-
70
   return data.products.nodes
68
   return data.products.nodes
71
 
69
 
72
 }
70
 }
128
 
126
 
129
 }
127
 }
130
 
128
 
129
+const getProductsByCollection = async (collectionId) => {
130
+
131
+  const query = `{
132
+    collection(id: "${collectionId}") {
133
+      products(first: 250) {
134
+        nodes {
135
+          id
136
+          title
137
+          productType
138
+          tags
139
+          compareAtPriceRange {
140
+            maxVariantPrice {
141
+              amount
142
+              currencyCode
143
+            }
144
+            minVariantPrice {
145
+              amount
146
+              currencyCode
147
+            }
148
+          }
149
+          images(first: 4) {
150
+            nodes {
151
+              src
152
+              url
153
+            }
154
+          }
155
+          collections(first: 55) {
156
+            nodes {
157
+              title
158
+            }
159
+          }
160
+        }
161
+      }
162
+    }
163
+  }`
164
+
165
+  const { data, errors, extensions } = await client.request(query, {
166
+    variables: {},
167
+  });
168
+
169
+  return data.collection.products.nodes
170
+}
171
+
131
 const getProductTypes = async () => {
172
 const getProductTypes = async () => {
132
   const query = `{
173
   const query = `{
133
     productTypes(first: 250) {
174
     productTypes(first: 250) {
191
 
232
 
192
 }
233
 }
193
 
234
 
235
+
236
+
194
 const ProductService = {
237
 const ProductService = {
195
   getProducts,
238
   getProducts,
196
   getProduct,
239
   getProduct,
240
+  getProductsByCollection,
197
   getProductTypes,
241
   getProductTypes,
198
   getCollections
242
   getCollections
199
 }
243
 }

Loading…
Cancel
Save