2 Commits

Author SHA1 Message Date
f4facf146c build fix 2025-07-30 15:27:32 +03:00
80ed5826e2 novyie parvki 2025-07-30 15:25:18 +03:00
6 changed files with 49 additions and 21 deletions

View File

@ -5,6 +5,7 @@ import { useArticleImage } from '@/hooks/useArticleImage';
import { useCatalogPrices } from '@/hooks/useCatalogPrices';
import { PartsAPIArticle } from '@/types/partsapi';
import toast from 'react-hot-toast';
import { useCart } from '@/contexts/CartContext';
interface ArticleCardProps {
article: PartsAPIArticle;
@ -17,6 +18,9 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
const [shouldShow, setShouldShow] = useState(false);
const [isChecking, setIsChecking] = useState(true);
// Cart context
const { isInCart: isItemInCart } = useCart();
// Используем хук для получения изображения
const { imageUrl, isLoading: imageLoading, error } = useArticleImage(article.artId, {
enabled: !!article.artId
@ -85,6 +89,8 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
const title = [brandName || 'N/A', articleNumber || 'N/A'].filter(part => part !== 'N/A').join(', ');
const brand = brandName || 'Unknown';
let priceText = 'от 17 087 ₽';
const isInCartFlag = isItemInCart(undefined, undefined, articleNumber, brandName);
return (
<CatalogProductCard
image={fallbackImage}
@ -97,6 +103,7 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
brandName={brandName}
artId={article.artId}
onAddToCart={() => {}}
isInCart={isInCartFlag}
/>
);
}
@ -109,6 +116,8 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
const brand = brandName || 'Unknown';
const isInCartFlag = isItemInCart(undefined, undefined, articleNumber, brandName);
// Формируем цену для отображения
let priceText = '';
if (priceData.isLoading) {
@ -144,6 +153,7 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
brandName={brandName}
artId={article.artId}
onAddToCart={handleAddToCart}
isInCart={isInCartFlag}
/>
);
});

View File

