fix(front) Добавил строку категорий и карточек товара
This commit is contained in:
@@ -91,7 +91,7 @@ func DeleteItem(c *gin.Context) {
|
||||
|
||||
func GetCategories(c *gin.Context) {
|
||||
var item models.Item
|
||||
var categories []string
|
||||
categories := make([]string, 0)
|
||||
|
||||
err := database.DB.Model(&item).Where("category IS NOT NULL AND category != ''").Distinct().Pluck("category", &categories).Error
|
||||
if err != nil {
|
||||
|
||||
@@ -37,4 +37,4 @@ export const updateItem = (id, itemData) =>
|
||||
|
||||
export const deleteItem = (id) => axios.delete(`${API_URL}/items/${id}`);
|
||||
|
||||
export const getAllCategories = () => axios.get(`${API_URL}/getCategory`)
|
||||
export const getAllCategories = () => axios.get(`${API_URL}/getCategories`)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function CardItem({ item, onAddToCart }) {
|
||||
// Начальный вес — 100 грамм (0.100 кг)
|
||||
const [weight, setWeight] = useState(0.100);
|
||||
|
||||
// Увеличение веса с шагом 100 грамм
|
||||
const handleIncrement = () => {
|
||||
setWeight((prev) => parseFloat((prev + 0.100).toFixed(3)));
|
||||
};
|
||||
|
||||
// Уменьшение веса (не позволяет опуститься ниже минимальных 100 грамм)
|
||||
const handleDecrement = () => {
|
||||
setWeight((prev) => (prev > 0.100 ? parseFloat((prev - 0.100).toFixed(3)) : 0.100));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col text-white w-full max-w-[360px] bg-slate-900/40 p-4 rounded-lg border border-slate-800 hover:border-slate-700 transition-all">
|
||||
|
||||
{/* Заглушка для изображения товара */}
|
||||
<div className="w-full aspect-[4/5] rounded-md overflow-hidden bg-slate-800/60 flex flex-col items-center justify-center border border-slate-700/50 select-none">
|
||||
<svg className="w-12 h-12 text-slate-600 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 002-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span className="text-xs text-slate-500 font-medium uppercase tracking-wider">
|
||||
Изображение товара
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Блок с информацией о товаре */}
|
||||
<div className="mt-4 flex justify-between items-start grow">
|
||||
<div>
|
||||
<h3 className="font-bold text-lg tracking-wide">{item.name}</h3>
|
||||
{item.origin && <p className="text-slate-500 text-sm mt-0.5">{item.origin}</p>}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="font-bold text-lg">
|
||||
{Number(item.price || 0).toLocaleString('ru-RU')} ₽
|
||||
</span>
|
||||
<span className="text-slate-400 text-sm"> / {item.unit || 'кг'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Инпут управления весом и кнопка добавления */}
|
||||
<div className="mt-6 flex items-center justify-between gap-4">
|
||||
|
||||
{/* Шаговый переключатель веса */}
|
||||
<div className="flex items-center bg-amber-500/10 border border-amber-500/30 rounded-full px-2 py-1 text-amber-500">
|
||||
<button
|
||||
onClick={handleDecrement}
|
||||
className="w-8 h-8 flex items-center justify-center font-bold text-lg hover:text-amber-400 cursor-pointer select-none"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span className="mx-2 font-mono text-sm min-w-[50px] text-center">
|
||||
{weight.toFixed(3)}
|
||||
</span>
|
||||
<button
|
||||
onClick={handleIncrement}
|
||||
className="w-8 h-8 flex items-center justify-center font-bold text-lg hover:text-amber-400 cursor-pointer select-none"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Кнопка действия "В корзину" */}
|
||||
<button
|
||||
onClick={() => onAddToCart(item, weight)}
|
||||
className="group flex items-center gap-2 text-sm font-medium tracking-wide text-slate-300 hover:text-amber-400 transition-colors cursor-pointer"
|
||||
>
|
||||
<span>В корзину</span>
|
||||
<svg className="w-5 h-5 stroke-current transform group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export default function CategoryTabs({
|
||||
categories,
|
||||
activeCategory,
|
||||
onSelectCategory,
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center md:justify-start gap-6 md:gap-8 my-15 text-sm font-medium tracking-wider text-slate-400 ">
|
||||
<button
|
||||
className={`uppercase transition-colors duration-200 hover:text-white cursor-pointer ${activeCategory === "all"
|
||||
? "text-white border-b-2 border-gold pb-1"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
все
|
||||
</button>
|
||||
{categories.map((cat) => (
|
||||
<button
|
||||
key={cat}
|
||||
onClick={() => onSelectCategory(cat)}
|
||||
className={`uppercase transition-colors duration-200 hover:text-white cursor-pointer ${activeCategory === cat
|
||||
? "text-white border-b-2 border-gold pb-1"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{cat}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
CategoryTabs.propTypes = {
|
||||
categories: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
activeCategory: PropTypes.string.isRequired,
|
||||
onSelectCategory: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -1,6 +1,8 @@
|
||||
import PageLayout from "../components/PageLayout";
|
||||
import * as api from "../api/index";
|
||||
import { useState, useEffect } from "react";
|
||||
import CategoryTabs from "../components/CategoryTabs";
|
||||
import CardItem from "../components/CardItem";
|
||||
|
||||
export default function Shop() {
|
||||
const [items, setItems] = useState([]);
|
||||
@@ -10,29 +12,43 @@ export default function Shop() {
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([api.getAllItems(), api.getAllCategories()])
|
||||
.then((itemsRes, categoriesRes) => {
|
||||
setItems(itemsRes.data);
|
||||
setCategories(categoriesRes.data);
|
||||
.then(([itemsRes, categoriesRes]) => {
|
||||
setItems(itemsRes?.data || []);
|
||||
setCategories(categoriesRes?.data || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Ошибка при загрузке товара", err);
|
||||
alert(err);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const filteredItems =
|
||||
activeCategory === "all"
|
||||
? items
|
||||
: items.fitler(
|
||||
(item) =>
|
||||
item.category?.toLowerCase() === activeCategory.toLowerCase(),
|
||||
);
|
||||
? items || []
|
||||
: (items || []).filter((item) => {
|
||||
if (!item || !item.category) return false;
|
||||
|
||||
return item.category.toLowerCase() === activeCategory.toLowerCase();
|
||||
});
|
||||
|
||||
const handleAddToCart = (product, selectedWeight) => {
|
||||
console.log(`Добавлен товар ${product.name}, Вес ${selectedWeight} кг`);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<main className="px-16 grow w-full">
|
||||
<div className="flex flex-row text-white"></div>
|
||||
<h1 className="text">Ассортимент</h1>
|
||||
<CategoryTabs
|
||||
categories={categories || []}
|
||||
activeCategory={activeCategory}
|
||||
onSelectCategory={setActiveCategory}
|
||||
/>
|
||||
{filteredItems.map((item) => (
|
||||
<CardItem key={item.id} item={item} onAddToCart={handleAddToCart} />
|
||||
))}
|
||||
</main>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user