added filter to category page
This commit is contained in:
59
package-lock.json
generated
59
package-lock.json
generated
@@ -173,6 +173,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.26.0",
|
"@babel/code-frame": "^7.26.0",
|
||||||
@@ -1084,7 +1085,6 @@
|
|||||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
"@jridgewell/trace-mapping": "^0.3.25"
|
"@jridgewell/trace-mapping": "^0.3.25"
|
||||||
@@ -1574,17 +1574,6 @@
|
|||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
|
||||||
"version": "22.10.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
|
||||||
"integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"undici-types": "~6.20.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/prop-types": {
|
"node_modules/@types/prop-types": {
|
||||||
"version": "15.7.14",
|
"version": "15.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
@@ -1596,6 +1585,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
@@ -1639,6 +1629,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -1935,6 +1926,7 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001688",
|
"caniuse-lite": "^1.0.30001688",
|
||||||
"electron-to-chromium": "^1.5.73",
|
"electron-to-chromium": "^1.5.73",
|
||||||
@@ -1959,8 +1951,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/call-bind": {
|
"node_modules/call-bind": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
@@ -2088,8 +2079,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/compute-scroll-into-view": {
|
"node_modules/compute-scroll-into-view": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -2207,7 +2197,8 @@
|
|||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.13",
|
"version": "1.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
@@ -2527,6 +2518,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz",
|
||||||
"integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
|
"integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3101,6 +3093,7 @@
|
|||||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.23.2"
|
"@babel/runtime": "^7.23.2"
|
||||||
},
|
},
|
||||||
@@ -7320,6 +7313,7 @@
|
|||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
@@ -7331,6 +7325,7 @@
|
|||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.2"
|
"scheduler": "^0.23.2"
|
||||||
@@ -7399,6 +7394,7 @@
|
|||||||
"version": "9.2.0",
|
"version": "9.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/use-sync-external-store": "^0.0.6",
|
"@types/use-sync-external-store": "^0.0.6",
|
||||||
"use-sync-external-store": "^1.4.0"
|
"use-sync-external-store": "^1.4.0"
|
||||||
@@ -7467,7 +7463,8 @@
|
|||||||
"node_modules/redux": {
|
"node_modules/redux": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@@ -8213,7 +8210,6 @@
|
|||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -8233,7 +8229,6 @@
|
|||||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-from": "^1.0.0",
|
"buffer-from": "^1.0.0",
|
||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
@@ -8417,26 +8412,6 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
|
||||||
"version": "5.37.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
|
|
||||||
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
|
||||||
"acorn": "^8.8.2",
|
|
||||||
"commander": "^2.20.0",
|
|
||||||
"source-map-support": "~0.5.20"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"terser": "bin/terser"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/throttle-debounce": {
|
"node_modules/throttle-debounce": {
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
|
||||||
@@ -8569,8 +8544,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -8630,6 +8604,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
|
||||||
"integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==",
|
"integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.24.2",
|
"esbuild": "^0.24.2",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ export const brandsApi = baseApi.injectEndpoints({
|
|||||||
if (params.page) {
|
if (params.page) {
|
||||||
queryParams.append("page", params.page);
|
queryParams.append("page", params.page);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.limit) {
|
if (params.limit) {
|
||||||
queryParams.append("limit", params.limit);
|
queryParams.append("limit", params.limit);
|
||||||
}
|
}
|
||||||
@@ -29,12 +28,10 @@ export const brandsApi = baseApi.injectEndpoints({
|
|||||||
|
|
||||||
getBrandProducts: builder.query({
|
getBrandProducts: builder.query({
|
||||||
query: (params) => {
|
query: (params) => {
|
||||||
// Handle both string ID and object with pagination params
|
|
||||||
if (typeof params === 'string' || typeof params === 'number') {
|
if (typeof params === 'string' || typeof params === 'number') {
|
||||||
return `/brands/${params}/products`;
|
return `/brands/${params}/products`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle object with pagination
|
|
||||||
const { id, page = 1, limit } = params;
|
const { id, page = 1, limit } = params;
|
||||||
let url = `/brands/${id}/products?page=${page}`;
|
let url = `/brands/${id}/products?page=${page}`;
|
||||||
|
|
||||||
@@ -44,9 +41,10 @@ export const brandsApi = baseApi.injectEndpoints({
|
|||||||
|
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
transformResponse: (response) => {
|
transformResponse: (response) => ({
|
||||||
return response.data || response;
|
data: response.data || response,
|
||||||
},
|
pagination: response.pagination || {},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,17 +5,24 @@ export const categoriesApi = baseApi.injectEndpoints({
|
|||||||
getCategories: builder.query({
|
getCategories: builder.query({
|
||||||
query: (type = "tree") => `/categories?type=${type}`,
|
query: (type = "tree") => `/categories?type=${type}`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getCategoryProducts: builder.query({
|
getCategoryProducts: builder.query({
|
||||||
query: ({ categoryId, page = 1, limit }) => {
|
query: ({ categoryId, page = 1, limit, brands, min_price, max_price }) => {
|
||||||
let url = `categories/${categoryId}/products?page=${page}`;
|
const params = new URLSearchParams();
|
||||||
if (limit) url += `&limit=${limit}`;
|
params.append('page', page);
|
||||||
return url;
|
if (limit) params.append('limit', limit);
|
||||||
|
if (brands) params.append('brands', brands);
|
||||||
|
if (min_price) params.append('min_price', min_price);
|
||||||
|
if (max_price) params.append('max_price', max_price);
|
||||||
|
|
||||||
|
return `categories/${categoryId}/products?${params.toString()}`;
|
||||||
},
|
},
|
||||||
transformResponse: (response) => ({
|
transformResponse: (response) => ({
|
||||||
data: response.data || [],
|
data: response.data || [],
|
||||||
pagination: response.pagination || {},
|
pagination: response.pagination || {},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAllCategoryProducts: builder.query({
|
getAllCategoryProducts: builder.query({
|
||||||
async queryFn(category, queryApi, extraOptions, baseQuery) {
|
async queryFn(category, queryApi, extraOptions, baseQuery) {
|
||||||
const fetchProducts = async (categoryId) => {
|
const fetchProducts = async (categoryId) => {
|
||||||
@@ -36,7 +43,7 @@ export const categoriesApi = baseApi.injectEndpoints({
|
|||||||
|
|
||||||
getAllCategoryProductsPaginated: builder.query({
|
getAllCategoryProductsPaginated: builder.query({
|
||||||
async queryFn(
|
async queryFn(
|
||||||
{ category, page = 1, limit = 6 },
|
{ category, page = 1, limit = 6, brands, min_price, max_price },
|
||||||
queryApi,
|
queryApi,
|
||||||
extraOptions,
|
extraOptions,
|
||||||
baseQuery
|
baseQuery
|
||||||
@@ -51,14 +58,20 @@ export const categoriesApi = baseApi.injectEndpoints({
|
|||||||
const perCategoryLimit = Math.ceil(limit / categoryIds.length);
|
const perCategoryLimit = Math.ceil(limit / categoryIds.length);
|
||||||
|
|
||||||
for (const categoryId of categoryIds) {
|
for (const categoryId of categoryIds) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', currentPage);
|
||||||
|
params.append('limit', perCategoryLimit);
|
||||||
|
if (brands) params.append('brands', brands);
|
||||||
|
if (min_price) params.append('min_price', min_price);
|
||||||
|
if (max_price) params.append('max_price', max_price);
|
||||||
|
|
||||||
const result = await baseQuery(
|
const result = await baseQuery(
|
||||||
`categories/${categoryId}/products?page=${currentPage}&limit=${perCategoryLimit}`
|
`categories/${categoryId}/products?${params.toString()}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.data && result.data.data) {
|
if (result.data && result.data.data) {
|
||||||
allPageProducts = [...allPageProducts, ...result.data.data];
|
allPageProducts = [...allPageProducts, ...result.data.data];
|
||||||
|
hasMoreByCategory[categoryId] = !!result.data.pagination.next_page_url;
|
||||||
hasMoreByCategory[categoryId] =
|
|
||||||
!!result.data.pagination.next_page_url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,9 +103,11 @@ export const categoriesApi = baseApi.injectEndpoints({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getProductById: builder.query({
|
getProductById: builder.query({
|
||||||
query: (productId) => `/products/${productId}`,
|
query: (productId) => `/products/${productId}`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getRelatedProducts: builder.query({
|
getRelatedProducts: builder.query({
|
||||||
query: (productId) => `/products/${productId}/related`,
|
query: (productId) => `/products/${productId}/related`,
|
||||||
}),
|
}),
|
||||||
@@ -107,4 +122,4 @@ export const {
|
|||||||
useLazyGetAllCategoryProductsPaginatedQuery,
|
useLazyGetAllCategoryProductsPaginatedQuery,
|
||||||
useGetProductByIdQuery,
|
useGetProductByIdQuery,
|
||||||
useGetRelatedProductsQuery,
|
useGetRelatedProductsQuery,
|
||||||
} = categoriesApi;
|
} = categoriesApi;
|
||||||
@@ -5,12 +5,13 @@ export const collectionsApi = baseApi.injectEndpoints({
|
|||||||
getCollections: builder.query({
|
getCollections: builder.query({
|
||||||
query: () => `/collections`,
|
query: () => `/collections`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getCollectionById: builder.query({
|
getCollectionById: builder.query({
|
||||||
query: (collectionId) => `/collections/${collectionId}`,
|
query: (collectionId) => `/collections/${collectionId}`,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getCollectionProducts: builder.query({
|
getCollectionProducts: builder.query({
|
||||||
query: (collectionId) => `/collections/${collectionId}/products`,
|
query: (collectionId) => `/collections/${collectionId}/products`,
|
||||||
// Ürünleri dönüştürerek boş kontrol edilebilir
|
|
||||||
transformResponse: (response) => {
|
transformResponse: (response) => {
|
||||||
return {
|
return {
|
||||||
data: response.data || [],
|
data: response.data || [],
|
||||||
@@ -18,7 +19,7 @@ export const collectionsApi = baseApi.injectEndpoints({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// Yeni endpoint: Koleksiyonun ürün içerip içermediğini kontrol eder
|
|
||||||
checkCollectionHasProducts: builder.query({
|
checkCollectionHasProducts: builder.query({
|
||||||
query: (collectionId) => `/collections/${collectionId}/products?limit=1`,
|
query: (collectionId) => `/collections/${collectionId}/products?limit=1`,
|
||||||
transformResponse: (response) => {
|
transformResponse: (response) => {
|
||||||
@@ -27,10 +28,18 @@ export const collectionsApi = baseApi.injectEndpoints({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// Sayfalı koleksiyon ürünleri için endpoint
|
|
||||||
getCollectionProductsPaginated: builder.query({
|
getCollectionProductsPaginated: builder.query({
|
||||||
query: ({ collectionId, page = 1, limit = 6 }) =>
|
query: ({ collectionId, page = 1, limit = 6, brands, min_price, max_price }) => {
|
||||||
`/collections/${collectionId}/products?page=${page}${limit ? `&limit=${limit}` : ''}`,
|
const params = new URLSearchParams();
|
||||||
|
params.append('page', page);
|
||||||
|
if (limit) params.append('limit', limit);
|
||||||
|
if (brands) params.append('brands', brands);
|
||||||
|
if (min_price) params.append('min_price', min_price);
|
||||||
|
if (max_price) params.append('max_price', max_price);
|
||||||
|
|
||||||
|
return `/collections/${collectionId}/products?${params.toString()}`;
|
||||||
|
},
|
||||||
transformResponse: (response) => ({
|
transformResponse: (response) => ({
|
||||||
data: response.data || [],
|
data: response.data || [],
|
||||||
pagination: response.pagination || {},
|
pagination: response.pagination || {},
|
||||||
|
|||||||
77
src/app/api/filtersApi.js
Normal file
77
src/app/api/filtersApi.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { baseApi } from "./baseApi"
|
||||||
|
|
||||||
|
export const filtersApi = baseApi.injectEndpoints({
|
||||||
|
endpoints: (builder) => ({
|
||||||
|
getFilters: builder.query({
|
||||||
|
query: (params) => {
|
||||||
|
const queryParams = new URLSearchParams()
|
||||||
|
|
||||||
|
if (params?.category_id) {
|
||||||
|
queryParams.append("category_id", String(params.category_id))
|
||||||
|
}
|
||||||
|
if (params?.collection_id) {
|
||||||
|
queryParams.append("collection_id", String(params.collection_id))
|
||||||
|
}
|
||||||
|
if (params?.brand_id) {
|
||||||
|
queryParams.append("brand_id", String(params.brand_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
return `/filters?${queryParams.toString()}`
|
||||||
|
},
|
||||||
|
transformResponse: (response) => {
|
||||||
|
return {
|
||||||
|
categories: response.data?.categories || [],
|
||||||
|
brands: response.data?.brands || [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keepUnusedDataFor: 300,
|
||||||
|
|
||||||
|
serializeQueryArgs: ({ queryArgs }) => {
|
||||||
|
if (!queryArgs) return 'no-params';
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
if (queryArgs.category_id) {
|
||||||
|
parts.push(`cat:${queryArgs.category_id}`);
|
||||||
|
}
|
||||||
|
if (queryArgs.collection_id) {
|
||||||
|
parts.push(`col:${queryArgs.collection_id}`);
|
||||||
|
}
|
||||||
|
if (queryArgs.brand_id) {
|
||||||
|
parts.push(`brd:${queryArgs.brand_id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.length > 0 ? parts.join('|') : 'no-params';
|
||||||
|
},
|
||||||
|
|
||||||
|
merge: (currentCache, newItems) => {
|
||||||
|
return newItems;
|
||||||
|
},
|
||||||
|
|
||||||
|
refetchOnMountOrArgChange: 30, // Refetch if older than 30 seconds
|
||||||
|
refetchOnFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
|
||||||
|
providesTags: (result, error, arg) => {
|
||||||
|
if (!arg) return [{ type: "Filters", id: "no-params" }];
|
||||||
|
|
||||||
|
const tags = [{ type: "Filters", id: "LIST" }];
|
||||||
|
|
||||||
|
if (arg.category_id) {
|
||||||
|
tags.push({ type: "Filters", id: `cat-${arg.category_id}` });
|
||||||
|
}
|
||||||
|
if (arg.collection_id) {
|
||||||
|
tags.push({ type: "Filters", id: `col-${arg.collection_id}` });
|
||||||
|
}
|
||||||
|
if (arg.brand_id) {
|
||||||
|
tags.push({ type: "Filters", id: `brd-${arg.brand_id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
overrideExisting: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { useGetFiltersQuery, useLazyGetFiltersQuery } = filtersApi
|
||||||
@@ -13,6 +13,7 @@ import { mediaApi } from "./api/bannersApi";
|
|||||||
import { reviewsApi } from "./api/reviewApi";
|
import { reviewsApi } from "./api/reviewApi";
|
||||||
import { profileApi } from "./api/myProfileApi";
|
import { profileApi } from "./api/myProfileApi";
|
||||||
import { contactApi } from "./api/contactUs";
|
import { contactApi } from "./api/contactUs";
|
||||||
|
import { filtersApi } from "./api/filtersApi";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
@@ -30,6 +31,7 @@ const store = configureStore({
|
|||||||
[reviewsApi.reducerPath]: reviewsApi.reducer,
|
[reviewsApi.reducerPath]: reviewsApi.reducer,
|
||||||
[profileApi.reducerPath]: profileApi.reducer,
|
[profileApi.reducerPath]: profileApi.reducer,
|
||||||
[contactApi.reducerPath]: contactApi.reducer,
|
[contactApi.reducerPath]: contactApi.reducer,
|
||||||
|
[filtersApi.reducerPath]: filtersApi.reducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware().concat(
|
getDefaultMiddleware().concat(
|
||||||
@@ -46,6 +48,7 @@ const store = configureStore({
|
|||||||
mediaApi.middleware,
|
mediaApi.middleware,
|
||||||
profileApi.middleware,
|
profileApi.middleware,
|
||||||
contactApi.middleware,
|
contactApi.middleware,
|
||||||
|
filtersApi.middleware
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,18 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState, useMemo } from "react";
|
||||||
import { Drawer, Input, Checkbox } from "antd";
|
import { Drawer, Input, Checkbox } from "antd";
|
||||||
import styles from "./BrandsSidebar.module.scss";
|
import styles from "./BrandsSidebar.module.scss";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import brand from "../../assets/icons/brand.svg";
|
import brand from "../../assets/icons/brand.svg";
|
||||||
|
|
||||||
const Sidebar = () => {
|
const BrandSidebar = ({
|
||||||
|
brands = [],
|
||||||
|
selectedBrand = null,
|
||||||
|
onBrandSelect,
|
||||||
|
onBrandDeselect
|
||||||
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const { t, i18n } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const brands = [
|
|
||||||
"Abat",
|
|
||||||
"Altın",
|
|
||||||
"Arçalyk",
|
|
||||||
"Aýaz baba",
|
|
||||||
"Balşeker",
|
|
||||||
"Bars",
|
|
||||||
"Belet Film",
|
|
||||||
"Beýlekiler / Другие",
|
|
||||||
"Bingo",
|
|
||||||
"Bold",
|
|
||||||
"Carte Noire",
|
|
||||||
"Çaykur",
|
|
||||||
"Dabara",
|
|
||||||
"Datmeni",
|
|
||||||
"Elin",
|
|
||||||
"Emin Et",
|
|
||||||
"Enemeli",
|
|
||||||
"Ermak",
|
|
||||||
"Eyfel",
|
|
||||||
"Familia",
|
|
||||||
"Farmasi",
|
|
||||||
"Ferrero Rocher",
|
|
||||||
"Granum",
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleToggle = () => {
|
const handleToggle = () => {
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
@@ -42,15 +22,28 @@ const Sidebar = () => {
|
|||||||
setSearchTerm(e.target.value.toLowerCase());
|
setSearchTerm(e.target.value.toLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredBrands = brands.filter((brand) =>
|
const filteredBrands = useMemo(() => {
|
||||||
brand.toLowerCase().includes(searchTerm)
|
if (!brands || brands.length === 0) return [];
|
||||||
);
|
|
||||||
|
return brands.filter((brand) =>
|
||||||
|
brand.name.toLowerCase().includes(searchTerm)
|
||||||
|
);
|
||||||
|
}, [brands, searchTerm]);
|
||||||
|
|
||||||
|
const handleBrandChange = (brandId, checked) => {
|
||||||
|
if (checked) {
|
||||||
|
onBrandSelect?.(brandId);
|
||||||
|
} else {
|
||||||
|
onBrandDeselect?.();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.sidebarContainer}>
|
<div className={styles.sidebarContainer}>
|
||||||
<button onClick={handleToggle} className={styles.mobileNavButton}>
|
<button onClick={handleToggle} className={styles.mobileNavButton}>
|
||||||
<img src={brand} alt="" />
|
<img src={brand} alt="" />
|
||||||
{t("navbar.brands")}
|
{t("navbar.brands")}
|
||||||
|
{selectedBrand && <span className={styles.badge}>1</span>}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
@@ -67,16 +60,30 @@ const Sidebar = () => {
|
|||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
className={styles.searchInput}
|
className={styles.searchInput}
|
||||||
/>
|
/>
|
||||||
<div className={styles.brandsList}>
|
|
||||||
{filteredBrands.map((brand, index) => (
|
{filteredBrands.length > 0 ? (
|
||||||
<div key={index} className={styles.brandItem}>
|
<div className={styles.brandsList}>
|
||||||
<Checkbox>{brand}</Checkbox>
|
{filteredBrands.map((brand) => (
|
||||||
</div>
|
<div key={brand.id} className={styles.brandItem}>
|
||||||
))}
|
<Checkbox
|
||||||
</div>
|
checked={selectedBrand === brand.id}
|
||||||
|
onChange={(e) => handleBrandChange(brand.id, e.target.checked)}
|
||||||
|
>
|
||||||
|
{brand.name}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.emptyState}>
|
||||||
|
{brands.length === 0
|
||||||
|
? t("common.noData")
|
||||||
|
: t("common.noResults")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default BrandSidebar;
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px 1.375rem;
|
padding: 20px 1.375rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
flex-direction: column;
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
59
src/pages/Category/components/CategoryBreadcrumbs.jsx
Normal file
59
src/pages/Category/components/CategoryBreadcrumbs.jsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { Breadcrumb } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "../CategoryPage.module.scss";
|
||||||
|
|
||||||
|
const CategoryBreadcrumbs = ({ categoriesData, categoryId, onCategoryClick }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const breadcrumbs = useMemo(() => {
|
||||||
|
if (!categoriesData?.data || !categoryId) return [];
|
||||||
|
|
||||||
|
const buildPath = (categories, targetId, path = []) => {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.id === parseInt(targetId)) {
|
||||||
|
return [...path, category];
|
||||||
|
}
|
||||||
|
if (category.children) {
|
||||||
|
const found = buildPath(category.children, targetId, [...path, category]);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return buildPath(categoriesData.data, categoryId) || [];
|
||||||
|
}, [categoriesData, categoryId]);
|
||||||
|
|
||||||
|
if (breadcrumbs.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb className={styles.breadcrumb}>
|
||||||
|
|
||||||
|
|
||||||
|
{breadcrumbs.map((crumb, index) => {
|
||||||
|
const isLast = index === breadcrumbs.length - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumb.Item key={crumb.id}>
|
||||||
|
{isLast ? (
|
||||||
|
<span>{crumb.name}</span>
|
||||||
|
) : (
|
||||||
|
<a
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onCategoryClick?.(crumb.id);
|
||||||
|
}}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
>
|
||||||
|
{crumb.name}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Breadcrumb>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryBreadcrumbs;
|
||||||
98
src/pages/Category/components/CategoryFilters.jsx
Normal file
98
src/pages/Category/components/CategoryFilters.jsx
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TiTick } from "react-icons/ti";
|
||||||
|
import styles from "../CategoryPage.module.scss";
|
||||||
|
|
||||||
|
const CategoryFilters = ({
|
||||||
|
filtersData,
|
||||||
|
selectedFilterCategory,
|
||||||
|
selectedFilterBrand,
|
||||||
|
brandSearchQuery,
|
||||||
|
searchQuery,
|
||||||
|
onCategorySelect,
|
||||||
|
onCategoryDeselect,
|
||||||
|
onBrandSelect,
|
||||||
|
onBrandDeselect,
|
||||||
|
onBrandSearchChange,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (searchQuery) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className={styles.sidebar}>
|
||||||
|
{filtersData?.categories?.length > 0 && (
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>{t("category.subCategories")}</h3>
|
||||||
|
<ul>
|
||||||
|
{filtersData.categories.map((category) => (
|
||||||
|
<li
|
||||||
|
key={category.id}
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedFilterCategory === category.id) {
|
||||||
|
onCategoryDeselect();
|
||||||
|
} else {
|
||||||
|
onCategorySelect(category.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
selectedFilterCategory === category.id
|
||||||
|
? styles.activeSubcategory
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{category.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{filtersData?.brands?.length > 0 && (
|
||||||
|
<div className={styles.filterSection}>
|
||||||
|
<h3>{t("navbar.brands")}</h3>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Gözleg"
|
||||||
|
value={brandSearchQuery}
|
||||||
|
onChange={(e) => onBrandSearchChange(e.target.value)}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{filtersData.brands
|
||||||
|
.filter((brand) =>
|
||||||
|
brand.name
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(brandSearchQuery.toLowerCase())
|
||||||
|
)
|
||||||
|
.map((brand) => (
|
||||||
|
<li key={brand.id}>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedFilterBrand === brand.id}
|
||||||
|
onChange={() => {
|
||||||
|
if (selectedFilterBrand === brand.id) {
|
||||||
|
onBrandDeselect();
|
||||||
|
} else {
|
||||||
|
onBrandSelect(brand.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className={styles.customCheckbox}>
|
||||||
|
<TiTick className={styles.checkIcon} />
|
||||||
|
</span>
|
||||||
|
{brand.name}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryFilters;
|
||||||
110
src/pages/Category/hooks/useCategoryData.js
Normal file
110
src/pages/Category/hooks/useCategoryData.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { useGetCategoriesQuery } from "../../../app/api/categories";
|
||||||
|
import { useGetCollectionByIdQuery } from "../../../app/api/collectionsApi";
|
||||||
|
import {
|
||||||
|
useGetFiltersQuery,
|
||||||
|
useLazyGetFiltersQuery,
|
||||||
|
} from "../../../app/api/filtersApi";
|
||||||
|
|
||||||
|
const useCategoryData = ({
|
||||||
|
categoryId,
|
||||||
|
collectionId,
|
||||||
|
brandId,
|
||||||
|
selectedFilterCategory,
|
||||||
|
searchQuery,
|
||||||
|
}) => {
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState(null);
|
||||||
|
|
||||||
|
const { data: categoriesData } = useGetCategoriesQuery("tree");
|
||||||
|
|
||||||
|
const filterParams = useMemo(() => {
|
||||||
|
if (searchQuery) return null;
|
||||||
|
if (selectedFilterCategory) return { category_id: selectedFilterCategory };
|
||||||
|
if (categoryId) return { category_id: categoryId };
|
||||||
|
if (collectionId) return { collection_id: collectionId };
|
||||||
|
if (brandId) return { brand_id: brandId };
|
||||||
|
return null;
|
||||||
|
}, [categoryId, collectionId, brandId, selectedFilterCategory, searchQuery]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: filtersData,
|
||||||
|
isLoading: filtersLoading,
|
||||||
|
error: filtersError,
|
||||||
|
} = useGetFiltersQuery(filterParams, {
|
||||||
|
skip: !filterParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [fetchFilters, { data: lazyFiltersData }] = useLazyGetFiltersQuery();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: collectionData,
|
||||||
|
isLoading: collectionLoading,
|
||||||
|
error: collectionError,
|
||||||
|
} = useGetCollectionByIdQuery(collectionId, {
|
||||||
|
skip: !collectionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSubCategory = useMemo(() => {
|
||||||
|
if (!categoriesData?.data || !categoryId) return false;
|
||||||
|
|
||||||
|
const checkIsSubCategory = (categories, targetId) => {
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.children) {
|
||||||
|
for (const subCategory of category.children) {
|
||||||
|
if (subCategory.id === parseInt(targetId)) return true;
|
||||||
|
if (subCategory.children) {
|
||||||
|
const found = checkIsSubCategory([subCategory], targetId);
|
||||||
|
if (found) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return checkIsSubCategory(categoriesData.data, parseInt(categoryId));
|
||||||
|
}, [categoriesData, categoryId]);
|
||||||
|
|
||||||
|
const activeFilters = useMemo(() => {
|
||||||
|
return selectedFilterCategory && lazyFiltersData
|
||||||
|
? lazyFiltersData
|
||||||
|
: filtersData;
|
||||||
|
}, [selectedFilterCategory, lazyFiltersData, filtersData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!categoryId || !categoriesData?.data) {
|
||||||
|
setSelectedCategory(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const findCategory = (categories, targetId) => {
|
||||||
|
for (const cat of categories) {
|
||||||
|
if (cat.id === parseInt(targetId)) return cat;
|
||||||
|
if (cat.children) {
|
||||||
|
const found = findCategory(cat.children, targetId);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const category = findCategory(categoriesData.data, parseInt(categoryId));
|
||||||
|
setSelectedCategory(category);
|
||||||
|
}, [categoryId, categoriesData]);
|
||||||
|
|
||||||
|
const isLoading = filtersLoading || collectionLoading;
|
||||||
|
const hasError = filtersError || collectionError;
|
||||||
|
|
||||||
|
return {
|
||||||
|
categoriesData,
|
||||||
|
selectedCategory,
|
||||||
|
isSubCategory,
|
||||||
|
filtersData: activeFilters,
|
||||||
|
collectionData,
|
||||||
|
isLoading,
|
||||||
|
hasError,
|
||||||
|
fetchFilters,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCategoryData;
|
||||||
316
src/pages/Category/hooks/useCategoryProducts.js
Normal file
316
src/pages/Category/hooks/useCategoryProducts.js
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
import { useState, useEffect, useMemo, useRef } from "react";
|
||||||
|
import {
|
||||||
|
useGetCategoryProductsQuery,
|
||||||
|
useLazyGetAllCategoryProductsPaginatedQuery,
|
||||||
|
} from "../../../app/api/categories";
|
||||||
|
import { useLazyGetBrandProductsQuery } from "../../../app/api/brandsApi";
|
||||||
|
import { useLazyGetCollectionProductsPaginatedQuery } from "../../../app/api/collectionsApi";
|
||||||
|
|
||||||
|
const useCategoryProducts = ({
|
||||||
|
categoryId,
|
||||||
|
collectionId,
|
||||||
|
brandId,
|
||||||
|
selectedCategory,
|
||||||
|
isSubCategory,
|
||||||
|
currentPage,
|
||||||
|
selectedFilterCategory,
|
||||||
|
selectedFilterBrand,
|
||||||
|
minPrice,
|
||||||
|
maxPrice,
|
||||||
|
searchQuery,
|
||||||
|
}) => {
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|
||||||
|
const isFetchingRef = useRef(false);
|
||||||
|
const lastFetchKeyRef = useRef(null);
|
||||||
|
const abortControllerRef = useRef(null);
|
||||||
|
|
||||||
|
const contextId = useMemo(() => {
|
||||||
|
const parts = [
|
||||||
|
selectedFilterCategory && `fcat-${selectedFilterCategory}`,
|
||||||
|
categoryId && `cat-${categoryId}`,
|
||||||
|
brandId && `brand-${brandId}`,
|
||||||
|
collectionId && `col-${collectionId}`,
|
||||||
|
selectedFilterBrand && `fbrand-${selectedFilterBrand}`,
|
||||||
|
].filter(Boolean);
|
||||||
|
return parts.join("|") || "none";
|
||||||
|
}, [
|
||||||
|
selectedFilterCategory,
|
||||||
|
categoryId,
|
||||||
|
brandId,
|
||||||
|
collectionId,
|
||||||
|
selectedFilterBrand,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fetchParams = useMemo(
|
||||||
|
() => ({
|
||||||
|
page: currentPage,
|
||||||
|
limit: 6,
|
||||||
|
brands: selectedFilterBrand || undefined,
|
||||||
|
min_price: minPrice || undefined,
|
||||||
|
max_price: maxPrice || undefined,
|
||||||
|
}),
|
||||||
|
[currentPage, selectedFilterBrand, minPrice, maxPrice]
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchKey = `${contextId}-p${currentPage}`;
|
||||||
|
|
||||||
|
const shouldUseBaseQuery =
|
||||||
|
categoryId &&
|
||||||
|
!isSubCategory &&
|
||||||
|
!searchQuery &&
|
||||||
|
!selectedFilterCategory &&
|
||||||
|
!selectedFilterBrand &&
|
||||||
|
!brandId &&
|
||||||
|
!collectionId;
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: paginatedCategoryProducts,
|
||||||
|
isLoading: categoryLoading,
|
||||||
|
isFetching: categoryFetching,
|
||||||
|
} = useGetCategoryProductsQuery(
|
||||||
|
{
|
||||||
|
categoryId: categoryId,
|
||||||
|
page: currentPage,
|
||||||
|
min_price: minPrice || undefined,
|
||||||
|
max_price: maxPrice || undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skip: !shouldUseBaseQuery,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const [
|
||||||
|
fetchCategoryPaginated,
|
||||||
|
{
|
||||||
|
data: lazyCategoryProducts,
|
||||||
|
isLoading: lazyCategoryLoading,
|
||||||
|
isFetching: lazyCategoryFetching,
|
||||||
|
reset: resetCategoryPaginated,
|
||||||
|
},
|
||||||
|
] = useLazyGetAllCategoryProductsPaginatedQuery();
|
||||||
|
|
||||||
|
const [
|
||||||
|
fetchBrandPaginated,
|
||||||
|
{
|
||||||
|
data: paginatedBrandProducts,
|
||||||
|
isLoading: brandPaginatedLoading,
|
||||||
|
isFetching: brandFetching,
|
||||||
|
reset: resetBrandPaginated,
|
||||||
|
},
|
||||||
|
] = useLazyGetBrandProductsQuery();
|
||||||
|
|
||||||
|
const [
|
||||||
|
fetchCollectionPaginated,
|
||||||
|
{
|
||||||
|
data: paginatedCollectionProducts,
|
||||||
|
isLoading: collectionPaginatedLoading,
|
||||||
|
isFetching: collectionFetching,
|
||||||
|
reset: resetCollectionPaginated,
|
||||||
|
},
|
||||||
|
] = useLazyGetCollectionProductsPaginatedQuery();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProducts([]);
|
||||||
|
setHasMore(true);
|
||||||
|
|
||||||
|
resetCategoryPaginated?.();
|
||||||
|
resetBrandPaginated?.();
|
||||||
|
resetCollectionPaginated?.();
|
||||||
|
|
||||||
|
lastFetchKeyRef.current = null;
|
||||||
|
isFetchingRef.current = false;
|
||||||
|
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
abortControllerRef.current = null;
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
contextId,
|
||||||
|
resetCategoryPaginated,
|
||||||
|
resetBrandPaginated,
|
||||||
|
resetCollectionPaginated,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQuery) return;
|
||||||
|
|
||||||
|
if (lastFetchKeyRef.current === fetchKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetchingRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
abortControllerRef.current = new AbortController();
|
||||||
|
isFetchingRef.current = true;
|
||||||
|
lastFetchKeyRef.current = fetchKey;
|
||||||
|
|
||||||
|
const executeFetch = async () => {
|
||||||
|
try {
|
||||||
|
if (selectedFilterBrand) {
|
||||||
|
await fetchBrandPaginated({
|
||||||
|
id: selectedFilterBrand,
|
||||||
|
...fetchParams,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilterCategory) {
|
||||||
|
await fetchCategoryPaginated({
|
||||||
|
category: {
|
||||||
|
id: selectedFilterCategory,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
...fetchParams,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryId && isSubCategory) {
|
||||||
|
await fetchCategoryPaginated({
|
||||||
|
category: {
|
||||||
|
id: parseInt(categoryId),
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
...fetchParams,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brandId) {
|
||||||
|
await fetchBrandPaginated({
|
||||||
|
id: brandId,
|
||||||
|
...fetchParams,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionId) {
|
||||||
|
await fetchCollectionPaginated({
|
||||||
|
collectionId,
|
||||||
|
...fetchParams,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name !== "AbortError") {
|
||||||
|
console.error("Fetch error:", error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isFetchingRef.current = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
executeFetch();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (abortControllerRef.current) {
|
||||||
|
abortControllerRef.current.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
fetchKey,
|
||||||
|
searchQuery,
|
||||||
|
selectedFilterBrand,
|
||||||
|
selectedFilterCategory,
|
||||||
|
categoryId,
|
||||||
|
isSubCategory,
|
||||||
|
brandId,
|
||||||
|
collectionId,
|
||||||
|
fetchParams,
|
||||||
|
fetchCategoryPaginated,
|
||||||
|
fetchBrandPaginated,
|
||||||
|
fetchCollectionPaginated,
|
||||||
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateProducts = (newData, hasNextPage) => {
|
||||||
|
if (!newData || newData.length === 0) {
|
||||||
|
if (currentPage === 1) {
|
||||||
|
setProducts([]);
|
||||||
|
setHasMore(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProducts((prev) => {
|
||||||
|
if (currentPage === 1) {
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingIds = new Set(prev.map((p) => p.id));
|
||||||
|
const newProducts = newData.filter((p) => !existingIds.has(p.id));
|
||||||
|
|
||||||
|
return newProducts.length > 0 ? [...prev, ...newProducts] : prev;
|
||||||
|
});
|
||||||
|
|
||||||
|
setHasMore(hasNextPage);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (paginatedCategoryProducts && shouldUseBaseQuery) {
|
||||||
|
updateProducts(
|
||||||
|
paginatedCategoryProducts.data || [],
|
||||||
|
!!paginatedCategoryProducts.pagination?.next_page_url
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lazyCategoryProducts) {
|
||||||
|
updateProducts(
|
||||||
|
lazyCategoryProducts.data || [],
|
||||||
|
lazyCategoryProducts.pagination?.hasMorePages || false
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Brand products
|
||||||
|
if (paginatedBrandProducts) {
|
||||||
|
updateProducts(
|
||||||
|
paginatedBrandProducts.data || [],
|
||||||
|
!!paginatedBrandProducts.pagination?.next_page_url
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paginatedCollectionProducts) {
|
||||||
|
updateProducts(
|
||||||
|
paginatedCollectionProducts.data || [],
|
||||||
|
!!paginatedCollectionProducts.pagination?.next_page_url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
paginatedCategoryProducts,
|
||||||
|
lazyCategoryProducts,
|
||||||
|
paginatedBrandProducts,
|
||||||
|
paginatedCollectionProducts,
|
||||||
|
currentPage,
|
||||||
|
shouldUseBaseQuery,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
categoryLoading ||
|
||||||
|
lazyCategoryLoading ||
|
||||||
|
brandPaginatedLoading ||
|
||||||
|
collectionPaginatedLoading ||
|
||||||
|
categoryFetching ||
|
||||||
|
lazyCategoryFetching ||
|
||||||
|
brandFetching ||
|
||||||
|
collectionFetching;
|
||||||
|
|
||||||
|
return {
|
||||||
|
products,
|
||||||
|
hasMore,
|
||||||
|
isLoading,
|
||||||
|
setProducts,
|
||||||
|
setHasMore,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCategoryProducts;
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user