добавил поиск по slug исправил логику подгрузки конкретного товара

This commit is contained in:
Joe_Peach committed 2026-07-28 21:06:00 +03:00
1 parent a585a6a823
commit 090b641906
6 files changed
+54 -31

No files matched your search

+3 -3
View File
@@ -67,7 +67,7 @@ function generateHomePage(products) {
const topProducts = products.slice(0, 6).map(product => ` const topProducts = products.slice(0, 6).map(product => `
<div class="product-card"> <div class="product-card">
<h3>${escapeHtml(product.full_name || `Товар ${product.id}`)}</h3> <h3>${escapeHtml(product.full_name || `Товар ${product.id}`)}</h3>
<a href="/product/${product.id}">Подробнее</a> <a href="/product/${product.slug || product.id}">Подробнее</a>
</div>`).join(''); </div>`).join('');
return ` return `
@@ -88,7 +88,7 @@ function generateCatalogPage(products) {
<div class="product-card"> <div class="product-card">
<h2>${escapeHtml(product.full_name || `Товар ${product.id}`)}</h2> <h2>${escapeHtml(product.full_name || `Товар ${product.id}`)}</h2>
<p>${escapeHtml((product.description || '').slice(0, 150))}...</p> <p>${escapeHtml((product.description || '').slice(0, 150))}...</p>
<a href="/product/${product.id}">Подробнее</a> <a href="/product/${product.slug || product.id}">Подробнее</a>
</div>`).join(''); </div>`).join('');
return ` return `
@@ -139,7 +139,7 @@ const routes = [
// Добавляем страницы товаров // Добавляем страницы товаров
for (const product of products) { for (const product of products) {
routes.push({ routes.push({
path: `/product/${product.id}`, path: `/product/${product.slug || product.id}`,
title: product.full_name, title: product.full_name,
description: (product.description || ''), description: (product.description || ''),
getContent: () => generateProductPage(product) getContent: () => generateProductPage(product)
+1 -1
View File
@@ -15,7 +15,7 @@ function AppRoutes() {
<Route path="about" element={<About />} /> <Route path="about" element={<About />} />
<Route path="products" element={<Products />} /> <Route path="products" element={<Products />} />
<Route path="contacts" element={<Contacts />} /> <Route path="contacts" element={<Contacts />} />
<Route path="/product/:id" element={<ItemPage />} /> <Route path="/product/:slug" element={<ItemPage />} />
</Route> </Route>
</Routes> </Routes>
) )
+14 -11
View File
@@ -2,31 +2,34 @@ import React from "react";
import type { Product } from "../../types/product"; import type { Product } from "../../types/product";
import styles from "./Catalog.module.css"; import styles from "./Catalog.module.css";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { setSelectedProducts } from "./productsSlice";
interface Props { interface Props {
product: Product; product: Product;
} }
const ProductCard: React.FC<Props> = ({ product }) => { const ProductCard: React.FC<Props> = ({ product }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const onSelect = (prodId: number) => { const onSelect = () => {
navigate(`/product/${product.id}`); navigate(`/product/${product.slug}`);
setSelectedProducts(prodId); };
}
return ( return (
<div <div
className={`${styles.card} ${styles.productCard}`} className={`${styles.card} ${styles.productCard}`}
onClick={() => onSelect(product.id)} onClick={() => onSelect()}
> >
<div className={styles.productImageFrame}> <div className={styles.productImageFrame}>
<img className={styles.productImage} src={product.images[0]} alt={product.name} /> <img
className={styles.productImage}
src={product.images[0]}
alt={product.name}
/>
</div> </div>
<div className={styles.cardContent}> <div className={styles.cardContent}>
<p className={styles.name}>{product.name}</p> <p className={styles.name}>{product.name}</p>
<button className={styles.plus}>+</button> <button className={styles.plus}>+</button>
</div> </div>
</div> </div>
); );
+29 -10
View File
@@ -38,10 +38,10 @@ export const fetchProductsByCategory = createAsyncThunk(
} }
); );
export const fetchProductById = createAsyncThunk( export const fetchProductBySlug = createAsyncThunk(
"product/fetchById", "product/fetchBySlug",
async (id: string) => { async (slug: string) => {
const response = await client.get<Product>(`/products/${id}/`); const response = await client.get<Product>(`/products/${slug}/`);
return response.data; return response.data;
} }
); );
@@ -54,7 +54,8 @@ interface ProductsState {
loading: boolean; loading: boolean;
error: string | null; error: string | null;
filtersApplied: Record<string, (string|number)[]>; filtersApplied: Record<string, (string|number)[]>;
selectedProductId: number | null; selectedProduct: Product | null;
//selectedProductId: number | null;
} }
const initialState: ProductsState = { const initialState: ProductsState = {
@@ -65,16 +66,17 @@ const initialState: ProductsState = {
loading: false, loading: false,
error: null, error: null,
filtersApplied: {}, filtersApplied: {},
selectedProductId: null selectedProduct: null
//selectedProductId: null
}; };
const slice = createSlice({ const slice = createSlice({
name: "products", name: "products",
initialState, initialState,
reducers: { reducers: {
setSelectedProducts(state, action: PayloadAction<number>){ /*setSelectedProducts(state, action: PayloadAction<number>){
state.selectedProductId = action.payload; state.selectedProductId = action.payload;
}, },*/
setPage(state, action: PayloadAction<number>) { setPage(state, action: PayloadAction<number>) {
state.page = action.payload; state.page = action.payload;
}, },
@@ -84,10 +86,14 @@ const slice = createSlice({
setFilters(state, action: PayloadAction<ProductsState["filtersApplied"]>) { setFilters(state, action: PayloadAction<ProductsState["filtersApplied"]>) {
state.filtersApplied = action.payload; state.filtersApplied = action.payload;
}, },
setSelectedProduct(state, action: PayloadAction<Product | null>) {
state.selectedProduct = action.payload;
},
resetProducts(state) { resetProducts(state) {
state.items = []; state.items = [];
state.total = 0; state.total = 0;
state.page = 1; state.page = 1;
state.selectedProduct = null;
} }
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
@@ -106,9 +112,22 @@ const slice = createSlice({
.addCase(fetchProductsByCategory.rejected, (s, a) => { .addCase(fetchProductsByCategory.rejected, (s, a) => {
s.loading = false; s.loading = false;
s.error = a.error.message ?? "Ошибка"; s.error = a.error.message ?? "Ошибка";
})
.addCase(fetchProductBySlug.pending, (s) => {
s.loading = true;
s.error = null;
})
.addCase(fetchProductBySlug.fulfilled, (s, a) => {
s.loading = false;
s.selectedProduct = a.payload;
s.error = null;
})
.addCase(fetchProductBySlug.rejected, (s, a) => {
s.loading = false;
s.error = a.error.message ?? "Ошибка";
}); });
} }
}); });
export const { setPage, setLimit, setFilters, resetProducts, setSelectedProducts } = slice.actions; export const { setPage, setLimit, setFilters, resetProducts } = slice.actions;
export default slice.reducer; export default slice.reducer;
+6 -6
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import { ArrowLeftOutlined } from "@ant-design/icons"; import { ArrowLeftOutlined } from "@ant-design/icons";
import { useAppDispatch, useAppSelector } from "../../app/hooks"; import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { fetchProductById } from "../../features/catalog/productsSlice"; import { fetchProductBySlug } from "../../features/catalog/productsSlice";
import styles from "./ItemPage.module.css"; import styles from "./ItemPage.module.css";
import Button from "../../components/Button/Button"; import Button from "../../components/Button/Button";
import PriceLisForm from "../../components/PriceLisForm/PriceLisForm"; import PriceLisForm from "../../components/PriceLisForm/PriceLisForm";
@@ -11,20 +11,20 @@ import Link from "antd/es/typography/Link";
import { setSelectedCategory } from "../../features/catalog/categoriesSlice"; import { setSelectedCategory } from "../../features/catalog/categoriesSlice";
const ItemPage = () => { const ItemPage = () => {
const { id } = useParams(); const { slug } = useParams();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const { items, loading } = useAppSelector((state) => state.products); const { loading } = useAppSelector((state) => state.products);
const { items: cats, selectedCategoryId } = useAppSelector((s) => s.categories); const { items: cats, selectedCategoryId } = useAppSelector((s) => s.categories);
const [activeTab, setActiveTab] = useState<"desc" | "chars">("desc"); const [activeTab, setActiveTab] = useState<"desc" | "chars">("desc");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useEffect(() => { useEffect(() => {
if (id) dispatch(fetchProductById(id)); if (slug) dispatch(fetchProductBySlug(slug));
}, [dispatch, id]); }, [dispatch, slug]);
const product = items.find((item) => item.id === Number(id)); const product = useAppSelector((state) => state.products.selectedProduct);
const category = cats.find((cat) => cat.id === Number(selectedCategoryId)); const category = cats.find((cat) => cat.id === Number(selectedCategoryId));
if (loading || !product) { if (loading || !product) {
+1
View File
@@ -9,4 +9,5 @@ export interface Product {
categoryId?: number; categoryId?: number;
created_at: string; created_at: string;
full_name: string; full_name: string;
slug:string;
} }