added channels products

This commit is contained in:
@jcarymuhammedow
2026-04-30 16:15:29 +05:00
parent 9419ec0af0
commit 6cdde96c61
8 changed files with 139 additions and 37 deletions

View File

@@ -0,0 +1,34 @@
import { baseApi } from "./baseApi";
export const channelsApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
getChannelProducts: builder.query({
query: (params) => {
// params: { channelId, page, limit, min_price, max_price, sorting }
const {
channelId,
page = 1,
limit = 24,
min_price,
max_price,
sorting,
} = params;
const urlParams = new URLSearchParams();
urlParams.append("page", page);
urlParams.append("limit", limit);
if (min_price) urlParams.append("min_price", min_price);
if (max_price) urlParams.append("max_price", max_price);
if (sorting) urlParams.append("sorting", sorting);
return `/channels/${channelId}/products?${urlParams.toString()}`;
},
transformResponse: (response) => ({
data: response.data || response,
pagination: response.pagination || {},
}),
}),
}),
overrideExisting: true,
});
export const { useGetChannelProductsQuery, useLazyGetChannelProductsQuery } =
channelsApi;

View File

@@ -15,6 +15,9 @@ export const filtersApi = baseApi.injectEndpoints({
if (params?.brand_id) {
queryParams.append("brand_id", String(params.brand_id))
}
if (params?.channel_id) {
queryParams.append("channel_id", String(params.channel_id))
}
return `/filters?${queryParams.toString()}`
},
@@ -22,6 +25,7 @@ export const filtersApi = baseApi.injectEndpoints({
return {
categories: response.data?.categories || [],
brands: response.data?.brands || [],
channels: response.data?.channels || [],
}
},
keepUnusedDataFor: 300,
@@ -40,7 +44,9 @@ export const filtersApi = baseApi.injectEndpoints({
if (queryArgs.brand_id) {
parts.push(`brd:${queryArgs.brand_id}`);
}
if (queryArgs.channel_id) {
parts.push(`chn:${queryArgs.channel_id}`);
}
return parts.length > 0 ? parts.join('|') : 'no-params';
},
@@ -66,7 +72,9 @@ export const filtersApi = baseApi.injectEndpoints({
if (arg.brand_id) {
tags.push({ type: "Filters", id: `brd-${arg.brand_id}` });
}
if (arg.channel_id) {
tags.push({ type: "Filters", id: `chn-${arg.channel_id}` });
}
return tags;
},
}),

View File

@@ -87,24 +87,31 @@
overflow: hidden;
width: 1336px;
max-height: 520px;
// max-width: calc(100vw - 32px);
max-width: calc(100vw - 32px);
}
// ---- LEFT LIST ----
.categoriesList {
width: 270px;
flex-shrink: 0;
border-right: 1px solid rgba(255, 255, 255, 0.12);
border-right: 1px solid #e5e7eb;
padding: 10px 0;
max-height: 520px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 4px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f9fafb;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
background: #d1d5db;
border-radius: 10px;
&:hover {
background: #9ca3af;
}
}
}
@@ -119,13 +126,13 @@
transition: background-color 0.12s, color 0.12s;
&:hover {
background-color: rgba(255, 255, 255, 0.08);
color: #000;
background-color: #f3f4f6;
color: #e63946;
}
&.active {
background-color: rgba(255, 255, 255, 0.15);
color: #000;
background-color: #f3f4f6;
color: #e63946;
.title {
font-weight: 600;
@@ -138,7 +145,7 @@
}
.chevron {
color: rgba(255, 255, 255, 0.4);
color: #9ca3af;
flex-shrink: 0;
}
@@ -151,11 +158,18 @@
background: #ffffff;
&::-webkit-scrollbar {
width: 4px;
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f9fafb;
}
&::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 2px;
border-radius: 10px;
&:hover {
background: #9ca3af;
}
}
}

View File

