паспорт товара

This commit is contained in:
Joe_Peach committed 2026-02-09 21:47:23 +03:00
1 parent e517c58d57
commit 5d9191ac24
5 files changed
+206 -1

No files matched your search

+2
View File
@@ -4,6 +4,7 @@ import { Route } from "react-router";
import Main from "../pages/Main/Main"; import Main from "../pages/Main/Main";
import About from "../pages/About/About"; import About from "../pages/About/About";
import Products from "../pages/Products/Products"; import Products from "../pages/Products/Products";
import ItemPage from "../pages/ItemPage/ItemPage";
function AppRoutes() { function AppRoutes() {
return ( return (
@@ -12,6 +13,7 @@ function AppRoutes() {
<Route index element={<Main />} /> <Route index element={<Main />} />
<Route path="about" element={<About />} /> <Route path="about" element={<About />} />
<Route path="products" element={<Products />} /> <Route path="products" element={<Products />} />
<Route path="/product/:id" element={<ItemPage />} />
</Route> </Route>
</Routes> </Routes>
) )
+7 -1
View File
@@ -1,14 +1,20 @@
import React from "react"; 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";
interface Props { interface Props {
product: Product; product: Product;
} }
const ProductCard: React.FC<Props> = ({ product }) => { const ProductCard: React.FC<Props> = ({ product }) => {
const navigate = useNavigate();
return ( return (
<div className={styles.card}> <div
className={styles.card}
onClick={() => navigate(`/product/${product.id}`)}
>
<img src={product.image} alt={product.name} /> <img src={product.image} alt={product.name} />
<p className={styles.name}>{product.name}</p> <p className={styles.name}>{product.name}</p>
<button className={styles.plus}>+</button> <button className={styles.plus}>+</button>
@@ -32,6 +32,14 @@ export const fetchProductsByCategory = createAsyncThunk(
} }
); );
export const fetchProductById = createAsyncThunk(
"product/fetchById",
async (id: string) => {
const response = await axios.get(`/api/products/${id}`);
return response.data;
}
);
interface ProductsState { interface ProductsState {
items: Product[]; items: Product[];
total: number; total: number;
@@ -0,0 +1,93 @@
.page {
background: #f5f5f5;
min-height: 100vh;
}
.top {
background: #000;
padding: 15px 40px;
}
.back {
color: white;
background: none;
border: none;
cursor: pointer;
font-weight: 600;
}
.hero {
display: flex;
min-height: 500px;
}
.left {
flex: 1;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.left img {
max-width: 400px;
}
.right {
flex: 1;
background: linear-gradient(
rgba(0, 0, 0, 0.7),
rgba(0, 0, 0, 0.7)
),
url("/bg.jpg");
color: white;
padding: 80px;
display: flex;
flex-direction: column;
justify-content: center;
}
.breadcrumbs {
color: #f5c400;
margin-bottom: 20px;
}
.download {
margin-top: 30px;
background: #f5c400;
border: none;
padding: 15px 40px;
border-radius: 30px;
font-weight: bold;
cursor: pointer;
}
.tabs {
display: flex;
border-top: 1px solid #ddd;
}
.tabs button {
flex: 1;
padding: 20px;
border: none;
background: #eee;
cursor: pointer;
}
.active {
background: white;
font-weight: bold;
}
.content {
padding: 40px;
background: white;
}
.attrRow {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
+96
View File
@@ -0,0 +1,96 @@
import { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { fetchProductById } from "../../features/catalog/productsSlice";
import styles from "./ProductPage.module.css";
//import type { Product } from "../../types/product";
import type { CategoryAtributes } from "../../types/categories";
const ItemPage = () => {
const { id } = useParams();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { items, loading } = useAppSelector((state) => state.products);
const { items: cats, selectedCategoryId } = useAppSelector(s => s.categories);
//const filters = useAppSelector(s => s.products.filtersApplied);
const [activeTab, setActiveTab] = useState<"desc" | "chars">("desc");
useEffect(() => {
if (id) dispatch(fetchProductById(id));
}, [id]);
const product = items.find((item) => item.id === Number(id));
const category = cats.find((cat) => cat.id === Number(selectedCategoryId))
if ((loading || !product)) return <div>Загрузка...</div>;
if(!category) return <div>Загрузка...</div>;
return (
<div className={styles.page}>
<div className={styles.top}>
<button className={styles.back} onClick={() => navigate(-1)}>
← НАЗАД В КАТАЛОГ
</button>
</div>
<div className={styles.hero}>
<div className={styles.left}>
<img src={product.image} alt={product.name} />
</div>
<div className={styles.right}>
<div className={styles.breadcrumbs}>
ГЛАВНАЯ / {category?.name} / {product.name}
</div>
<h1>{product.name}</h1>
<p className={styles.short}>{product.description}</p>
<button className={styles.download}>
СКАЧАТЬ ПРАЙС-ЛИСТ
</button>
</div>
</div>
<div className={styles.tabs}>
<button
className={activeTab === "desc" ? styles.active : ""}
onClick={() => setActiveTab("desc")}
>
ОПИСАНИЕ
</button>
<button
className={activeTab === "chars" ? styles.active : ""}
onClick={() => setActiveTab("chars")}
>
ХАРАКТЕРИСТИКИ
</button>
</div>
<div className={styles.content}>
{activeTab === "desc" && (
<div>
<h2>ОПИСАНИЕ</h2>
<p>{product.description}</p>
</div>
)}
{activeTab === "chars" && (
<div>
<h2>ХАРАКТЕРИСТИКИ</h2>
{category.atributes?.map((attr: CategoryAtributes) => (
<div key={attr.id} className={styles.attrRow}>
<span>{attr.name}</span>
<span>{}</span>
</div>
))}
</div>
)}
</div>
</div>
);
}
export default ItemPage;