@ -28,10 +28,15 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
onAddToCart,
isInCart = false,
}) => {
const { addItem } = useCart();
const { addItem, isInCart: isItemInCart, state: cartState } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false);
// Determine inCart via context if not provided
const inCartContext = isItemInCart(productId, undefined, article, brand);
const inCart = isInCart || inCartContext;
// Проверяем, есть ли товар в избранном
const isItemFavorite = isFavorite(productId, undefined, article, brand);
@ -172,15 +177,16 @@ const BestPriceItem: React.FC<BestPriceItemProps> = ({
<div className="nameitembp">{title}</div>
<a
href="#"
className="button-icon w-inline-block"
onClick={isInCart ? undefined : handleAddToCart}
style={{
cursor: isInCart ? 'default' : (localInCart ? 'default' : 'pointer'),
background: isInCart ? '#9ca3af' : (localInCart ? '#2563eb' : undefined),
opacity: isInCart || localInCart ? 0.5 : 1,
filter: isInCart || localInCart ? 'grayscale(1)' : 'none'
className={`button-icon w-inline-block ${inCart || localInCart ? 'in-cart' : ''}`}
onClick={inCart ? undefined : handleAddToCart}
style={{
cursor: inCart || localInCart ? 'default' : 'pointer',
textDecoration: 'none',
opacity: inCart || localInCart ? 0.5 : 1,
backgroundColor: inCart || localInCart ? '#9ca3af' : undefined
}}
aria-label="Добавить в корзину"
aria-label={inCart || localInCart ? "Товар уже в корзине" : "Добавить в корзину"}
title={inCart || localInCart ? "Товар уже в корзине" : "Добавить в корзину"}
>
<div className="div-block-26">
<div className="icon-setting w-embed">

View File

@ -13,7 +13,7 @@ interface TopSalesItemProps {
productId?: string;
onAddToCart?: (e: React.MouseEvent) => void;
discount?: string; // Новый пропс для лейбла/скидки
// isInCart?: boolean; // Удаляем из пропсов
isInCart?: boolean;
}
const TopSalesItem: React.FC<TopSalesItemProps> = ({
@ -27,15 +27,13 @@ const TopSalesItem: React.FC<TopSalesItemProps> = ({
discount = 'Топ продаж', // По умолчанию как раньше
// isInCart = false, // Удаляем из пропсов
}) => {
const { addItem, cartItems = [] } = useCart();
const { addItem, isInCart: isItemInCart } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false);
const isItemFavorite = isFavorite(productId, undefined, article, brand);
const isInCart = cartItems.some(item =>
(productId && item.productId === productId) ||
(article && brand && item.article === article && item.brand === brand)
);
const isInCart = isItemInCart(productId, undefined, article, brand);
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
@ -152,6 +150,7 @@ const TopSalesItem: React.FC<TopSalesItemProps> = ({
filter: isInCart || localInCart ? 'grayscale(1)' : 'none'
}}
aria-label={isInCart ? 'В корзине' : (localInCart ? 'Добавлено' : 'Добавить в корзину')}
title={isInCart ? 'Товар уже в корзине - нажмите для добавления еще' : 'Добавить в корзину'}
>
<div className="div-block-26">
<div className="icon-setting w-embed">

View File

@ -3,10 +3,17 @@ import { useQuery } from '@apollo/client';
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
import { useRouter } from 'next/router';
interface CategoryNavGroup {
id: string;
name: string;
image?: string;
}
interface CategoryNavItem {
id: string;
name: string;
image?: string;
groups?: CategoryNavGroup[];
}
const FALLBACK_CATEGORIES: CategoryNavItem[] = [
@ -38,11 +45,15 @@ const CategoryNavSection: React.FC = () => {
: FALLBACK_CATEGORIES;
const handleCategoryClick = (category: CategoryNavItem) => {
// Получаем первую доступную группу для навигации в PartsIndex режим
const firstGroupId = category.groups && category.groups.length > 0 ? category.groups[0].id : undefined;
router.push({
pathname: '/catalog',
query: {
categoryId: category.id,
categoryName: encodeURIComponent(category.name)
partsIndexCatalog: category.id,
categoryName: encodeURIComponent(category.name),
...(firstGroupId && { partsIndexCategory: firstGroupId })
}
});
};

View File

@ -23,7 +23,7 @@ const SCROLL_AMOUNT = 340; // px, ширина одной карточки + о
const TopSalesSection: React.FC = () => {
const { data, loading, error } = useQuery(GET_TOP_SALES_PRODUCTS);
const { cartItems = [] } = useCart();
const { isInCart: isItemInCart } = useCart();
const scrollRef = useRef<HTMLDivElement>(null);
const scrollLeft = () => {
@ -214,7 +214,7 @@ const TopSalesSection: React.FC = () => {
const title = product.name;
const brand = product.brand || 'Неизвестный бренд';
const isInCart = cartItems.some(cartItem => cartItem.productId === product.id);
const isInCart = isItemInCart(product.id, undefined, product.article, brand);
return (
<TopSalesItem

View File

@ -44,7 +44,7 @@ export default function Catalog() {
const MAX_BRANDS_DISPLAY = 24; // Сколько брендов показывать изначально
const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE);
const router = useRouter();
const { addItem } = useCart();
const { addItem, isInCart: isItemInCart } = useCart();
const {
partsApiCategory: strId,
categoryName,
@ -1141,6 +1141,8 @@ export default function Catalog() {
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
const priceData = getPrice(productForPrice);
const isLoadingPriceData = isLoadingPrice(productForPrice);
// Fallback cart check via frontend context
const inCartFallback = isItemInCart(entity.id, priceData?.offerKey, entity.code, entity.brand.name);
// Определяем цену для отображения (все товары уже отфильтрованы на сервере)
let displayPrice = "";
@ -1174,7 +1176,7 @@ export default function Catalog() {
productId={entity.id}
artId={entity.id}
offerKey={priceData?.offerKey}
isInCart={priceData?.isInCart}
isInCart={priceData?.isInCart || inCartFallback}
onAddToCart={async () => {
// Если цена не загружена, загружаем её и добавляем в корзину
if (!priceData && !isLoadingPriceData) {