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

View File

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

View File

@ -13,7 +13,7 @@ interface TopSalesItemProps {
productId?: string; productId?: string;
onAddToCart?: (e: React.MouseEvent) => void; onAddToCart?: (e: React.MouseEvent) => void;
discount?: string; // Новый пропс для лейбла/скидки discount?: string; // Новый пропс для лейбла/скидки
// isInCart?: boolean; // Удаляем из пропсов isInCart?: boolean;
} }
const TopSalesItem: React.FC<TopSalesItemProps> = ({ const TopSalesItem: React.FC<TopSalesItemProps> = ({
@ -27,15 +27,13 @@ const TopSalesItem: React.FC<TopSalesItemProps> = ({
discount = 'Топ продаж', // По умолчанию как раньше discount = 'Топ продаж', // По умолчанию как раньше
// isInCart = false, // Удаляем из пропсов // isInCart = false, // Удаляем из пропсов
}) => { }) => {
const { addItem, cartItems = [] } = useCart(); const { addItem, isInCart: isItemInCart } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites(); const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false); const [localInCart, setLocalInCart] = useState(false);
const isItemFavorite = isFavorite(productId, undefined, article, brand); const isItemFavorite = isFavorite(productId, undefined, article, brand);
const isInCart = cartItems.some(item =>
(productId && item.productId === productId) || const isInCart = isItemInCart(productId, undefined, article, brand);
(article && brand && item.article === article && item.brand === brand)
);
const parsePrice = (priceStr: string): number => { const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.'); const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
@ -152,6 +150,7 @@ const TopSalesItem: React.FC<TopSalesItemProps> = ({
filter: isInCart || localInCart ? 'grayscale(1)' : 'none' filter: isInCart || localInCart ? 'grayscale(1)' : 'none'
}} }}
aria-label={isInCart ? 'В корзине' : (localInCart ? 'Добавлено' : 'Добавить в корзину')} aria-label={isInCart ? 'В корзине' : (localInCart ? 'Добавлено' : 'Добавить в корзину')}
title={isInCart ? 'Товар уже в корзине - нажмите для добавления еще' : 'Добавить в корзину'}
> >
<div className="div-block-26"> <div className="div-block-26">
<div className="icon-setting w-embed"> <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 { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
interface CategoryNavGroup {
id: string;
name: string;
image?: string;
}
interface CategoryNavItem { interface CategoryNavItem {
id: string; id: string;
name: string; name: string;
image?: string; image?: string;
groups?: CategoryNavGroup[];
} }
const FALLBACK_CATEGORIES: CategoryNavItem[] = [ const FALLBACK_CATEGORIES: CategoryNavItem[] = [
@ -38,11 +45,15 @@ const CategoryNavSection: React.FC = () => {
: FALLBACK_CATEGORIES; : FALLBACK_CATEGORIES;
const handleCategoryClick = (category: CategoryNavItem) => { const handleCategoryClick = (category: CategoryNavItem) => {
// Получаем первую доступную группу для навигации в PartsIndex режим
const firstGroupId = category.groups && category.groups.length > 0 ? category.groups[0].id : undefined;
router.push({ router.push({
pathname: '/catalog', pathname: '/catalog',
query: { query: {
categoryId: category.id, partsIndexCatalog: category.id,
categoryName: encodeURIComponent(category.name) categoryName: encodeURIComponent(category.name),
...(firstGroupId && { partsIndexCategory: firstGroupId })
} }
}); });
}; };

View File

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

View File

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