@@ -10,6 +10,7 @@ const useCategoryData = ({
categoryId,
collectionId,
brandId,
channelId, // EKLE
selectedFilterCategory,
searchQuery,
}) => {
@@ -23,8 +24,9 @@ const useCategoryData = ({
if (categoryId) return { category_id: categoryId };
if (collectionId) return { collection_id: collectionId };
if (brandId) return { brand_id: brandId };
if (channelId) return { channel_id: channelId };
return null;
}, [categoryId, collectionId, brandId, selectedFilterCategory, searchQuery]);
}, [categoryId, collectionId, brandId, channelId, selectedFilterCategory, searchQuery]);
const {
data: filtersData,

View File

@@ -1,15 +1,17 @@
import { useState, useEffect, useRef, useCallback } from "react";
import { useState, useEffect, useRef } from "react";
import {
useLazyGetAllCategoryProductsPaginatedQuery,
useGetCategoryProductsQuery,
} from "../../../app/api/categories";
import { useLazyGetBrandProductsQuery } from "../../../app/api/brandsApi";
import { useLazyGetCollectionProductsPaginatedQuery } from "../../../app/api/collectionsApi";
import { useLazyGetChannelProductsQuery } from "../../../app/api/channelsApi"; // EKLE
const useCategoryProducts = ({
categoryId,
collectionId,
brandId,
channelId,
selectedCategory,
isSubCategory,
currentPage,
@@ -27,8 +29,6 @@ const useCategoryProducts = ({
const [isFetching, setIsFetching] = useState(false);
const activeRequestId = useRef(0);
// Tüm parametreleri ref'te tut — stale closure'ı tamamen engelle
const paramsRef = useRef({});
const shouldUseBaseQuery =
categoryId &&
@@ -36,7 +36,8 @@ const useCategoryProducts = ({
!searchQuery &&
!selectedFilterCategory &&
!brandId &&
!collectionId;
!collectionId &&
!channelId;
const { data: baseQueryData, isFetching: baseQueryFetching } =
useGetCategoryProductsQuery(
@@ -54,8 +55,17 @@ const useCategoryProducts = ({
const [fetchCategoryPaginated] = useLazyGetAllCategoryProductsPaginatedQuery();
const [fetchBrandPaginated] = useLazyGetBrandProductsQuery();
const [fetchCollectionPaginated] = useLazyGetCollectionProductsPaginatedQuery();
const [fetchChannelPaginated] = useLazyGetChannelProductsQuery();
// ✅ Ref'e al — dependency array'den çıkar, stale closure yok
const fetchersRef = useRef({});
fetchersRef.current = {
fetchCategoryPaginated,
fetchBrandPaginated,
fetchCollectionPaginated,
fetchChannelPaginated,
};
// Base query handler
useEffect(() => {
if (!shouldUseBaseQuery || !baseQueryData) return;
const data = baseQueryData.data || [];
@@ -69,11 +79,23 @@ const useCategoryProducts = ({
setHasMore(hasNextPage);
}, [baseQueryData, currentPage, shouldUseBaseQuery]);
// Her fetch çağrısını doğrudan effect içinde yap — useCallback kaldırıldı
useEffect(() => {
if (shouldUseBaseQuery || searchQuery) return;
// Parametreleri snapshot al
console.log("🔥 LAZY EFFECT TRIGGERED", {
shouldUseBaseQuery,
categoryId,
collectionId,
brandId,
channelId,
isSubCategory,
selectedFilterCategory,
selectedCategory,
});
const snapshot = {
currentPage,
selectedFilterCategory,
@@ -81,6 +103,7 @@ const useCategoryProducts = ({
isSubCategory,
brandId,
collectionId,
channelId,
selectedFilterBrand,
minPrice,
maxPrice,
@@ -92,6 +115,13 @@ const useCategoryProducts = ({
const run = async () => {
try {
const {
fetchCategoryPaginated,
fetchBrandPaginated,
fetchCollectionPaginated,
fetchChannelPaginated,
} = fetchersRef.current; // ✅ ref'ten oku
const params = {
page: snapshot.currentPage,
limit: 24,
@@ -123,6 +153,11 @@ const useCategoryProducts = ({
collectionId: snapshot.collectionId,
...params,
}).unwrap();
} else if (snapshot.channelId) {
result = await fetchChannelPaginated({
channelId: snapshot.channelId,
...params,
}).unwrap();
}
if (requestId !== activeRequestId.current) return;
@@ -159,7 +194,6 @@ const useCategoryProducts = ({
run();
}, [
// useCallback YOK — her dependency değişince effect direkt çalışır
shouldUseBaseQuery,
searchQuery,
currentPage,
@@ -168,13 +202,12 @@ const useCategoryProducts = ({
isSubCategory,
brandId,
collectionId,
channelId,
selectedFilterBrand,
minPrice,
maxPrice,
sorting,
fetchCategoryPaginated,
fetchBrandPaginated,
fetchCollectionPaginated,
// ✅ fetcher fonksiyonlar dependency'den tamamen çıktı
]);
const isLoading = shouldUseBaseQuery ? baseQueryFetching : isFetching;
@@ -188,4 +221,5 @@ const useCategoryProducts = ({
};
};
export default useCategoryProducts;
export default useCategoryProducts;

View File

@@ -19,16 +19,19 @@ import MobilePhoneCard from "./components/Mobilephonecard";
const CategoryPage = () => {
const { t } = useTranslation();
const { categoryId, collectionId, brandId } = useParams();
const { categoryId, collectionId, brandId, channelId } = useParams();
const location = useLocation();
const navigate = useNavigate();
const routeKey = useMemo(
() => `${categoryId || "x"}-${collectionId || "x"}-${brandId || "x"}`,
[categoryId, collectionId, brandId],
() => `${categoryId || "x"}-${collectionId || "x"}-${brandId || "x"}-${channelId || "x"}`,
[categoryId, collectionId, brandId, channelId],
);
const getSavedState = (key, defaultVal) => {
if (location.state?.clearFilters) {
return defaultVal;
}
try {
const saved = sessionStorage.getItem(`category_${key}_${routeKey}`);
if (saved) return JSON.parse(saved);
@@ -92,6 +95,7 @@ const CategoryPage = () => {
categoryId,
collectionId,
brandId,
channelId,
selectedFilterCategory: filterState.selectedFilterCategory,
searchQuery,
});
@@ -105,6 +109,7 @@ const CategoryPage = () => {
} = useCategoryProducts({
categoryId,
collectionId,
channelId,
brandId,
selectedCategory,
isSubCategory,
@@ -137,10 +142,12 @@ const CategoryPage = () => {
prevRouteRef.current = routeKey;
const savedPageState = getSavedStateByKey(routeKey, "pageState");
const savedFilterState = getSavedStateByKey(routeKey, "filterState");
const savedProducts = getSavedStateByKey(routeKey, "products");
const savedHasMore = getSavedStateByKey(routeKey, "hasMore");
const shouldClear = location.state?.clearFilters;
const savedPageState = shouldClear ? null : getSavedStateByKey(routeKey, "pageState");
const savedFilterState = shouldClear ? null : getSavedStateByKey(routeKey, "filterState");
const savedProducts = shouldClear ? null : getSavedStateByKey(routeKey, "products");
const savedHasMore = shouldClear ? null : getSavedStateByKey(routeKey, "hasMore");
if (savedPageState && savedFilterState && savedProducts) {
setPageState(savedPageState);

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useParams, useNavigate, Link } from "react-router-dom";
import styles from "./ProductPage.module.scss";
import { IoMdHeartEmpty, IoMdHeart } from "react-icons/io";
import { FaShoppingCart } from "react-icons/fa";
@@ -366,12 +366,14 @@ const ProductPage = ({
)}
{product.channel?.[0]?.name && (
<div className={styles.metaItem}>
<Link to={`/channel/${product.channel[0].id}`} state={{ clearFilters: true }} className={styles.metaItem}>
<span className={styles.metaLabel}>{t("order.channel")}</span>
<span className={styles.metaValue}>
{product.channel[0].name}
</span>
</div>
</Link>
)}
{product.properties?.length > 0 && (

View File

@@ -40,6 +40,7 @@ export default function Router() {
{ path: "/category/:categoryId", element: <Category /> },
{ path: "/search", element: <Category /> },
{ path: "/collections/:collectionId", element: <Category /> },
{ path: "/channel/:channelId", element: <Category /> },
{ path: "/product/:productId", element: <ProductDetail /> },
{ path: "/profile", element: <ProfileMenu /> },
{ path: "/orders", element: <Orders /> },