From 2f30bdc783d14d0f08fbc8af1e6e9a982dfd2546 Mon Sep 17 00:00:00 2001 From: ilya_zahvatkin Date: Thu, 1 May 2025 20:15:28 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?UTF-8?q?=D0=B8=20docker-compose=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=83?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82=D0=B0=20Meilisearch.=20=D0=92=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D1=82=D0=B0?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=20=D0=B8=20=D0=BC=D0=BE=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BA=D0=BE=D0=BD.=20=D0=9E?= =?UTF-8?q?=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=20=D0=BA=D0=BE=D1=80=D0=B7=D0=B8=D0=BD=D1=8B,=20=D1=83?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B0=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BF=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D1=8B=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=BE=D0=B2.=20=D0=A3=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=83=D1=81=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=88=D0=B8=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=B8=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B8=20=D0=B2=20=D0=BA=D0=BE=D0=B4=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 4 +- frontend/app/(main)/cart/page.tsx | 161 ++++---- frontend/app/(main)/wishlist/page.tsx | 35 +- frontend/app/globals.css | 28 +- frontend/components/layout/site-header.tsx | 4 +- frontend/components/size-guide/size-modal.tsx | 7 +- frontend/components/size-guide/size-table.tsx | 379 +++++++++++++----- frontend/hooks/useCart.ts | 14 +- frontend/hooks/useCategoriesCache.ts | 21 +- frontend/lib/catalog.ts | 22 +- frontend/public/images/.DS_Store | Bin 6148 -> 6148 bytes .../home/{IMG_9135.JPG => IMG_9135.jpeg} | Bin 12 files changed, 433 insertions(+), 242 deletions(-) rename frontend/public/images/home/{IMG_9135.JPG => IMG_9135.jpeg} (100%) diff --git a/docker-compose.yml b/docker-compose.yml index 9c59af0..9e791c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,8 +100,8 @@ services: container_name: meilisearch hostname: meilisearch # Не публикуем порт наружу, доступ только через FastAPI/Nginx - # ports: - # - "7700:7700" + ports: + - "7700:7700" expose: - "7700" # Внутренний порт environment: diff --git a/frontend/app/(main)/cart/page.tsx b/frontend/app/(main)/cart/page.tsx index 6102782..c57ec68 100644 --- a/frontend/app/(main)/cart/page.tsx +++ b/frontend/app/(main)/cart/page.tsx @@ -3,19 +3,16 @@ import { useState, useRef, useEffect } from "react" import Image from "next/image" import Link from "next/link" -import { Trash2, Plus, Minus, ArrowRight, ShoppingBag, Heart, ChevronLeft, Clock, ShieldCheck, Truck, Package } from "lucide-react" +import { Trash2, Plus, Minus, ArrowRight, ShoppingBag, ChevronLeft, Clock, ShieldCheck, Truck, Package } from "lucide-react" import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { motion, AnimatePresence } from "framer-motion" -import { useInView } from "react-intersection-observer" + import { useCart } from "@/hooks/useCart" -import { useRouter } from "next/navigation" + import { formatPrice } from "@/lib/utils" -import { toast } from "@/components/ui/use-toast" -import { ProductCard } from "@/components/product/product-card" -import { normalizeProductImage } from "@/lib/catalog" // Импортируем нормализацию -import React from "react" // Добавляем импорт React для React.memo +import { normalizeProductImage } from "@/lib/catalog" +import React from "react" // Компонент для анимации изменения чисел const AnimatedNumber = ({ value, className }: { value: number, className?: string }) => { @@ -45,38 +42,50 @@ const AnimatedPrice = ({ price, className }: { price: number, className?: string ); }; -// Убираем компонент CartItemImage - -interface RecommendedProduct { - id: number - name: string - price: number - image: string - slug: string -} +// Компонент для отображения товаров в корзине export default function CartPage() { const { cart, loading, updateCartItem, removeFromCart, clearCart } = useCart() - // Убираем useInView для ref1 и ref2, так как анимация будет управляться isFirstRender - // const [ref1, inView1] = useInView({ triggerOnce: true, threshold: 0.1 }) - // const [ref2, inView2] = useInView({ triggerOnce: true, threshold: 0.1 }) - const [ref3, inView3] = useInView({ triggerOnce: true, threshold: 0.1 }) // Оставляем для будущих секций, если нужно - const router = useRouter() + // Используем isFirstRender вместо useInView для управления анимацией + const [processing, setProcessing] = useState<{ [key: number]: boolean }>({}) - + // Флаг для отслеживания первого рендера - const isFirstRender = useRef(true); - + // const isFirstRender = useRef(true); // Удаляем isFirstRender + // Сбрасываем флаг первого рендера после монтирования + // и инициализируем фоновые изображения, если они есть useEffect(() => { - return () => { - isFirstRender.current = false; + // Сбрасываем флаг первого рендера + // isFirstRender.current = false; // Удаляем + + // Инициализация фоновых изображений для элементов с классом lazy-bg + // Оставляем эту логику, т.к. она не связана напрямую с ошибкой гидратации + const initLazyBackgrounds = () => { + const lazyBackgrounds = document.querySelectorAll('.lazy-bg'); + if (lazyBackgrounds.length > 0) { + lazyBackgrounds.forEach(bg => { + const element = bg as HTMLElement; + const bgUrl = element.getAttribute('data-bg'); + if (bgUrl) { + element.style.backgroundImage = `url(${bgUrl})`; // Ensure url() wrapper + } + }); + } }; - }, []); + + // Запускаем инициализацию фоновых изображений + initLazyBackgrounds(); + + // Очистка при размонтировании + // return () => { // Удаляем связанное с isFirstRender + // isFirstRender.current = true; + // }; + }, []); // Пустой массив зависимостей означает, что эффект запускается один раз после монтирования const handleQuantityChange = async (itemId: number, newQuantity: number) => { if (processing[itemId]) return - + setProcessing(prev => ({ ...prev, [itemId]: true })) try { await updateCartItem(itemId, newQuantity) @@ -87,7 +96,7 @@ export default function CartPage() { const handleRemoveItem = async (itemId: number) => { if (processing[itemId]) return - + setProcessing(prev => ({ ...prev, [itemId]: true })) try { await removeFromCart(itemId) @@ -96,9 +105,7 @@ export default function CartPage() { } } - const handleCheckout = () => { - router.push('/checkout') - } + // Переход к оформлению заказа происходит через Link // Calculate totals const subtotal = cart.total_amount ?? 0 @@ -109,14 +116,14 @@ export default function CartPage() { // Анимационные варианты const containerVariants = { hidden: { opacity: 0 }, - visible: { + visible: { opacity: 1, - transition: { + transition: { staggerChildren: 0.1 } } } - + const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5 } } @@ -152,9 +159,9 @@ export default function CartPage() { {loading ? ( - {/* Cart Items */} {/* Убираем ref1 и меняем логику animate */} -
@@ -195,28 +202,27 @@ export default function CartPage() {
- {cart.items.map((item, index) => ( + {cart.items.map((item, index) => ( - {/* Изображение товара через background-image */} + {/* Изображение товара через Image компонент */}
{item.product_image ? ( -
+
+ {item.product_name +
) : (
@@ -227,22 +233,22 @@ export default function CartPage() { {/* Информация о товаре */}
-

{item.product_name || "Товар"}

- + {/* Вариант товара - размер и цвет */}
{item.variant_name && ( Размер: {item.variant_name} )}
- + {/* Количество и цена */}
@@ -259,11 +265,11 @@ export default function CartPage() { )} - + - +
- +
Цена:
@@ -286,7 +292,7 @@ export default function CartPage() {
- +
) diff --git a/frontend/app/(main)/wishlist/page.tsx b/frontend/app/(main)/wishlist/page.tsx index 748f446..cf6c6ab 100644 --- a/frontend/app/(main)/wishlist/page.tsx +++ b/frontend/app/(main)/wishlist/page.tsx @@ -10,37 +10,6 @@ import { useWishlist } from "@/hooks/use-wishlist" import catalogService, { ProductDetails } from "@/lib/catalog" import { Skeleton } from "@/components/ui/skeleton" -const recommended = [ - { - id: 5, - name: "ЮБКА МИДИ ПЛИССЕ", - price: 3490, - image: "/placeholder.svg?height=600&width=400", - slug: "skirt-midi", - }, - { - id: 6, - name: "ПАЛЬТО ИЗ ШЕРСТИ", - price: 12990, - image: "/placeholder.svg?height=600&width=400", - slug: "coat-wool", - }, - { - id: 7, - name: "ДЖЕМПЕР ИЗ КАШЕМИРА", - price: 7990, - oldPrice: 9990, - image: "/placeholder.svg?height=600&width=400", - slug: "sweater-cashmere", - }, - { - id: 8, - name: "РУБАШКА ОВЕРСАЙЗ", - price: 4490, - image: "/placeholder.svg?height=600&width=400", - slug: "shirt-oversize", - }, -] export default function WishlistPage() { const { items, removeItem, clearWishlist } = useWishlist() @@ -158,7 +127,7 @@ export default function WishlistPage() { {/* Рекомендации */} -
+ {/*

Рекомендуем вам

{recommended.map((item) => ( @@ -174,7 +143,7 @@ export default function WishlistPage() { /> ))}
-
+
*/}
) diff --git a/frontend/app/globals.css b/frontend/app/globals.css index d53b5eb..a04fd04 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -88,7 +88,7 @@ h1 { @apply font-light; } - + h2 { @apply font-normal; } @@ -150,5 +150,31 @@ .product-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); } + + /* Стили для таблицы размеров на мобильных устройствах */ + .size-table-mobile .tabs-list { + font-size: 0.75rem; + padding: 0.25rem; + } + + .size-table-mobile .tab-trigger { + padding: 0.25rem 0.5rem; + min-width: auto; + } + + /* Улучшение читаемости модальных окон на мобильных устройствах */ + .dialog-content-mobile { + padding: 0.75rem !important; + max-height: 90vh !important; + overflow-y: auto !important; + width: 95vw !important; + } + + /* Уменьшение отступов в таблице размеров */ + .size-table-mobile td, + .size-table-mobile th { + padding: 0.5rem 0.25rem; + font-size: 0.75rem; + } } diff --git a/frontend/components/layout/site-header.tsx b/frontend/components/layout/site-header.tsx index bc28ffd..a9c81a5 100644 --- a/frontend/components/layout/site-header.tsx +++ b/frontend/components/layout/site-header.tsx @@ -116,13 +116,13 @@ export function SiteHeader() { > Каталог - Личный кабинет - + */}
{/* Дополнительные ссылки */} diff --git a/frontend/components/size-guide/size-modal.tsx b/frontend/components/size-guide/size-modal.tsx index 80edae5..14397b9 100644 --- a/frontend/components/size-guide/size-modal.tsx +++ b/frontend/components/size-guide/size-modal.tsx @@ -8,8 +8,11 @@ import { import { Button } from "@/components/ui/button" import { Ruler } from "lucide-react" import { SizeTable } from "./size-table" +import { useIsMobile } from "@/hooks/use-mobile" export function SizeModal() { + const isMobile = useIsMobile() + return ( @@ -18,7 +21,7 @@ export function SizeModal() { Таблица размеров - + Таблица размеров @@ -28,4 +31,4 @@ export function SizeModal() { ) -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/components/size-guide/size-table.tsx b/frontend/components/size-guide/size-table.tsx index 19b2710..35a1b8b 100644 --- a/frontend/components/size-guide/size-table.tsx +++ b/frontend/components/size-guide/size-table.tsx @@ -1,63 +1,153 @@ -import { Ruler, Info } from "lucide-react" +import { Ruler, Info, ChevronLeft, ChevronRight } from "lucide-react" import Image from "next/image" +import { useState } from "react" +import { useIsMobile } from "@/hooks/use-mobile" +import { Button } from "@/components/ui/button" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" + +// Данные размеров для повторного использования +const sizeData = [ + { + ruSize: "40-42", + intSize: "XS", + bust: "88-90", + waist: "60-64", + hips: "88-90" + }, + { + ruSize: "42-44", + intSize: "S", + bust: "90-92", + waist: "64-68", + hips: "90-94" + }, + { + ruSize: "44-46", + intSize: "M", + bust: "92-96", + waist: "68-75", + hips: "94-96" + }, + { + ruSize: "46-48", + intSize: "L", + bust: "96-100", + waist: "75-80", + hips: "96-104" + }, + { + ruSize: "48-50", + intSize: "XL", + bust: "100-108", + waist: "80-84", + hips: "104-108" + } +] + +// Компонент для мобильной версии таблицы размеров +function MobileSizeTable() { + const [activeIndex, setActiveIndex] = useState(0) + + const nextSize = () => { + setActiveIndex((prev) => (prev === sizeData.length - 1 ? 0 : prev + 1)) + } + + const prevSize = () => { + setActiveIndex((prev) => (prev === 0 ? sizeData.length - 1 : prev - 1)) + } + + const currentSize = sizeData[activeIndex] + + return ( +
+
+ +
+ {currentSize.intSize} ({currentSize.ruSize}) +
+ +
+ +
+
+ Обхват груди: + {currentSize.bust} см +
+
+ Обхват талии: + {currentSize.waist} см +
+
+ Обхват бедер: + {currentSize.hips} см +
+
+ +
+ + + {sizeData.map((size) => ( + setActiveIndex(sizeData.findIndex(s => s.intSize === size.intSize))} + className="tab-trigger" + > + {size.intSize} + + ))} + + +
+
+ ) +} + +// Компонент для десктопной версии таблицы размеров +function DesktopSizeTable() { + return ( +
+ + + + + + + + + + + + {sizeData.map((size, index) => ( + + + + + + + + ))} + +
Российский размерРазмер производ-ля (INT)Обхват груди, смОбхват талии, смОбхват бедер, см
{size.ruSize}{size.intSize}{size.bust}{size.waist}{size.hips}
+
+ ) +} export function SizeTable() { + const isMobile = useIsMobile() + return ( -
- {/* Таблица размеров */} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Российский размерРазмер производ-ля (INT)Обхват груди, смОбхват талии, смОбхват бедер, см
40-42XS88-9060-6488-90
42-44S90-9264-6890-94
44-46M92-9668-7594-96
46-48L96-10075-8096-104
48-50XL100-10880-84104-108
-
+
+ {/* Таблица размеров - адаптивная версия */} + {isMobile ? : } {/* Инструкции по измерению */}
+ {/* Изображение схемы измерений */}
- -
-
-

- - 1. ОБХВАТ ГРУДИ -

-

- Сантиметровая лента должна проходить по наиболее выступающим точкам груди, сбоку - под подмышечными впадинами, обхватываю лопатки сзади. -

-
+ {/* Мобильная версия инструкций */} + {isMobile ? (
-

- - 2. ОБХВАТ ТАЛИИ -

-

- Измеряется горизонтально в самой узкой части талии. При измерении лента должна плотно (без натяжения) прилегать к телу. -

-
+ + + 1 + 2 + 3 + 4 + 5 + -
-

- - 3. ОБХВАТ БЕДЕР -

-

- Сантиметровая лента проходит строго горизонтально по наиболее выступающим точкам ягодиц. -

-
+ +

+ + 1. ОБХВАТ ГРУДИ +

+

+ Сантиметровая лента должна проходить по наиболее выступающим точкам груди, сбоку - под подмышечными впадинами, обхватываю лопатки сзади. +

+
-
-

- - 4. ДЛИНА РУКАВОВ -

-

- Измеряется сантиметровой лентой от шва соединения с проймой до нижнего края рукава. -

-
+ +

+ + 2. ОБХВАТ ТАЛИИ +

+

+ Измеряется горизонтально в самой узкой части талии. При измерении лента должна плотно (без натяжения) прилегать к телу. +

+
-
-

- - 5. ДЛИНА БРЮЧИН -

-

- Данная мерка снимается по боковому шву от верхнего края пояса до нижнего края брюк. -

-
+ +

+ + 3. ОБХВАТ БЕДЕР +

+

+ Сантиметровая лента проходит строго горизонтально по наиболее выступающим точкам ягодиц. +

+
-
-
- -

- Для наиболее точного определения размера рекомендуем снять мерки с себя или похожей одежды, сверить их с таблицей размеров и только после этого сделать заказ. -

+ +

+ + 4. ДЛИНА РУКАВОВ +

+

+ Измеряется сантиметровой лентой от шва соединения с проймой до нижнего края рукава. +

+
+ + +

+ + 5. ДЛИНА БРЮЧИН +

+

+ Данная мерка снимается по боковому шву от верхнего края пояса до нижнего края брюк. +

+
+ + +
+
+ +

+ Для наиболее точного определения размера рекомендуем снять мерки с себя или похожей одежды, сверить их с таблицей размеров и только после этого сделать заказ. +

+
-
+ ) : ( + /* Десктопная версия инструкций */ +
+
+

+ + 1. ОБХВАТ ГРУДИ +

+

+ Сантиметровая лента должна проходить по наиболее выступающим точкам груди, сбоку - под подмышечными впадинами, обхватываю лопатки сзади. +

+
+ +
+

+ + 2. ОБХВАТ ТАЛИИ +

+

+ Измеряется горизонтально в самой узкой части талии. При измерении лента должна плотно (без натяжения) прилегать к телу. +

+
+ +
+

+ + 3. ОБХВАТ БЕДЕР +

+

+ Сантиметровая лента проходит строго горизонтально по наиболее выступающим точкам ягодиц. +

+
+ +
+

+ + 4. ДЛИНА РУКАВОВ +

+

+ Измеряется сантиметровой лентой от шва соединения с проймой до нижнего края рукава. +

+
+ +
+

+ + 5. ДЛИНА БРЮЧИН +

+

+ Данная мерка снимается по боковому шву от верхнего края пояса до нижнего края брюк. +

+
+ +
+
+ +

+ Для наиболее точного определения размера рекомендуем снять мерки с себя или похожей одежды, сверить их с таблицей размеров и только после этого сделать заказ. +

+
+
+
+ )}
) -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/hooks/useCart.ts b/frontend/hooks/useCart.ts index 6e82806..507de52 100644 --- a/frontend/hooks/useCart.ts +++ b/frontend/hooks/useCart.ts @@ -91,7 +91,9 @@ export function useCart() { // Обновление количества товара в корзине const updateCartItem = useCallback(async (id: number, quantity: number) => { try { - setLoading(true); + // Не устанавливаем глобальный флаг загрузки, так как он используется для индикации загрузки всей корзины + // Вместо этого используем локальный флаг processing в компоненте корзины для каждого элемента + // setLoading(true); setError(null); const result = await cartStore.updateCartItem(id, quantity); @@ -112,14 +114,17 @@ export function useCart() { }); return false; } finally { - setLoading(false); + // Не сбрасываем глобальный флаг загрузки, так как мы его не устанавливали + // setLoading(false); } }, [toast]); // Удаление товара из корзины const removeFromCart = useCallback(async (id: number) => { try { - setLoading(true); + // Не устанавливаем глобальный флаг загрузки, так как он используется для индикации загрузки всей корзины + // Вместо этого используем локальный флаг processing в компоненте корзины для каждого элемента + // setLoading(true); setError(null); const result = await cartStore.removeFromCart(id); @@ -146,7 +151,8 @@ export function useCart() { }); return false; } finally { - setLoading(false); + // Не сбрасываем глобальный флаг загрузки, так как мы его не устанавливали + // setLoading(false); } }, [toast]); diff --git a/frontend/hooks/useCategoriesCache.ts b/frontend/hooks/useCategoriesCache.ts index c5d232a..7c9887d 100644 --- a/frontend/hooks/useCategoriesCache.ts +++ b/frontend/hooks/useCategoriesCache.ts @@ -3,15 +3,30 @@ import { useCategories } from './useAdminApi'; import { Category } from '@/lib/catalog-admin'; export default function useCategoriesCache(params: Record = {}) { - const { data, isLoading, error, refetch } = useCategories(params) as { data?: Category[], isLoading: boolean, error: any, refetch: () => void }; - const categories: Category[] = data || []; + const { data, isLoading, error, refetch } = useCategories(params) as { + data?: { categories?: Category[], success?: boolean, total?: number }, + isLoading: boolean, + error: any, + refetch: () => void + }; + + // Извлекаем массив категорий из ответа API + const categories: Category[] = data?.categories || []; // Строим дерево категорий из flat-списка const getCategoryTree = () => { const map = new Map(); + + // Проверяем, что categories - это массив перед вызовом forEach + if (!Array.isArray(categories)) { + console.error('categories is not an array:', categories); + return []; + } + categories.forEach(cat => { map.set(cat.id, { ...cat, children: [] }); }); + const tree: (Category & { children?: Category[] })[] = []; map.forEach(cat => { if (cat.parent_id && map.has(cat.parent_id)) { @@ -30,4 +45,4 @@ export default function useCategoriesCache(params: Record = {}) { refetch, getCategoryTree, }; -} \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/lib/catalog.ts b/frontend/lib/catalog.ts index 6984885..8ec36af 100644 --- a/frontend/lib/catalog.ts +++ b/frontend/lib/catalog.ts @@ -371,17 +371,17 @@ export function normalizeProductImage(imageUrl: string | null | undefined): stri } // Обработка полного URL MinIO - преобразуем в относительный путь - if (imageUrl.includes('45.129.128.113:9000/dressedforsuccess/')) { - // Извлекаем только путь /dressedforsuccess/filename.jpg из полного URL - const match = imageUrl.match(/\/dressedforsuccess\/[^\/]+\.\w+$/); - if (match) { - const relativePath = match[0]; - if (apiStatus.debugMode) { - console.log(`Преобразование полного URL MinIO в относительный путь: ${imageUrl} -> ${relativePath}`); - } - return relativePath; - } - } + // if (imageUrl.includes('45.129.128.113:9000/dressedforsuccess/')) { + // // Извлекаем только путь /dressedforsuccess/filename.jpg из полного URL + // const match = imageUrl.match(/\/dressedforsuccess\/[^\/]+\.\w+$/); + // if (match) { + // const relativePath = match[0]; + // if (apiStatus.debugMode) { + // console.log(`Преобразование полного URL MinIO в относительный путь: ${imageUrl} -> ${relativePath}`); + // } + // return relativePath; + // } + // } // Если это data URI или blob, возвращаем как есть if (imageUrl.startsWith('data:') || imageUrl.startsWith('blob:')) { diff --git a/frontend/public/images/.DS_Store b/frontend/public/images/.DS_Store index 633a81701e05c33f369b6bbcda330b380c367a84..d52ac5ae4443e8e505fb48447153141b250d076e 100644 GIT binary patch delta 51 zcmZoMXfc@J&nU1lU^gS9z-AsMSw>M7h75*$hFpf!vf!e;ocz3W1_lPk&1%diSvIqC H{N)D#T@DSI delta 32 ocmZoMXfc@J&&a