Добавлены новые компоненты для отображения лучших цен и товаров дня с использованием GraphQL. Реализована логика загрузки данных, обработка ошибок и отображение состояния загрузки. Обновлены компоненты BestPriceSection, ProductOfDaySection и TopSalesSection для интеграции с новыми запросами. Улучшено взаимодействие с пользователем через уведомления и обработку кликов.
This commit is contained in:
@ -1,4 +1,7 @@
|
||||
import React from "react";
|
||||
import { useCart } from "@/contexts/CartContext";
|
||||
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface BestPriceItemProps {
|
||||
image: string;
|
||||
@ -7,6 +10,8 @@ interface BestPriceItemProps {
|
||||
oldPrice: string;
|
||||
title: string;
|
||||
brand: string;
|
||||
article?: string;
|
||||
productId?: string;
|
||||
onAddToCart?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
@ -17,14 +22,129 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
|
||||
oldPrice,
|
||||
title,
|
||||
brand,
|
||||
article,
|
||||
productId,
|
||||
onAddToCart,
|
||||
}) => {
|
||||
const { addItem } = useCart();
|
||||
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||
|
||||
// Проверяем, есть ли товар в избранном
|
||||
const isItemFavorite = isFavorite(productId, undefined, article, brand);
|
||||
|
||||
// Функция для парсинга цены из строки
|
||||
const parsePrice = (priceStr: string): number => {
|
||||
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
|
||||
return parseFloat(cleanPrice) || 0;
|
||||
};
|
||||
|
||||
// Обработчик добавления в корзину
|
||||
const handleAddToCart = async (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Если передан кастомный обработчик, используем его
|
||||
if (onAddToCart) {
|
||||
onAddToCart(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const numericPrice = parsePrice(price);
|
||||
|
||||
if (numericPrice <= 0) {
|
||||
toast.error('Цена товара не найдена');
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавляем товар в корзину
|
||||
const result = await addItem({
|
||||
productId: productId,
|
||||
name: title,
|
||||
description: `${brand} - ${title}`,
|
||||
brand: brand,
|
||||
article: article,
|
||||
price: numericPrice,
|
||||
currency: 'RUB',
|
||||
quantity: 1,
|
||||
image: image,
|
||||
supplier: 'Protek',
|
||||
deliveryTime: '1 день',
|
||||
isExternal: false
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
// Показываем успешный тоастер
|
||||
toast.success(
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
|
||||
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${brand} - ${title}`}</div>
|
||||
</div>,
|
||||
{
|
||||
duration: 3000,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Показываем ошибку
|
||||
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка добавления в корзину:', error);
|
||||
toast.error('Ошибка при добавлении товара в корзину');
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик клика по иконке избранного
|
||||
const handleFavoriteClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isItemFavorite) {
|
||||
// Находим товар в избранном и удаляем
|
||||
const favoriteItem = favorites.find((fav: any) => {
|
||||
if (productId && fav.productId === productId) return true;
|
||||
if (fav.article === article && fav.brand === brand) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (favoriteItem) {
|
||||
removeFromFavorites(favoriteItem.id);
|
||||
toast.success('Товар удален из избранного');
|
||||
}
|
||||
} else {
|
||||
// Добавляем в избранное
|
||||
const numericPrice = parsePrice(price);
|
||||
addToFavorites({
|
||||
productId,
|
||||
name: title,
|
||||
brand: brand,
|
||||
article: article || '',
|
||||
price: numericPrice,
|
||||
currency: 'RUB',
|
||||
image: image
|
||||
});
|
||||
toast.success('Товар добавлен в избранное');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-layout-vflex bestpriceitem">
|
||||
<div className="favcardcat">
|
||||
<div
|
||||
className="favcardcat"
|
||||
onClick={handleFavoriteClick}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
color: isItemFavorite ? '#ef4444' : 'currentColor'
|
||||
}}
|
||||
title={isItemFavorite ? 'Удалить из избранного' : 'Добавить в избранное'}
|
||||
>
|
||||
<div className="icon-setting w-embed">
|
||||
<svg width="currenWidth" height="currentHeight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
|
||||
<path
|
||||
d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z"
|
||||
fill={isItemFavorite ? 'currentColor' : 'none'}
|
||||
stroke="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@ -46,7 +166,13 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-120">
|
||||
<div className="nameitembp">{title}</div>
|
||||
<a href="#" className="button-icon w-inline-block" onClick={onAddToCart}>
|
||||
<a
|
||||
href="#"
|
||||
className="button-icon w-inline-block"
|
||||
onClick={handleAddToCart}
|
||||
style={{ cursor: 'pointer' }}
|
||||
aria-label="Добавить в корзину"
|
||||
>
|
||||
<div className="div-block-26">
|
||||
<div className="icon-setting w-embed">
|
||||
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
164
src/components/TopSalesItem.tsx
Normal file
164
src/components/TopSalesItem.tsx
Normal file
@ -0,0 +1,164 @@
|
||||
import React from "react";
|
||||
import { useCart } from "@/contexts/CartContext";
|
||||
import { useFavorites } from "@/contexts/FavoritesContext";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface TopSalesItemProps {
|
||||
image: string;
|
||||
price: string;
|
||||
title: string;
|
||||
brand: string;
|
||||
article?: string;
|
||||
productId?: string;
|
||||
onAddToCart?: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const TopSalesItem: React.FC<TopSalesItemProps> = ({
|
||||
image,
|
||||
price,
|
||||
title,
|
||||
brand,
|
||||
article,
|
||||
productId,
|
||||
onAddToCart,
|
||||
}) => {
|
||||
const { addItem } = useCart();
|
||||
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
|
||||
|
||||
// Проверяем, есть ли товар в избранном
|
||||
const isItemFavorite = isFavorite(productId, undefined, article, brand);
|
||||
|
||||
// Функция для парсинга цены из строки
|
||||
const parsePrice = (priceStr: string): number => {
|
||||
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
|
||||
return parseFloat(cleanPrice) || 0;
|
||||
};
|
||||
|
||||
// Обработчик клика по корзине
|
||||
const handleAddToCart = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (onAddToCart) {
|
||||
onAddToCart(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!article || !brand) {
|
||||
toast.error('Недостаточно данных для добавления товара в корзину');
|
||||
return;
|
||||
}
|
||||
|
||||
const numericPrice = parsePrice(price);
|
||||
|
||||
addItem({
|
||||
name: title,
|
||||
brand: brand,
|
||||
article: article,
|
||||
description: title,
|
||||
price: numericPrice,
|
||||
quantity: 1,
|
||||
currency: 'RUB',
|
||||
image: image,
|
||||
isExternal: true
|
||||
});
|
||||
|
||||
toast.success('Товар добавлен в корзину');
|
||||
} catch (error) {
|
||||
console.error('Ошибка добавления в корзину:', error);
|
||||
toast.error('Ошибка добавления товара в корзину');
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик клика по иконке избранного
|
||||
const handleFavoriteClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isItemFavorite) {
|
||||
// Находим товар в избранном и удаляем
|
||||
const favoriteItem = favorites.find((fav: any) => {
|
||||
if (productId && fav.productId === productId) return true;
|
||||
if (fav.article === article && fav.brand === brand) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (favoriteItem) {
|
||||
removeFromFavorites(favoriteItem.id);
|
||||
toast.success('Товар удален из избранного');
|
||||
}
|
||||
} else {
|
||||
// Добавляем в избранное
|
||||
const numericPrice = parsePrice(price);
|
||||
addToFavorites({
|
||||
productId,
|
||||
name: title,
|
||||
brand: brand,
|
||||
article: article || '',
|
||||
price: numericPrice,
|
||||
currency: 'RUB',
|
||||
image: image
|
||||
});
|
||||
toast.success('Товар добавлен в избранное');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-layout-vflex flex-block-15">
|
||||
<div
|
||||
className={`favcardcat ${isItemFavorite ? 'favorite-active' : ''}`}
|
||||
onClick={handleFavoriteClick}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
color: isItemFavorite ? '#ff4444' : '#ccc'
|
||||
}}
|
||||
>
|
||||
<div className="icon-setting w-embed">
|
||||
<svg width="currenWidth" height="currentHeight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="div-block-4">
|
||||
<img
|
||||
src={image}
|
||||
loading="lazy"
|
||||
width="Auto"
|
||||
height="Auto"
|
||||
alt={title}
|
||||
className="image-5"
|
||||
/>
|
||||
<div className="text-block-7">Топ продаж</div>
|
||||
</div>
|
||||
|
||||
<div className="div-block-3">
|
||||
<div className="w-layout-hflex flex-block-16">
|
||||
<div className="text-block-8">{price}</div>
|
||||
</div>
|
||||
<div className="text-block-10">{title}</div>
|
||||
<div className="text-block-11">{brand}</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
className="button-icon w-inline-block"
|
||||
onClick={handleAddToCart}
|
||||
style={{ cursor: 'pointer' }}
|
||||
aria-label="Добавить в корзину"
|
||||
>
|
||||
<div className="div-block-25">
|
||||
<div className="icon-setting w-embed">
|
||||
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-block-6">Купить</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopSalesItem;
|
@ -1,44 +1,111 @@
|
||||
import React from "react";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import BestPriceItem from "../BestPriceItem";
|
||||
import { GET_BEST_PRICE_PRODUCTS } from "../../lib/graphql";
|
||||
|
||||
// Моковые данные для лучших цен
|
||||
const bestPriceItems = [
|
||||
{
|
||||
image: "images/162615.webp",
|
||||
discount: "-35%",
|
||||
price: "от 17 087 ₽",
|
||||
oldPrice: "22 347 ₽",
|
||||
title: 'Аккумуляторная батарея TYUMEN BATTERY "STANDARD", 6CT-60L, 60',
|
||||
brand: "TYUMEN BATTERY",
|
||||
},
|
||||
// ...добавьте еще 7 карточек для примера
|
||||
...Array(7).fill(0).map((_, i) => ({
|
||||
image: "images/162615.webp",
|
||||
discount: "-35%",
|
||||
price: `от ${(17087 + i * 1000).toLocaleString('ru-RU')} ₽`,
|
||||
oldPrice: `${(22347 + i * 1000).toLocaleString('ru-RU')} ₽`,
|
||||
title: `Товар №${i + 2}`,
|
||||
brand: `Бренд ${i + 2}`,
|
||||
}))
|
||||
];
|
||||
interface BestPriceProductData {
|
||||
id: string;
|
||||
productId: string;
|
||||
discount: number;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article?: string;
|
||||
brand?: string;
|
||||
retailPrice?: number;
|
||||
images: { url: string; alt?: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
const BestPriceSection: React.FC = () => (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-hflex flex-block-118">
|
||||
<div className="w-layout-vflex flex-block-119">
|
||||
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||
<div className="text-block-58">Подборка лучших предложенийпо цене</div>
|
||||
<a href="#" className="button-24 w-button">Показать все</a>
|
||||
const BestPriceSection: React.FC = () => {
|
||||
const { data, loading, error } = useQuery(GET_BEST_PRICE_PRODUCTS);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-hflex flex-block-118">
|
||||
<div className="w-layout-vflex flex-block-119">
|
||||
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||
<div className="text-block-58">Загрузка...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-121">
|
||||
{bestPriceItems.map((item, i) => (
|
||||
<BestPriceItem key={i} {...item} />
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.error('Ошибка загрузки товаров с лучшей ценой:', error);
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-hflex flex-block-118">
|
||||
<div className="w-layout-vflex flex-block-119">
|
||||
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||
<div className="text-block-58">Ошибка загрузки данных</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const bestPriceProducts: BestPriceProductData[] = data?.bestPriceProducts || [];
|
||||
|
||||
// Функция для форматирования цены
|
||||
const formatPrice = (price?: number) => {
|
||||
if (!price) return '—';
|
||||
return `от ${price.toLocaleString('ru-RU')} ₽`;
|
||||
};
|
||||
|
||||
// Функция для расчета цены со скидкой
|
||||
const calculateDiscountedPrice = (price?: number, discount?: number) => {
|
||||
if (!price || !discount) return price;
|
||||
return price * (1 - discount / 100);
|
||||
};
|
||||
|
||||
// Преобразование данных для компонента BestPriceItem
|
||||
const bestPriceItems = bestPriceProducts
|
||||
.filter(item => item.isActive)
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
.slice(0, 8) // Ограничиваем до 8 товаров
|
||||
.map(item => ({
|
||||
image: item.product.images?.[0]?.url || "images/162615.webp", // Fallback изображение
|
||||
discount: `-${item.discount}%`,
|
||||
price: formatPrice(calculateDiscountedPrice(item.product.retailPrice, item.discount)),
|
||||
oldPrice: formatPrice(item.product.retailPrice),
|
||||
title: item.product.name,
|
||||
brand: item.product.brand || "",
|
||||
article: item.product.article,
|
||||
productId: item.product.id,
|
||||
}));
|
||||
|
||||
// Если нет товаров, не показываем секцию
|
||||
if (bestPriceItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-hflex flex-block-118">
|
||||
<div className="w-layout-vflex flex-block-119">
|
||||
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
|
||||
<div className="text-block-58">Подборка лучших предложенийпо цене</div>
|
||||
<a href="#" className="button-24 w-button">Показать все</a>
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-121">
|
||||
{bestPriceItems.map((item, i) => (
|
||||
<BestPriceItem key={i} {...item} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default BestPriceSection;
|
@ -1,67 +1,350 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { GET_DAILY_PRODUCTS, PARTS_INDEX_SEARCH_BY_ARTICLE } from '@/lib/graphql';
|
||||
import Link from 'next/link';
|
||||
|
||||
const ProductOfDaySection: React.FC = () => (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer batd w-container">
|
||||
<div className="w-layout-hflex flex-block-108">
|
||||
<div data-delay="4000" data-animation="slide" className="slider w-slider" data-autoplay="false" data-easing="ease" data-hide-arrows="false" data-disable-swipe="false" data-autoplay-limit="0" data-nav-spacing="3" data-duration="500" data-infinite="true">
|
||||
<div className="mask w-slider-mask">
|
||||
<div className="slide w-slide">
|
||||
<div className="div-block-128"></div>
|
||||
interface DailyProduct {
|
||||
id: string;
|
||||
discount?: number;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
article?: string;
|
||||
brand?: string;
|
||||
retailPrice?: number;
|
||||
wholesalePrice?: number;
|
||||
images: Array<{
|
||||
id: string;
|
||||
url: string;
|
||||
alt?: string;
|
||||
order: number;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
const ProductOfDaySection: React.FC = () => {
|
||||
// Получаем текущую дату в формате YYYY-MM-DD
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
|
||||
// Состояние для текущего слайда
|
||||
const [currentSlide, setCurrentSlide] = useState(0);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { data, loading, error } = useQuery<{ dailyProducts: DailyProduct[] }>(
|
||||
GET_DAILY_PRODUCTS,
|
||||
{
|
||||
variables: { displayDate: today },
|
||||
errorPolicy: 'all'
|
||||
}
|
||||
);
|
||||
|
||||
// Фильтруем только активные товары и сортируем по sortOrder
|
||||
const activeProducts = React.useMemo(() => {
|
||||
if (!data?.dailyProducts) return [];
|
||||
return data.dailyProducts
|
||||
.filter(item => item.isActive)
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
}, [data]);
|
||||
|
||||
// Получаем данные из PartsIndex для текущего товара
|
||||
const currentProduct = activeProducts[currentSlide];
|
||||
const { data: partsIndexData } = useQuery(
|
||||
PARTS_INDEX_SEARCH_BY_ARTICLE,
|
||||
{
|
||||
variables: {
|
||||
articleNumber: currentProduct?.product?.article || '',
|
||||
brandName: currentProduct?.product?.brand || '',
|
||||
lang: 'ru'
|
||||
},
|
||||
skip: !currentProduct?.product?.article || !currentProduct?.product?.brand,
|
||||
errorPolicy: 'ignore'
|
||||
}
|
||||
);
|
||||
|
||||
// Функция для расчета цены со скидкой
|
||||
const calculateDiscountedPrice = (price: number, discount?: number) => {
|
||||
if (!discount) return price;
|
||||
return price * (1 - discount / 100);
|
||||
};
|
||||
|
||||
// Функция для форматирования цены
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat('ru-RU').format(Math.round(price));
|
||||
};
|
||||
|
||||
// Функция для получения изображения товара
|
||||
const getProductImage = (product: DailyProduct['product']) => {
|
||||
// Сначала пытаемся использовать собственные изображения товара
|
||||
const productImage = product.images
|
||||
?.sort((a, b) => a.order - b.order)
|
||||
?.[0];
|
||||
|
||||
if (productImage) {
|
||||
return {
|
||||
url: productImage.url,
|
||||
alt: productImage.alt || product.name,
|
||||
source: 'internal'
|
||||
};
|
||||
}
|
||||
|
||||
// Если нет собственных изображений, используем PartsIndex
|
||||
const partsIndexImage = partsIndexData?.partsIndexSearchByArticle?.images?.[0];
|
||||
if (partsIndexImage) {
|
||||
return {
|
||||
url: partsIndexImage,
|
||||
alt: product.name,
|
||||
source: 'partsindex'
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Обработчики для слайдера
|
||||
const handlePrevSlide = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
|
||||
};
|
||||
|
||||
const handleNextSlide = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
|
||||
};
|
||||
|
||||
const handlePrevSlideTouch = (e: React.TouchEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
|
||||
};
|
||||
|
||||
const handleNextSlideTouch = (e: React.TouchEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
|
||||
};
|
||||
|
||||
const handleSlideIndicator = (index: number) => {
|
||||
setCurrentSlide(index);
|
||||
};
|
||||
|
||||
// Сброс слайда при изменении товаров
|
||||
useEffect(() => {
|
||||
setCurrentSlide(0);
|
||||
}, [activeProducts]);
|
||||
|
||||
// Если нет активных товаров дня, не показываем секцию
|
||||
if (loading || error || activeProducts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const product = currentProduct.product;
|
||||
const productImage = getProductImage(product);
|
||||
|
||||
const originalPrice = product.retailPrice || product.wholesalePrice || 0;
|
||||
const discountedPrice = calculateDiscountedPrice(originalPrice, currentProduct.discount);
|
||||
const hasDiscount = currentProduct.discount && currentProduct.discount > 0;
|
||||
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer batd w-container">
|
||||
<div className="w-layout-hflex flex-block-108">
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className="slider w-slider"
|
||||
>
|
||||
<div className="mask w-slider-mask">
|
||||
{activeProducts.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`slide w-slide ${index === currentSlide ? 'w--current' : ''}`}
|
||||
style={{
|
||||
display: index === currentSlide ? 'block' : 'none'
|
||||
}}
|
||||
>
|
||||
<div className="div-block-128"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-slide"></div>
|
||||
<div className="w-slide"></div>
|
||||
</div>
|
||||
<div className="left-arrow w-slider-arrow-left">
|
||||
<div className="div-block-34">
|
||||
<div className="code-embed-14 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-arrow w-slider-arrow-right">
|
||||
<div className="div-block-34 right">
|
||||
<div className="code-embed-14 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round"></div>
|
||||
</div>
|
||||
<div className="div-block-129">
|
||||
<div className="w-layout-hflex flex-block-109">
|
||||
<h1 className="heading-18">ТОВАРЫ ДНЯ</h1>
|
||||
<div className="saletag">-35%</div>
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-110">
|
||||
<div className="w-layout-vflex flex-block-111">
|
||||
<div className="w-layout-hflex flex-block-16">
|
||||
<div className="text-block-8">от 17 087 ₽</div>
|
||||
<div className="text-block-9">22 347 ₽</div>
|
||||
|
||||
{/* Стрелки слайдера (показываем только если товаров больше 1) */}
|
||||
{activeProducts.length > 1 && (
|
||||
<>
|
||||
<div className="left-arrow w-slider-arrow-left">
|
||||
<div className="div-block-34">
|
||||
<div className="code-embed-14 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="right-arrow w-slider-arrow-right">
|
||||
<div className="div-block-34 right">
|
||||
<div className="code-embed-14 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Индикаторы слайдов */}
|
||||
{activeProducts.length > 1 && (
|
||||
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round">
|
||||
{activeProducts.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-slider-dot ${index === currentSlide ? 'w--current' : ''}`}
|
||||
onClick={() => handleSlideIndicator(index)}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
style={{ cursor: 'pointer', zIndex: 10 }}
|
||||
></div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-block-10">Аккумуляторная батарея TYUMEN BATTERY "STANDARD", 6CT-60L, 60</div>
|
||||
</div><img width="Auto" height="Auto" alt="" src="/images/162615.webp" loading="lazy" srcSet="/images/162615-p-500.webp 500w, /images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" className="image-5-copy" />
|
||||
)}
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-125">
|
||||
<div className="div-block-134">
|
||||
<div className="code-embed-17 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg></div>
|
||||
|
||||
<div className="div-block-129">
|
||||
<div className="w-layout-hflex flex-block-109">
|
||||
<h1 className="heading-18">ТОВАРЫ ДНЯ</h1>
|
||||
{hasDiscount && (
|
||||
<div className="saletag">-{currentProduct.discount}%</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="div-block-134-copy">
|
||||
<div className="code-embed-17 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg></div>
|
||||
|
||||
<div className="w-layout-hflex flex-block-110">
|
||||
<div className="w-layout-vflex flex-block-111">
|
||||
<div className="w-layout-hflex flex-block-16">
|
||||
<div className="text-block-8">
|
||||
от {formatPrice(discountedPrice)} ₽
|
||||
</div>
|
||||
{hasDiscount && (
|
||||
<div className="text-block-9">
|
||||
{formatPrice(originalPrice)} ₽
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-block-10" title={product.name}>
|
||||
{product.brand && `${product.brand} `}
|
||||
{product.name}
|
||||
</div>
|
||||
{/* Счетчик товаров если их больше одного */}
|
||||
{activeProducts.length > 1 && (
|
||||
<div className="text-xs text-gray-500 mt-2">
|
||||
{currentSlide + 1} из {activeProducts.length}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{productImage && (
|
||||
<div className="relative">
|
||||
<img
|
||||
width="Auto"
|
||||
height="Auto"
|
||||
alt={productImage.alt}
|
||||
src={productImage.url}
|
||||
loading="lazy"
|
||||
className="image-5-copy"
|
||||
style={{ cursor: 'pointer' }}
|
||||
/>
|
||||
{/* Метка источника изображения */}
|
||||
{productImage.source === 'partsindex' && (
|
||||
<div className="absolute bottom-0 right-0 bg-blue-600 text-white text-xs px-2 py-1 rounded-tl">
|
||||
Parts Index
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-layout-hflex flex-block-126">
|
||||
<div className="div-block-135"></div>
|
||||
<div className="div-block-135"></div>
|
||||
|
||||
<div className="w-layout-hflex flex-block-125">
|
||||
{/* Левая стрелка - предыдущий товар */}
|
||||
{activeProducts.length > 1 ? (
|
||||
<div
|
||||
className="div-block-134"
|
||||
onClick={handlePrevSlide}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onTouchStart={handlePrevSlideTouch}
|
||||
style={{ cursor: 'pointer' }}
|
||||
title="Предыдущий товар"
|
||||
>
|
||||
<div className="code-embed-17 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="div-block-134" style={{ opacity: 0.3 }}>
|
||||
<div className="code-embed-17 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Правая стрелка - следующий товар */}
|
||||
{activeProducts.length > 1 ? (
|
||||
<div
|
||||
className="div-block-134-copy"
|
||||
onClick={handleNextSlide}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onTouchStart={handleNextSlideTouch}
|
||||
style={{ cursor: 'pointer' }}
|
||||
title="Следующий товар"
|
||||
>
|
||||
<div className="code-embed-17 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="div-block-134-copy" style={{ opacity: 0.3 }}>
|
||||
<div className="code-embed-17 w-embed">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Индикаторы точки */}
|
||||
<div className="w-layout-hflex flex-block-126">
|
||||
{activeProducts.length > 1 ? (
|
||||
activeProducts.map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="div-block-135"
|
||||
onClick={() => handleSlideIndicator(index)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
opacity: index === currentSlide ? 1 : 0.5,
|
||||
backgroundColor: index === currentSlide ? 'currentColor' : 'rgba(128,128,128,0.5)'
|
||||
}}
|
||||
title={`Товар ${index + 1}`}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
<div className="div-block-135" style={{ backgroundColor: 'currentColor' }}></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductOfDaySection;
|
@ -1,54 +1,122 @@
|
||||
import React from "react";
|
||||
import ArticleCard from "../ArticleCard";
|
||||
import { PartsAPIArticle } from "@/types/partsapi";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import TopSalesItem from "../TopSalesItem";
|
||||
import { GET_TOP_SALES_PRODUCTS } from "../../lib/graphql";
|
||||
|
||||
// Моковые данные для топ продаж
|
||||
const topSalesArticles: PartsAPIArticle[] = [
|
||||
{
|
||||
artId: "1",
|
||||
artArticleNr: "6CT-60L",
|
||||
artSupBrand: "TYUMEN BATTERY",
|
||||
supBrand: "TYUMEN BATTERY",
|
||||
supId: 1,
|
||||
productGroup: "Аккумуляторная батарея",
|
||||
ptId: 1,
|
||||
},
|
||||
{
|
||||
artId: "2",
|
||||
artArticleNr: "A0001",
|
||||
artSupBrand: "Borsehung",
|
||||
supBrand: "Borsehung",
|
||||
supId: 2,
|
||||
productGroup: "Масляный фильтр",
|
||||
ptId: 2,
|
||||
},
|
||||
// ...добавьте еще 6 статей для примера
|
||||
...Array(6).fill(0).map((_, i) => ({
|
||||
artId: `${i+3}`,
|
||||
artArticleNr: `ART${i+3}`,
|
||||
artSupBrand: `Brand${i+3}`,
|
||||
supBrand: `Brand${i+3}`,
|
||||
supId: i+3,
|
||||
productGroup: `Product Group ${i+3}`,
|
||||
ptId: i+3,
|
||||
}))
|
||||
];
|
||||
interface TopSalesProductData {
|
||||
id: string;
|
||||
productId: string;
|
||||
isActive: boolean;
|
||||
sortOrder: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article?: string;
|
||||
brand?: string;
|
||||
retailPrice?: number;
|
||||
images: { url: string; alt?: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
const TopSalesSection: React.FC = () => (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex inbt">
|
||||
<div className="w-layout-hflex flex-block-31">
|
||||
<h2 className="heading-4">Топ продаж</h2>
|
||||
const TopSalesSection: React.FC = () => {
|
||||
const { data, loading, error } = useQuery(GET_TOP_SALES_PRODUCTS);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex inbt">
|
||||
<div className="w-layout-hflex flex-block-31">
|
||||
<h2 className="heading-4">Топ продаж</h2>
|
||||
</div>
|
||||
<div className="w-layout-hflex core-product-search">
|
||||
<div className="text-block-58">Загрузка...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-layout-hflex core-product-search">
|
||||
{topSalesArticles.map((article, i) => (
|
||||
<ArticleCard key={article.artId || i} article={article} index={i} />
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.error('Ошибка загрузки топ продаж:', error);
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex inbt">
|
||||
<div className="w-layout-hflex flex-block-31">
|
||||
<h2 className="heading-4">Топ продаж</h2>
|
||||
</div>
|
||||
<div className="w-layout-hflex core-product-search">
|
||||
<div className="text-block-58">Ошибка загрузки</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Фильтруем активные товары и сортируем по sortOrder
|
||||
const activeTopSalesProducts = (data?.topSalesProducts || [])
|
||||
.filter((item: TopSalesProductData) => item.isActive)
|
||||
.sort((a: TopSalesProductData, b: TopSalesProductData) => a.sortOrder - b.sortOrder)
|
||||
.slice(0, 8); // Ограничиваем до 8 товаров
|
||||
|
||||
if (activeTopSalesProducts.length === 0) {
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex inbt">
|
||||
<div className="w-layout-hflex flex-block-31">
|
||||
<h2 className="heading-4">Топ продаж</h2>
|
||||
</div>
|
||||
<div className="w-layout-hflex core-product-search">
|
||||
<div className="text-block-58">Нет товаров в топ продаж</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="main">
|
||||
<div className="w-layout-blockcontainer container w-container">
|
||||
<div className="w-layout-vflex inbt">
|
||||
<div className="w-layout-hflex flex-block-31">
|
||||
<h2 className="heading-4">Топ продаж</h2>
|
||||
</div>
|
||||
<div className="w-layout-hflex core-product-search">
|
||||
{activeTopSalesProducts.map((item: TopSalesProductData) => {
|
||||
const product = item.product;
|
||||
const price = product.retailPrice
|
||||
? `от ${product.retailPrice.toLocaleString('ru-RU')} ₽`
|
||||
: 'По запросу';
|
||||
|
||||
const image = product.images && product.images.length > 0
|
||||
? product.images[0].url
|
||||
: '/images/162615.webp'; // Fallback изображение
|
||||
|
||||
const title = product.name;
|
||||
const brand = product.brand || 'Неизвестный бренд';
|
||||
|
||||
return (
|
||||
<TopSalesItem
|
||||
key={item.id}
|
||||
image={image}
|
||||
price={price}
|
||||
title={title}
|
||||
brand={brand}
|
||||
article={product.article}
|
||||
productId={product.id}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopSalesSection;
|
@ -1,5 +1,50 @@
|
||||
import { gql } from '@apollo/client'
|
||||
|
||||
export const GET_BEST_PRICE_PRODUCTS = gql`
|
||||
query GetBestPriceProducts {
|
||||
bestPriceProducts {
|
||||
id
|
||||
productId
|
||||
discount
|
||||
isActive
|
||||
sortOrder
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
brand
|
||||
retailPrice
|
||||
images {
|
||||
url
|
||||
alt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const GET_TOP_SALES_PRODUCTS = gql`
|
||||
query GetTopSalesProducts {
|
||||
topSalesProducts {
|
||||
id
|
||||
productId
|
||||
isActive
|
||||
sortOrder
|
||||
product {
|
||||
id
|
||||
name
|
||||
article
|
||||
brand
|
||||
retailPrice
|
||||
images {
|
||||
url
|
||||
alt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const CHECK_CLIENT_BY_PHONE = gql`
|
||||
mutation CheckClientByPhone($phone: String!) {
|
||||
checkClientByPhone(phone: $phone) {
|
||||
@ -1607,3 +1652,30 @@ export const GET_CATEGORY_PRODUCTS_WITH_OFFERS = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Запрос для получения товаров дня
|
||||
export const GET_DAILY_PRODUCTS = gql`
|
||||
query GetDailyProducts($displayDate: String!) {
|
||||
dailyProducts(displayDate: $displayDate) {
|
||||
id
|
||||
discount
|
||||
isActive
|
||||
sortOrder
|
||||
product {
|
||||
id
|
||||
name
|
||||
slug
|
||||
article
|
||||
brand
|
||||
retailPrice
|
||||
wholesalePrice
|
||||
images {
|
||||
id
|
||||
url
|
||||
alt
|
||||
order
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
Reference in New Issue
Block a user