добавил поиск по slug исправил логику подгрузки конкретного товара
This commit is contained in:
1 parent
a585a6a823
commit
090b641906
6 files changed
+54
-31
No files matched your search
@@ -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
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user