From 181a24571c143fb4851eb34633ad04f962c1d275 Mon Sep 17 00:00:00 2001 From: Oleg Urin Date: Sun, 23 Nov 2025 23:31:55 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20ap?= =?UTF-8?q?i=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82,=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=81=D1=82=D0=BE=D1=80,=20?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B9=D1=81=D1=8B=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D1=83=D0=BA=D1=82=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- front/src/api/client.ts | 9 ++ front/src/app/store.ts | 4 +- .../features/catalog/SidebarCategories.tsx | 12 +++ front/src/features/catalog/catalogSlice.ts | 95 ------------------- .../src/features/catalog/catalogeSidebar.tsx | 69 -------------- front/src/features/catalog/productsSlice.ts | 95 +++++++++++++++++++ front/src/pages/Products/Products.tsx | 2 +- front/src/types/categories.ts | 3 +- 8 files changed, 122 insertions(+), 167 deletions(-) create mode 100644 front/src/api/client.ts create mode 100644 front/src/features/catalog/SidebarCategories.tsx delete mode 100644 front/src/features/catalog/catalogSlice.ts delete mode 100644 front/src/features/catalog/catalogeSidebar.tsx create mode 100644 front/src/features/catalog/productsSlice.ts diff --git a/front/src/api/client.ts b/front/src/api/client.ts new file mode 100644 index 0000000..4188ab4 --- /dev/null +++ b/front/src/api/client.ts @@ -0,0 +1,9 @@ +import axios from "axios"; + +export const API_BASE = g;/* process.env.REACT_APP_API_BASE || "";*/ +const client = axios.create({ + baseURL: API_BASE, + headers: { "Content-Type": "application/json" }, +}); + +export default client; \ No newline at end of file diff --git a/front/src/app/store.ts b/front/src/app/store.ts index b7c77eb..8bd0c79 100644 --- a/front/src/app/store.ts +++ b/front/src/app/store.ts @@ -1,5 +1,7 @@ import { configureStore } from "@reduxjs/toolkit"; -import catalogReducer from "../features/catalog/catalogSlice"; +import catalogReducer from "../features/catalog/productsSlice"; +import categoriesReducer from "../features/catalog/categoriesSlice" +import productsReducer from "../features/catalog/productsSlice" export const store = configureStore({ reducer: { diff --git a/front/src/features/catalog/SidebarCategories.tsx b/front/src/features/catalog/SidebarCategories.tsx new file mode 100644 index 0000000..bbcdad2 --- /dev/null +++ b/front/src/features/catalog/SidebarCategories.tsx @@ -0,0 +1,12 @@ +import React, { useEffect } from "react"; +import styles from "./SidebarCategories.module.css"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import { fetchCategories, setSelectedCategory } from "./categoriesSlice"; +import { fetchProductsByCategory, setFilters, setPage } from "./productsSlice"; + +const SidebarCategories: React.FC = () => { + return( + <> + + ) +} \ No newline at end of file diff --git a/front/src/features/catalog/catalogSlice.ts b/front/src/features/catalog/catalogSlice.ts deleted file mode 100644 index 06dbda4..0000000 --- a/front/src/features/catalog/catalogSlice.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; -import type {PayloadAction} from "@reduxjs/toolkit"; -import { fetchCategoryProducts, fetchCategories } from "../../api/catalogApi"; -import type { Product } from "../../types/product"; -import axios from "axios"; - -interface CatalogState { - products: Product[]; - filtered: Product[]; - loading: boolean; - error: string | null; - filters: Record; - currentCategory: string | null; -} - -const initialState: CatalogState = { - products: [], - filtered: [], - loading: false, - error: null, - filters: {}, - currentCategory: null, -}; - -export const fetchProductsByCategory = createAsyncThunk( - "products/fetchProductsByCategory", - async (categoryId: number) => { - const res = await axios.get(`/api/products?category=${categoryId}`); - return res.data; - } -); - -export const fetchCategoryParameters = createAsyncThunk( - "products/fetchCategoryParametrs", - async (categoryId: number) => { - const res = await axios.get(`/api/category-params/${categoryId}`); - return res.data; - } -) - -const catalogSlice = createSlice({ - name: "catalog", - initialState, - reducers: { - setFilter(state, action: PayloadAction<{ name: string; value: string }>) { - const { name, value } = action.payload; - const active = state.filters[name] || []; - state.filters[name] = active.includes(value) - ? active.filter((v) => v !== value) - : [...active, value]; - }, - applyFilters(state) { - state.filtered = state.products.filter((p) => - Object.entries(state.filters).every(([key, values]) => - values.length ? values.includes(p[key as keyof Product] as string) : true - ) - ); - }, - }, - extraReducers: (builder) => { - builder - .addCase(fetchProductsByCategory.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchProductsByCategory.fulfilled, (state, action) => { - state.loading = false; - state.products = action.payload; - state.filtered = action.payload; - state.error = null; - }) - .addCase(fetchProductsByCategory.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message || "Ошибка загрузки товаров"; - state.products = []; - state.filtered = []; - }) - .addCase(fetchCategoryParameters.pending, (state) => { - state.loading = true; - state.error = null; - }) - .addCase(fetchCategoryParameters.fulfilled, (state, action) => { - state.loading = false; - state.filters = action.payload - }) - .addCase(fetchCategoryParameters.rejected, (state, action) => { - state.loading = false; - state.error = action.error.message || "Ошибка загрузки параметров категории"; - state.filters = {}; - }); - }, -}); - -export const { setFilter, applyFilters } = catalogSlice.actions; -export default catalogSlice.reducer; \ No newline at end of file diff --git a/front/src/features/catalog/catalogeSidebar.tsx b/front/src/features/catalog/catalogeSidebar.tsx deleted file mode 100644 index 4ce1209..0000000 --- a/front/src/features/catalog/catalogeSidebar.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; -import { useDispatch } from "react-redux"; -import { setFilter, applyFilters } from "./catalogSlice"; -import styles from "./Catalog.module.css"; -import type { Category } from "../../types/categories"; -import type { CategoryAtributes } from "../../types/categories"; -import type { CategoryAtrVals } from "../../types/categories"; - -interface Props{ - categories: Category[]; - atributes: CategoryAtributes[] - atrVals: CategoryAtrVals[]; -} - -const CatalogSidebar: React.FC = ({categories, atributes, atrVals}) => { - const dispatch = useDispatch(); - - const handleFilterChange = (name: string, value: string) => { - dispatch(setFilter({ name, value })); - dispatch(applyFilters()); - }; - - return ( - - ); -}; - -export default CatalogSidebar; \ No newline at end of file diff --git a/front/src/features/catalog/productsSlice.ts b/front/src/features/catalog/productsSlice.ts new file mode 100644 index 0000000..17c4591 --- /dev/null +++ b/front/src/features/catalog/productsSlice.ts @@ -0,0 +1,95 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import type {PayloadAction} from "@reduxjs/toolkit"; +import client from "../../api/client"; +import type { Product } from "../../types/product"; +import axios from "axios"; + +interface FetchParams { + categoryId: number; + page?: number; + limit?: number; + filters?: Record; +} + +export const fetchProductsByCategory = createAsyncThunk( + "products/fetchByCategory", + async (params: FetchParams) => { + const { categoryId, page = 1, limit = 9, filters} = params; + const qp = new URLSearchParams(); + qp.append("categoryId", String(categoryId)); + qp.append("page", String(page)); + qp.append("limit", String(limit)); + if (filters) { + Object.keys(filters).forEach((k) => { + const v = filters[k]; + if (Array.isArray(v)) qp.append(k, v.join(",")); + else qp.append(k, String(v)); + }); + } + const res = await client.get(`/products?${qp.toString()}`); + // ожидаем { items: Product[], total: number, page: number, limit: number } + return res.data; + } +); + +interface ProductsState { + items: Product[]; + total: number; + page: number; + limit: number; + loading: boolean; + error: string | null; + filtersApplied: Record; +} + +const initialState: ProductsState = { + items: [], + total: 0, + page: 1, + limit: 9, + loading: false, + error: null, + filtersApplied: {}, +}; + +const slice = createSlice({ + name: "products", + initialState, + reducers: { + setPage(state, action: PayloadAction) { + state.page = action.payload; + }, + setLimit(state, action: PayloadAction) { + state.limit = action.payload; + }, + setFilters(state, action: PayloadAction) { + state.filtersApplied = action.payload; + }, + resetProducts(state) { + state.items = []; + state.total = 0; + state.page = 1; + } + }, + extraReducers: (builder) => { + builder + .addCase(fetchProductsByCategory.pending, (s) => { + s.loading = true; + s.error = null; + }) + .addCase(fetchProductsByCategory.fulfilled, (s, a) => { + s.loading = false; + s.items = a.payload.items; + s.total = a.payload.total; + s.page = a.payload.page ?? s.page; + s.limit = a.payload.limit ?? s.limit; + }) + .addCase(fetchProductsByCategory.rejected, (s, a) => { + s.loading = false; + s.error = a.error.message ?? "Ошибка"; + }); + } +}); + +export const { setPage, setLimit, setFilters, resetProducts } = slice.actions; +export default slice.reducer; \ No newline at end of file diff --git a/front/src/pages/Products/Products.tsx b/front/src/pages/Products/Products.tsx index e85c1ad..d0a2c3c 100644 --- a/front/src/pages/Products/Products.tsx +++ b/front/src/pages/Products/Products.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import { useParams } from "react-router-dom"; import type { RootState } from "../../app/store"; -import { getCategoryProducts } from "../../features/catalog/catalogSlice"; +import { getCategoryProducts } from "../../features/catalog/productsSlice"; import CatalogSidebar from "../../features/catalog/catalogeSidebar" import ProductGrid from "../../features/catalog/ProductGrid"; import styles from "../../features/catalog/catalog.module.css"; diff --git a/front/src/types/categories.ts b/front/src/types/categories.ts index e0c1813..9b332c5 100644 --- a/front/src/types/categories.ts +++ b/front/src/types/categories.ts @@ -13,4 +13,5 @@ export interface Category { id: number; name: string; atributes: CategoryAtributes[]; -} \ No newline at end of file +} +