From fc1b55070fd867452bfa3e12ad86a18eebfe761d Mon Sep 17 00:00:00 2001 From: ilya_zahvatkin Date: Mon, 5 May 2025 10:09:19 +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=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=BE=D0=B2=20Mei?= =?UTF-8?q?lisearch,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B0=D1=82=D1=80?= =?UTF-8?q?=D0=B8=D0=B1=D1=83=D1=82=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=84?= =?UTF-8?q?=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8?= =?UTF-8?q?=20=D1=81=D0=BE=D1=80=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B8?= =?UTF-8?q?.=20=D0=92=D0=BD=D0=B5=D1=81=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B=20=D1=84?= =?UTF-8?q?=D1=80=D0=BE=D0=BD=D1=82=D0=B5=D0=BD=D0=B4=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D0=B0.=20=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88=D0=B8=D0=B5=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B8=20=D0=BE=D0=BF=D1=82=D0=B8?= =?UTF-8?q?=D0=BC=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catalog_router.cpython-310.pyc | Bin 16743 -> 18446 bytes backend/app/routers/catalog_router.py | 69 +- .../sync_meilisearch.cpython-310.pyc | Bin 5891 -> 5944 bytes backend/app/scripts/sync_meilisearch.py | 67 +- .../meilisearch_service.cpython-310.pyc | Bin 15516 -> 15526 bytes backend/app/services/meilisearch_service.py | 2 +- backend/update_and_sync_meilisearch.py | 55 + backend/update_meilisearch_settings.py | 20 +- frontend old/.dockerignore | 7 - frontend old/.gitignore | 40 - frontend old/Dockerfile | 66 - frontend old/README.md | 70 - frontend old/app.json | 10 - frontend old/components/AddToCartButton.tsx | 92 - frontend old/components/Collections.tsx | 46 - .../components/CookieNotification.tsx | 43 - frontend old/components/Footer.tsx | 117 -- frontend old/components/Header.tsx | 287 --- frontend old/components/Hero.tsx | 63 - frontend old/components/NewArrivals.tsx | 256 --- frontend old/components/PopularCategories.tsx | 43 - frontend old/components/TabSelector.tsx | 102 - frontend old/components/admin/AdminLayout.tsx | 298 --- .../components/admin/AdminSidebar.tsx | 106 - frontend old/components/admin/ProductForm.tsx | 391 ---- .../components/admin/ProductImageUploader.tsx | 230 --- .../admin/ProductVariantManager.tsx | 336 ---- frontend old/components/admin/Sidebar.tsx | 107 - frontend old/data/categories.ts | 87 - frontend old/data/collections.ts | 63 - frontend old/data/products.ts | 201 -- frontend old/next.config.js | 8 - frontend old/package-lock.json | 1773 ----------------- frontend old/package.json | 28 - frontend old/pages/_app.tsx | 6 - frontend old/pages/account/addresses.tsx | 447 ----- .../pages/account/change-password.tsx | 226 --- frontend old/pages/account/edit.tsx | 253 --- frontend old/pages/account/index.tsx | 149 -- frontend old/pages/account/orders.tsx | 251 --- frontend old/pages/account/orders/[id].tsx | 285 --- frontend old/pages/admin/categories/[id].tsx | 309 --- .../pages/admin/categories/create.tsx | 236 --- frontend old/pages/admin/categories/index.tsx | 242 --- .../pages/admin/collections/index.tsx | 348 ---- frontend old/pages/admin/customers/[id].tsx | 330 --- frontend old/pages/admin/customers/index.tsx | 236 --- frontend old/pages/admin/index.tsx | 320 --- frontend old/pages/admin/orders/[id].tsx | 564 ------ frontend old/pages/admin/orders/index.tsx | 367 ---- frontend old/pages/admin/products/[id].tsx | 560 ------ frontend old/pages/admin/products/create.tsx | 10 - frontend old/pages/admin/products/index.tsx | 423 ---- frontend old/pages/all-products.tsx | 328 --- frontend old/pages/api/hello.js | 5 - frontend old/pages/cart.tsx | 301 --- frontend old/pages/category/[slug].tsx | 187 -- frontend old/pages/category/index.tsx | 81 - frontend old/pages/checkout.tsx | 822 -------- frontend old/pages/collections/[slug].tsx | 200 -- frontend old/pages/collections/index.tsx | 81 - frontend old/pages/forgot-password.tsx | 130 -- frontend old/pages/index.tsx | 148 -- frontend old/pages/login.tsx | 200 -- frontend old/pages/new-arrivals/index.tsx | 117 -- frontend old/pages/order-success.tsx | 142 -- frontend old/pages/order-tracking.tsx | 291 --- frontend old/pages/product/[slug].tsx | 745 ------- frontend old/pages/register.tsx | 260 --- frontend old/pages/reset-password/[token].tsx | 210 -- frontend old/postcss.config.js | 6 - frontend old/public/category/dress.jpg | Bin 24177 -> 0 bytes frontend old/public/category/hat.jpg | Bin 11552 -> 0 bytes frontend old/public/category/jacket.jpg | Bin 24178 -> 0 bytes frontend old/public/category/pants.jpg | Bin 11701 -> 0 bytes frontend old/public/category/scarf.jpg | Bin 35522 -> 0 bytes frontend old/public/category/shoes.jpg | Bin 20241 -> 0 bytes frontend old/public/category/silk.jpg | Bin 10690 -> 0 bytes frontend old/public/category/sweaters.jpg | Bin 13625 -> 0 bytes frontend old/public/favicon.ico | Bin 15086 -> 0 bytes frontend old/public/hero_photos/hero1.png | Bin 3180874 -> 0 bytes .../public/hero_photos/photo_main_main_1.png | Bin 319911 -> 0 bytes .../public/hero_photos/photo_main_main_3.png | Bin 451160 -> 0 bytes frontend old/public/logo.png | Bin 13299 -> 0 bytes frontend old/public/logotip.png | Bin 28054 -> 0 bytes frontend old/public/photos/autumn_winter.jpg | Bin 47918 -> 0 bytes frontend old/public/photos/based_outfit.jpg | Bin 24216 -> 0 bytes .../public/photos/business_outfit.jpg | Bin 29197 -> 0 bytes frontend old/public/photos/head_photo.png | Bin 3180874 -> 0 bytes frontend old/public/photos/night_dress.jpg | Bin 115874 -> 0 bytes frontend old/public/photos/photo1.jpg | Bin 82960 -> 0 bytes frontend old/public/photos/photo2.jpg | Bin 128372 -> 0 bytes frontend old/public/vercel.svg | 4 - frontend old/public/wear/bag1.jpg | Bin 24464 -> 0 bytes frontend old/public/wear/bag2.jpg | Bin 30653 -> 0 bytes frontend old/public/wear/classic_bruk1.jpg | Bin 17344 -> 0 bytes frontend old/public/wear/classic_bruk2.jpg | Bin 10905 -> 0 bytes frontend old/public/wear/coat1.jpg | Bin 96154 -> 0 bytes frontend old/public/wear/coat2.jpg | Bin 22263 -> 0 bytes frontend old/public/wear/hat1.jpg | Bin 25166 -> 0 bytes frontend old/public/wear/jumpsuit_1.jpg | Bin 23867 -> 0 bytes frontend old/public/wear/jumpsuit_2.jpg | Bin 49842 -> 0 bytes frontend old/public/wear/kozh_boots1.jpg | Bin 26699 -> 0 bytes frontend old/public/wear/kozh_boots2.jpg | Bin 20951 -> 0 bytes frontend old/public/wear/palto1.jpg | Bin 115383 -> 0 bytes frontend old/public/wear/palto2.jpg | Bin 47533 -> 0 bytes frontend old/public/wear/pidzak1.jpg | Bin 37816 -> 0 bytes frontend old/public/wear/pidzak2.jpg | Bin 54544 -> 0 bytes frontend old/public/wear/sherst_sweater1.jpg | Bin 42951 -> 0 bytes frontend old/public/wear/sherst_sweater2.jpg | Bin 26358 -> 0 bytes frontend old/public/wear/silk1.jpg | Bin 25913 -> 0 bytes frontend old/public/wear/silk2.jpg | Bin 28184 -> 0 bytes frontend old/public/wear/silk_scarf1.jpg | Bin 86516 -> 0 bytes frontend old/public/wear/silk_scarf2.jpg | Bin 19752 -> 0 bytes frontend old/public/wear/sorochka1.jpg | Bin 32592 -> 0 bytes frontend old/public/wear/sorochka2.jpg | Bin 44761 -> 0 bytes frontend old/services/analytics.ts | 100 - frontend old/services/api.ts | 51 - frontend old/services/auth.ts | 94 - frontend old/services/cart.ts | 68 - frontend old/services/catalog.ts | 571 ------ frontend old/services/orders.ts | 341 ---- frontend old/services/users.ts | 115 -- frontend old/styles/Home.module.css | 409 ---- frontend old/styles/globals.css | 61 - frontend old/tailwind.config.js | 31 - frontend old/tsconfig.json | 28 - frontend/app/(main)/catalog/page.tsx | 360 +++- frontend/app/(main)/layout.tsx | 4 +- frontend/components/layout/site-header.tsx | 218 +- .../components/navigation/MobileNavBar.tsx | 95 + frontend/components/product/product-card.tsx | 44 +- frontend/hooks/useCatalogData.ts | 8 +- frontend/lib/api.ts | 2 +- frontend/lib/catalog.ts | 27 +- package-lock.json | 27 + package.json | 5 + 137 files changed, 699 insertions(+), 16863 deletions(-) create mode 100644 backend/update_and_sync_meilisearch.py delete mode 100644 frontend old/.dockerignore delete mode 100644 frontend old/.gitignore delete mode 100644 frontend old/Dockerfile delete mode 100644 frontend old/README.md delete mode 100644 frontend old/app.json delete mode 100644 frontend old/components/AddToCartButton.tsx delete mode 100644 frontend old/components/Collections.tsx delete mode 100644 frontend old/components/CookieNotification.tsx delete mode 100644 frontend old/components/Footer.tsx delete mode 100644 frontend old/components/Header.tsx delete mode 100644 frontend old/components/Hero.tsx delete mode 100644 frontend old/components/NewArrivals.tsx delete mode 100644 frontend old/components/PopularCategories.tsx delete mode 100644 frontend old/components/TabSelector.tsx delete mode 100644 frontend old/components/admin/AdminLayout.tsx delete mode 100644 frontend old/components/admin/AdminSidebar.tsx delete mode 100644 frontend old/components/admin/ProductForm.tsx delete mode 100644 frontend old/components/admin/ProductImageUploader.tsx delete mode 100644 frontend old/components/admin/ProductVariantManager.tsx delete mode 100644 frontend old/components/admin/Sidebar.tsx delete mode 100644 frontend old/data/categories.ts delete mode 100644 frontend old/data/collections.ts delete mode 100644 frontend old/data/products.ts delete mode 100644 frontend old/next.config.js delete mode 100644 frontend old/package-lock.json delete mode 100644 frontend old/package.json delete mode 100644 frontend old/pages/_app.tsx delete mode 100644 frontend old/pages/account/addresses.tsx delete mode 100644 frontend old/pages/account/change-password.tsx delete mode 100644 frontend old/pages/account/edit.tsx delete mode 100644 frontend old/pages/account/index.tsx delete mode 100644 frontend old/pages/account/orders.tsx delete mode 100644 frontend old/pages/account/orders/[id].tsx delete mode 100644 frontend old/pages/admin/categories/[id].tsx delete mode 100644 frontend old/pages/admin/categories/create.tsx delete mode 100644 frontend old/pages/admin/categories/index.tsx delete mode 100644 frontend old/pages/admin/collections/index.tsx delete mode 100644 frontend old/pages/admin/customers/[id].tsx delete mode 100644 frontend old/pages/admin/customers/index.tsx delete mode 100644 frontend old/pages/admin/index.tsx delete mode 100644 frontend old/pages/admin/orders/[id].tsx delete mode 100644 frontend old/pages/admin/orders/index.tsx delete mode 100644 frontend old/pages/admin/products/[id].tsx delete mode 100644 frontend old/pages/admin/products/create.tsx delete mode 100644 frontend old/pages/admin/products/index.tsx delete mode 100644 frontend old/pages/all-products.tsx delete mode 100644 frontend old/pages/api/hello.js delete mode 100644 frontend old/pages/cart.tsx delete mode 100644 frontend old/pages/category/[slug].tsx delete mode 100644 frontend old/pages/category/index.tsx delete mode 100644 frontend old/pages/checkout.tsx delete mode 100644 frontend old/pages/collections/[slug].tsx delete mode 100644 frontend old/pages/collections/index.tsx delete mode 100644 frontend old/pages/forgot-password.tsx delete mode 100644 frontend old/pages/index.tsx delete mode 100644 frontend old/pages/login.tsx delete mode 100644 frontend old/pages/new-arrivals/index.tsx delete mode 100644 frontend old/pages/order-success.tsx delete mode 100644 frontend old/pages/order-tracking.tsx delete mode 100644 frontend old/pages/product/[slug].tsx delete mode 100644 frontend old/pages/register.tsx delete mode 100644 frontend old/pages/reset-password/[token].tsx delete mode 100644 frontend old/postcss.config.js delete mode 100644 frontend old/public/category/dress.jpg delete mode 100644 frontend old/public/category/hat.jpg delete mode 100644 frontend old/public/category/jacket.jpg delete mode 100644 frontend old/public/category/pants.jpg delete mode 100644 frontend old/public/category/scarf.jpg delete mode 100644 frontend old/public/category/shoes.jpg delete mode 100644 frontend old/public/category/silk.jpg delete mode 100644 frontend old/public/category/sweaters.jpg delete mode 100644 frontend old/public/favicon.ico delete mode 100644 frontend old/public/hero_photos/hero1.png delete mode 100644 frontend old/public/hero_photos/photo_main_main_1.png delete mode 100644 frontend old/public/hero_photos/photo_main_main_3.png delete mode 100644 frontend old/public/logo.png delete mode 100644 frontend old/public/logotip.png delete mode 100644 frontend old/public/photos/autumn_winter.jpg delete mode 100644 frontend old/public/photos/based_outfit.jpg delete mode 100644 frontend old/public/photos/business_outfit.jpg delete mode 100644 frontend old/public/photos/head_photo.png delete mode 100644 frontend old/public/photos/night_dress.jpg delete mode 100644 frontend old/public/photos/photo1.jpg delete mode 100644 frontend old/public/photos/photo2.jpg delete mode 100644 frontend old/public/vercel.svg delete mode 100644 frontend old/public/wear/bag1.jpg delete mode 100644 frontend old/public/wear/bag2.jpg delete mode 100644 frontend old/public/wear/classic_bruk1.jpg delete mode 100644 frontend old/public/wear/classic_bruk2.jpg delete mode 100644 frontend old/public/wear/coat1.jpg delete mode 100644 frontend old/public/wear/coat2.jpg delete mode 100644 frontend old/public/wear/hat1.jpg delete mode 100644 frontend old/public/wear/jumpsuit_1.jpg delete mode 100644 frontend old/public/wear/jumpsuit_2.jpg delete mode 100644 frontend old/public/wear/kozh_boots1.jpg delete mode 100644 frontend old/public/wear/kozh_boots2.jpg delete mode 100644 frontend old/public/wear/palto1.jpg delete mode 100644 frontend old/public/wear/palto2.jpg delete mode 100644 frontend old/public/wear/pidzak1.jpg delete mode 100644 frontend old/public/wear/pidzak2.jpg delete mode 100644 frontend old/public/wear/sherst_sweater1.jpg delete mode 100644 frontend old/public/wear/sherst_sweater2.jpg delete mode 100644 frontend old/public/wear/silk1.jpg delete mode 100644 frontend old/public/wear/silk2.jpg delete mode 100644 frontend old/public/wear/silk_scarf1.jpg delete mode 100644 frontend old/public/wear/silk_scarf2.jpg delete mode 100644 frontend old/public/wear/sorochka1.jpg delete mode 100644 frontend old/public/wear/sorochka2.jpg delete mode 100644 frontend old/services/analytics.ts delete mode 100644 frontend old/services/api.ts delete mode 100644 frontend old/services/auth.ts delete mode 100644 frontend old/services/cart.ts delete mode 100644 frontend old/services/catalog.ts delete mode 100644 frontend old/services/orders.ts delete mode 100644 frontend old/services/users.ts delete mode 100644 frontend old/styles/Home.module.css delete mode 100644 frontend old/styles/globals.css delete mode 100644 frontend old/tailwind.config.js delete mode 100644 frontend old/tsconfig.json create mode 100644 frontend/components/navigation/MobileNavBar.tsx create mode 100644 package-lock.json create mode 100644 package.json diff --git a/backend/app/routers/__pycache__/catalog_router.cpython-310.pyc b/backend/app/routers/__pycache__/catalog_router.cpython-310.pyc index 291f164fea19e761984c387badabde0ba83f98fb..2fa74e5d061ce6a2f77094963e966e8ad19f21aa 100644 GIT binary patch delta 3426 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 Mn+a diff --git a/backend/app/routers/catalog_router.py b/backend/app/routers/catalog_router.py index 0308933..8bab70e 100644 --- a/backend/app/routers/catalog_router.py +++ b/backend/app/routers/catalog_router.py @@ -443,22 +443,50 @@ async def get_products_endpoint( skip: int = 0, limit: int = 100, category_id: Optional[int] = None, + category_ids: Optional[str] = None, # Добавляем параметр category_ids в виде строки с разделителями-запятыми collection_id: Optional[int] = None, + collection_ids: Optional[str] = None, # Добавляем параметр collection_ids в виде строки с разделителями-запятыми search: Optional[str] = None, min_price: Optional[float] = None, max_price: Optional[float] = None, is_active: Optional[bool] = True, sort_by: Optional[str] = None, sort_order: Optional[str] = "asc", + size_ids: Optional[str] = None, # Параметр size_ids в виде строки с разделителями-запятыми + sort: Optional[str] = None, # Параметр sort для прямой передачи опции сортировки db: Session = Depends(get_db) ): # Формируем фильтры для Meilisearch filters = [] - if category_id is not None: + # Обработка фильтрации по категориям + if category_ids: + try: + # Преобразуем строку с ID категорий в список + category_ids_list = [int(cat_id.strip()) for cat_id in category_ids.split(',') if cat_id.strip()] + if category_ids_list: + # Формируем условие для фильтрации по категориям + category_filter = " OR ".join([f"category_id = {cat_id}" for cat_id in category_ids_list]) + filters.append(f"({category_filter})") + except ValueError as e: + logging.warning(f"Некорректный формат category_ids: {category_ids}. Ошибка: {str(e)}") + # Для обратной совместимости + elif category_id is not None: filters.append(f"category_id = {category_id}") - if collection_id is not None: + # Обработка фильтрации по коллекциям + if collection_ids: + try: + # Преобразуем строку с ID коллекций в список + collection_ids_list = [int(coll_id.strip()) for coll_id in collection_ids.split(',') if coll_id.strip()] + if collection_ids_list: + # Формируем условие для фильтрации по коллекциям + collection_filter = " OR ".join([f"collection_id = {coll_id}" for coll_id in collection_ids_list]) + filters.append(f"({collection_filter})") + except ValueError as e: + logging.warning(f"Некорректный формат collection_ids: {collection_ids}. Ошибка: {str(e)}") + # Для обратной совместимости + elif collection_id is not None: filters.append(f"collection_id = {collection_id}") if is_active is not None: @@ -470,10 +498,39 @@ async def get_products_endpoint( if max_price is not None: filters.append(f"price <= {max_price}") + # Обработка фильтрации по размерам + if size_ids: + try: + # Преобразуем строку с ID размеров в список + size_ids_list = [int(size_id.strip()) for size_id in size_ids.split(',') if size_id.strip()] + if size_ids_list: + # Формируем условие для фильтрации по размерам + size_filter = " OR ".join([f"size_ids = {size_id}" for size_id in size_ids_list]) + filters.append(f"({size_filter})") + except ValueError as e: + logging.warning(f"Некорректный формат size_ids: {size_ids}. Ошибка: {str(e)}") + # Если формат некорректный, игнорируем этот фильтр + # Формируем параметры сортировки - sort = None - if sort_by: - sort = [f"{sort_by}:{sort_order}"] + sort_param = None + + # Если передан прямой параметр sort, используем его + if sort: + # Обрабатываем популярные варианты сортировки + if sort == 'popular': + sort_param = None # Используем сортировку по умолчанию + elif sort == 'price_asc': + sort_param = ['price:asc'] + elif sort == 'price_desc': + sort_param = ['price:desc'] + elif sort == 'newest': + sort_param = ['created_at:desc'] + else: + # Если передан другой вариант, используем его напрямую + sort_param = [sort] + # Если передан sort_by, используем его + elif sort_by: + sort_param = [f"{sort_by}:{sort_order}"] # Используем Meilisearch для поиска продуктов from app.services import meilisearch_service @@ -485,7 +542,7 @@ async def get_products_endpoint( result = meilisearch_service.search_products( query=search or "", filters=filter_str, - sort=sort, + sort=sort_param, limit=limit, offset=skip ) diff --git a/backend/app/scripts/__pycache__/sync_meilisearch.cpython-310.pyc b/backend/app/scripts/__pycache__/sync_meilisearch.cpython-310.pyc index abb170fadd27b0e4c5188bff1624f96be8f1e817..44ccc1820fa0d30971ae5f418fec50bbf44382ba 100644 GIT binary patch delta 1422 zcmZuxO>7%Q6yCSH9`A1Lwc}>v24W}9Pm4qAxJhd$t{{PipB@^~6jdrRmbG3IS5EBA zx`}dIdL#YB<>XwDnZb`H^xn~>RR*py*F>( z`@Wgky_WkXXE&lzOTlO7LUQ%f@vm%0-4{pq6&!v9L{2?xuP9aZW^7Yy!zray5ZBcV zl*Ao1^?`md+SaOUSa}b4sb_tjXk+vifP#K_SV2ET-bSvi;_8u2)@BzK9Q$z$;&7_Q zO?@+r*`~}6%e5Hi*JPGfwN5G6C~%w#G92G+lJI1X`DWLOerIwzO|`8*YI2o4Y&Bp61>Lf3FBEpMg4 zFY_Z5m5>ux!;_h}(D#QB^iaQx4hgV<-B%5EpU-xFz(x~~7qi?(z3#cKT4Q}rxxb8q z;E9x(f@7j+{tRQnv_@CQsNn!XkX3Ip9wUTF-KXDe@U=>-EH@MF@|stx*L<(S-PIN* z_z8qQSIKinWo(BZ?|fo?3v7lO9TH2CX()&*krX^5ZbYWifhJM^^154Y@<#QN+w!-> zZ;>fj>_9XPvy_Y&I{t6omn_VkwDp)OU7w27oSGv@5GNT1ehB%UnGbi zbO$A`O1)m*J$j$-)wCU-$UjL_yr=2WmGQr(vP%TZeF+N#TYDrN$JxFF`Nr(sCn@1q zs&HETJurz9(ut{JK!Qp0yAte266j_H$zM$*r=LXLlB?v3pQxUseZF%y@e8nCKOqLk zm(I}KGQoL*MTD-o_`d5kZT)wlJI~_xE$?Y%D2N`c;7#}7KcEK@FU5+^+Zb(w3G?0A_uhQ>GjHZu^h#9E z`2Cto{64>2J@vNniEc<*K)mh|!}@}^ywx}&4f&GNU42f@!-(tH7Q7`j!ZxJMbpdF6UXtzhAGB=sZ>Bw-;oi)XXuOp-TJq=}>%x4WmH18=w& zU;+&m?qlE7kRmBi{Ieg-%OnO2w4Xty%B2ij)ftmDkS;3{k7CwaS(s-4u`V(-NH&s0 zNNR+%fvqOuIrCJ8&+-n6I?{;+PYWEx51ue|lMDLP)#kOUv_vMIHWRs2W@g>Gc5iF= zgz&K{g(&V4ZwPc?E0Tw;@Lu7!3}GDB!tJzsUO`fhRqW1Mq6Mu1GWbZ_41I-H+H+u0 zTGxtKeOsX)AN%TH3oiR2^=sCM0^4uR&Loa!c_w*2k+bG7=#Rh%E(B@|lYR+$sJab! zH$5a)d1B{FBT!}sx;9)kgVs&^UNsB}BY4iXQ^ApX$n{lhffC8SN^ z{n#34jL=0aw+%RKfBK&?tvi?wY!+8?Ay5aS_#kj&U~g%aVC*G3iSMIOPbprs?4;NI zLSE2h3{MBQ3PcQcmm{i_AHbN43Ps8}y_6rso1vPCb*&U(+ij&TnU<|NkK@UBF3$f)t~umR z(Q9&Zn5PThL+^kU4-Vf{HXRtJ+$6~~$%qi!J9;r;W}UlDXK2e-j@#};I%An)WBn8Y zvus void; -} - -export default function AddToCartButton({ variantId, quantity = 1, className = '', onAddToCart }: AddToCartButtonProps) { - const router = useRouter(); - const [loading, setLoading] = useState(false); - const [success, setSuccess] = useState(false); - const [error, setError] = useState(null); - - const handleAddToCart = async () => { - // Проверяем, авторизован ли пользователь - if (!authService.isAuthenticated()) { - // Сохраняем текущий URL для редиректа после авторизации - const currentPath = router.asPath; - router.push(`/login?redirect=${encodeURIComponent(currentPath)}`); - return; - } - - setLoading(true); - setError(null); - - try { - await cartService.addToCart({ - variant_id: variantId, - quantity: quantity - }); - - setSuccess(true); - - // Вызываем колбэк, если он передан - if (onAddToCart) { - onAddToCart(); - } - - // Сбрасываем состояние успеха через 2 секунды - setTimeout(() => { - setSuccess(false); - }, 2000); - } catch (err) { - console.error('Ошибка при добавлении в корзину:', err); - setError('Не удалось добавить товар в корзину'); - - // Сбрасываем состояние ошибки через 3 секунды - setTimeout(() => { - setError(null); - }, 3000); - } finally { - setLoading(false); - } - }; - - return ( -
- - - {error && ( -
- - {error} -
- )} -
- ); -} \ No newline at end of file diff --git a/frontend old/components/Collections.tsx b/frontend old/components/Collections.tsx deleted file mode 100644 index 5610390..0000000 --- a/frontend old/components/Collections.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import Image from "next/image" -import Link from "next/link" -import { motion } from "framer-motion" -import { Collection } from "../data/collections" - -interface CollectionsProps { - collections: Collection[] -} - -export default function Collections({ collections }: CollectionsProps) { - return ( -
-

Коллекции

- -
- {collections.map((collection, index) => ( - - -
- {collection.name} -
-
-

{collection.name}

-

{collection.description}

- - Смотреть коллекцию - -
-
-
- - ))} -
-
- ) -} \ No newline at end of file diff --git a/frontend old/components/CookieNotification.tsx b/frontend old/components/CookieNotification.tsx deleted file mode 100644 index f0471ae..0000000 --- a/frontend old/components/CookieNotification.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client" - -import { useState, useEffect } from "react" - -export default function CookieNotification() { - const [isVisible, setIsVisible] = useState(false); - - // Проверяем, было ли уже показано уведомление - useEffect(() => { - const cookieAccepted = localStorage.getItem('cookieAccepted'); - if (!cookieAccepted) { - // Показываем уведомление с небольшой задержкой - const timer = setTimeout(() => { - setIsVisible(true); - }, 2000); - - return () => clearTimeout(timer); - } - }, []); - - const acceptCookies = () => { - localStorage.setItem('cookieAccepted', 'true'); - setIsVisible(false); - }; - - if (!isVisible) return null; - - return ( -
-

Уведомление о Cookies

-

- Наш сайт использует файлы cookie. Продолжая пользоваться сайтом, вы соглашаетесь на использование наших файлов - cookie. -

- -
- ); -} \ No newline at end of file diff --git a/frontend old/components/Footer.tsx b/frontend old/components/Footer.tsx deleted file mode 100644 index 77614f1..0000000 --- a/frontend old/components/Footer.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import Link from "next/link"; -import { Facebook, Instagram, Twitter, Youtube, ChevronDown, ChevronUp } from "lucide-react"; -import { useState } from "react"; - -export default function Footer() { - // Состояния для отображения/скрытия разделов на мобильных устройствах - const [helpOpen, setHelpOpen] = useState(false); - const [shopOpen, setShopOpen] = useState(false); - const [aboutOpen, setAboutOpen] = useState(false); - - return ( -
- ); -} \ No newline at end of file diff --git a/frontend old/components/Header.tsx b/frontend old/components/Header.tsx deleted file mode 100644 index c70349e..0000000 --- a/frontend old/components/Header.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import Link from "next/link"; -import { Search, Heart, User, ShoppingCart, ChevronLeft, LogOut, Menu, X } from "lucide-react"; -import { useState, useEffect } from "react"; -import { motion, AnimatePresence } from "framer-motion"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import authService from "../services/auth"; -import cartService from "../services/cart"; - -export default function Header() { - const router = useRouter(); - // Состояние для отслеживания прокрутки страницы - const [scrolled, setScrolled] = useState(false); - // Состояние для отслеживания аутентификации пользователя - const [isAuthenticated, setIsAuthenticated] = useState(false); - // Состояние для отображения выпадающего меню пользователя - const [showUserMenu, setShowUserMenu] = useState(false); - // Состояние для отображения мобильного меню - const [mobileMenuOpen, setMobileMenuOpen] = useState(false); - // Состояние для хранения количества товаров в корзине - const [cartItemsCount, setCartItemsCount] = useState(0); - - // Эффект для проверки аутентификации при загрузке компонента - useEffect(() => { - setIsAuthenticated(authService.isAuthenticated()); - }, []); - - // Эффект для загрузки количества товаров в корзине - useEffect(() => { - const fetchCartItemsCount = async () => { - if (authService.isAuthenticated()) { - try { - const cart = await cartService.getCart(); - setCartItemsCount(cart.items_count); - } catch (error) { - console.error('Ошибка при загрузке корзины:', error); - setCartItemsCount(0); - } - } else { - setCartItemsCount(0); - } - }; - - fetchCartItemsCount(); - - // Обновляем количество товаров в корзине при изменении маршрута - const handleRouteChange = () => { - fetchCartItemsCount(); - }; - - router.events.on('routeChangeComplete', handleRouteChange); - return () => { - router.events.off('routeChangeComplete', handleRouteChange); - }; - }, [isAuthenticated, router.events]); - - // Эффект для отслеживания прокрутки - useEffect(() => { - const handleScroll = () => { - const isScrolled = window.scrollY > 50; - if (isScrolled !== scrolled) { - setScrolled(isScrolled); - } - }; - - window.addEventListener('scroll', handleScroll); - return () => { - window.removeEventListener('scroll', handleScroll); - }; - }, [scrolled]); - - // Функция для выхода из системы - const handleLogout = () => { - authService.logout(); - setIsAuthenticated(false); - setShowUserMenu(false); - setCartItemsCount(0); - router.push('/'); - }; - - // Функция для возврата на предыдущую страницу - const goBack = () => { - router.back(); - }; - - // Проверяем, находимся ли мы на главной странице - const isHomePage = router.pathname === "/"; - // Проверяем, находимся ли мы на странице категорий или коллекций - const isDetailPage = router.pathname.includes("[slug]"); - - // Функция для переключения отображения меню пользователя - const toggleUserMenu = () => { - setShowUserMenu(!showUserMenu); - }; - - // Функция для переключения мобильного меню - const toggleMobileMenu = () => { - setMobileMenuOpen(!mobileMenuOpen); - }; - - // Закрыть мобильное меню при переходе на другую страницу - useEffect(() => { - setMobileMenuOpen(false); - }, [router.pathname]); - - // Закрыть меню пользователя при клике вне его - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - const target = event.target as HTMLElement; - if (showUserMenu && !target.closest('.user-menu-container')) { - setShowUserMenu(false); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [showUserMenu]); - - return ( -
- - - {/* Мобильное меню */} - - {mobileMenuOpen && ( - -
-
- - Каталог - - - Все товары - - - Коллекции - - - Новинки - - - Отследить заказ - - - - Поиск - -
-
-
- )} -
-
- ); -} \ No newline at end of file diff --git a/frontend old/components/Hero.tsx b/frontend old/components/Hero.tsx deleted file mode 100644 index cb9099c..0000000 --- a/frontend old/components/Hero.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client" - -import { useState, useEffect } from "react"; -import Image from "next/image"; -import { motion } from "framer-motion"; - -// Типы для свойств компонента -interface HeroProps { - images?: string[]; -} - -export default function Hero({ images = [] }: HeroProps) { - return ( -
- {/* Логотип по центру */} -
-
- Brand Logo -
-
- - {/* Контент */} -
-
- - Элегантность в каждой детали - - - Откройте для себя новую коллекцию, созданную с любовью к качеству и стилю - - - - Смотреть коллекцию - - -
-
-
- ); -} diff --git a/frontend old/components/NewArrivals.tsx b/frontend old/components/NewArrivals.tsx deleted file mode 100644 index aab4c50..0000000 --- a/frontend old/components/NewArrivals.tsx +++ /dev/null @@ -1,256 +0,0 @@ -"use client" - -import { useState, useRef, useEffect } from "react" -import Image from "next/image" -import { Heart, ChevronLeft, ChevronRight } from "lucide-react" -import Link from "next/link" -import { Product, formatPrice } from "../data/products" -import { motion } from "framer-motion" - -interface NewArrivalsProps { - products: Product[] -} - -export default function NewArrivals({ products }: NewArrivalsProps) { - const [hoveredProduct, setHoveredProduct] = useState(null) - const [favorites, setFavorites] = useState([]) - const sliderRef = useRef(null) - const [showLeftArrow, setShowLeftArrow] = useState(false) - const [showRightArrow, setShowRightArrow] = useState(true) - const [isDragging, setIsDragging] = useState(false) - const [startX, setStartX] = useState(0) - const [scrollLeftValue, setScrollLeftValue] = useState(0) - const [currentSlide, setCurrentSlide] = useState(0) - const [slidesPerView, setSlidesPerView] = useState(4) - - // Функция для добавления/удаления товара из избранного - const toggleFavorite = (id: number, e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - setFavorites((prev) => (prev.includes(id) ? prev.filter((itemId) => itemId !== id) : [...prev, id])) - } - - // Определение количества слайдов на экране в зависимости от размера экрана - useEffect(() => { - const handleResize = () => { - const width = window.innerWidth; - if (width < 640) { - setSlidesPerView(1); - } else if (width < 768) { - setSlidesPerView(2); - } else if (width < 1024) { - setSlidesPerView(3); - } else { - setSlidesPerView(4); - } - }; - - handleResize(); - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); - - // Обновление состояния стрелок при скролле - const updateArrowVisibility = () => { - if (sliderRef.current) { - const { scrollLeft, scrollWidth, clientWidth } = sliderRef.current - setShowLeftArrow(scrollLeft > 0) - setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 5) // 5px buffer - - // Обновление текущего слайда - if (clientWidth > 0) { - const slideWidth = scrollWidth / products.length; - const newCurrentSlide = Math.round(scrollLeft / slideWidth); - setCurrentSlide(newCurrentSlide); - } - } - } - - // Инициализация и обработка изменений размера - useEffect(() => { - updateArrowVisibility() - window.addEventListener("resize", updateArrowVisibility) - return () => window.removeEventListener("resize", updateArrowVisibility) - }, []) - - // Обработчики скролла - const handleScroll = () => { - updateArrowVisibility() - } - - const scrollLeft = () => { - if (sliderRef.current) { - const itemWidth = sliderRef.current.scrollWidth / products.length; - const newScrollLeft = Math.max(0, sliderRef.current.scrollLeft - (itemWidth * slidesPerView)); - sliderRef.current.scrollTo({ left: newScrollLeft, behavior: "smooth" }); - } - } - - const scrollRight = () => { - if (sliderRef.current) { - const itemWidth = sliderRef.current.scrollWidth / products.length; - const newScrollLeft = Math.min( - sliderRef.current.scrollWidth - sliderRef.current.clientWidth, - sliderRef.current.scrollLeft + (itemWidth * slidesPerView) - ); - sliderRef.current.scrollTo({ left: newScrollLeft, behavior: "smooth" }); - } - } - - // Обработчики перетаскивания (для мобильных) - const handleMouseDown = (e: React.MouseEvent) => { - setIsDragging(true) - setStartX(e.pageX - (sliderRef.current?.offsetLeft || 0)) - setScrollLeftValue(sliderRef.current?.scrollLeft || 0) - } - - const handleMouseUp = () => { - setIsDragging(false) - } - - const handleMouseMove = (e: React.MouseEvent) => { - if (!isDragging) return - e.preventDefault() - if (sliderRef.current) { - const x = e.pageX - (sliderRef.current.offsetLeft || 0) - const walk = (x - startX) * 2 // Скорость скролла - sliderRef.current.scrollLeft = scrollLeftValue - walk - } - } - - const handleMouseLeave = () => { - setIsDragging(false) - } - - // Обработчики тач-событий - const handleTouchStart = (e: React.TouchEvent) => { - if (sliderRef.current) { - setStartX(e.touches[0].pageX - (sliderRef.current.offsetLeft || 0)) - setScrollLeftValue(sliderRef.current.scrollLeft) - } - } - - const handleTouchMove = (e: React.TouchEvent) => { - if (sliderRef.current) { - const x = e.touches[0].pageX - (sliderRef.current.offsetLeft || 0) - const walk = (x - startX) * 2 - sliderRef.current.scrollLeft = scrollLeftValue - walk - } - } - - // Переход к определенному слайду - const goToSlide = (index: number) => { - if (sliderRef.current) { - const itemWidth = sliderRef.current.scrollWidth / products.length; - sliderRef.current.scrollTo({ left: itemWidth * index, behavior: "smooth" }); - } - }; - - return ( -
-

Новинки

- - {/* Контейнер слайдера с кнопками навигации */} -
- {/* Кнопка влево */} - {showLeftArrow && ( - - )} - - {/* Слайдер продуктов */} -
- {products.map((product) => ( - - setHoveredProduct(product.id)} - onMouseLeave={() => setHoveredProduct(null)} - > -
-
- 1 ? product.images[1] : product.images[0] - } - alt={product.name} - fill - sizes="(max-width: 640px) 280px, (max-width: 768px) 320px, (max-width: 1024px) 300px, 280px" - className="object-cover transition-all duration-500 hover:scale-105" - /> - {product.isNew && ( - Новинка - )} - -
-
-
-

{product.name}

-

{formatPrice(product.price)} ₽

-
- -
- ))} -
- - {/* Кнопка вправо */} - {showRightArrow && ( - - )} -
- - {/* Индикаторы слайдов (точки) */} -
- {Array.from({ length: Math.ceil(products.length / slidesPerView) }).map((_, index) => ( -
-
- ) -} \ No newline at end of file diff --git a/frontend old/components/PopularCategories.tsx b/frontend old/components/PopularCategories.tsx deleted file mode 100644 index 4503702..0000000 --- a/frontend old/components/PopularCategories.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import Image from "next/image" -import Link from "next/link" -import { motion } from "framer-motion" -import { Category } from "../data/categories" - -// Типы для свойств компонента -interface PopularCategoriesProps { - categories: Category[] -} - -export default function PopularCategories({ categories }: PopularCategoriesProps) { - return ( -
-

Популярные категории

- -
- {categories.map((category, index) => ( - - -
- {category.name} -
-
-

{category.name}

-
-
-
- - ))} -
-
- ) -} \ No newline at end of file diff --git a/frontend old/components/TabSelector.tsx b/frontend old/components/TabSelector.tsx deleted file mode 100644 index 75d0a0a..0000000 --- a/frontend old/components/TabSelector.tsx +++ /dev/null @@ -1,102 +0,0 @@ -"use client" - -import { useState, useRef, useEffect } from "react" - -const tabs = ["Новинки", "Коллекции", "Популярное"] - -interface TabSelectorProps { - onTabChange: (index: number) => void; -} - -export default function TabSelector({ onTabChange }: TabSelectorProps) { - const [hoveredIndex, setHoveredIndex] = useState(null) - const [activeIndex, setActiveIndex] = useState(0) - const [hoverStyle, setHoverStyle] = useState({}) - const [activeStyle, setActiveStyle] = useState({ left: "0px", width: "0px" }) - const tabRefs = useRef<(HTMLDivElement | null)[]>([]) - - useEffect(() => { - if (hoveredIndex !== null) { - const hoveredElement = tabRefs.current[hoveredIndex] - if (hoveredElement) { - const { offsetLeft, offsetWidth } = hoveredElement - setHoverStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px`, - }) - } - } - }, [hoveredIndex]) - - useEffect(() => { - const activeElement = tabRefs.current[activeIndex] - if (activeElement) { - const { offsetLeft, offsetWidth } = activeElement - setActiveStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px`, - }) - } - - // Вызываем функцию обратного вызова при изменении активного таба - onTabChange(activeIndex); - }, [activeIndex, onTabChange]) - - useEffect(() => { - requestAnimationFrame(() => { - const firstElement = tabRefs.current[0] - if (firstElement) { - const { offsetLeft, offsetWidth } = firstElement - setActiveStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px`, - }) - } - }) - }, []) - - return ( -
-
-
-
- {/* Hover Highlight */} -
- - {/* Active Indicator */} -
- - {/* Tabs */} -
- {tabs.map((tab, index) => ( -
(tabRefs.current[index] = el)} - className={`px-3 py-2 cursor-pointer transition-colors duration-300 h-[30px] ${ - index === activeIndex ? "text-[#2B5F47] font-medium" : "text-[#2B5F47]/70" - }`} - onMouseEnter={() => setHoveredIndex(index)} - onMouseLeave={() => setHoveredIndex(null)} - onClick={() => setActiveIndex(index)} - > -
- {tab} -
-
- ))} -
-
-
-
-
- ) -} \ No newline at end of file diff --git a/frontend old/components/admin/AdminLayout.tsx b/frontend old/components/admin/AdminLayout.tsx deleted file mode 100644 index ef36443..0000000 --- a/frontend old/components/admin/AdminLayout.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { useState, useEffect } from 'react'; -import Head from 'next/head'; -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { - LayoutDashboard, - Package, - Tag, - ShoppingBag, - Users, - FileText, - Settings, - LogOut, - Menu, - X, - Home, - Grid -} from 'lucide-react'; -import Image from 'next/image'; -import authService from '../../services/auth'; -import { userService } from '../../services/users'; - -// Компонент элемента бокового меню -interface SidebarItemProps { - icon: React.ReactNode; - text: string; - href: string; - active: boolean; -} - -const SidebarItem = ({ icon, text, href, active }: SidebarItemProps) => { - return ( - - {icon} - {text} - - ); -}; - -interface AdminLayoutProps { - children: React.ReactNode; - title: string; -} - -export default function AdminLayout({ children, title }: AdminLayoutProps) { - const router = useRouter(); - const [sidebarOpen, setSidebarOpen] = useState(false); - const [loading, setLoading] = useState(true); - - // Проверка прав администратора при загрузке компонента - useEffect(() => { - const checkAdminAccess = async () => { - try { - if (!authService.isAuthenticated()) { - // Если пользователь не авторизован, перенаправляем на страницу входа - router.push('/login'); - return; - } - - const user = await userService.getCurrentUser(); - - if (!user) { - // Если не удалось получить данные пользователя, перенаправляем на страницу входа - router.push('/login'); - return; - } - - if (!user.is_admin) { - // Если пользователь не администратор, перенаправляем на главную страницу - router.push('/'); - return; - } - - setLoading(false); - } catch (error) { - console.error('Ошибка при проверке прав администратора:', error); - router.push('/login'); - } - }; - - checkAdminAccess(); - }, [router]); - - // Определяем активный пункт меню на основе текущего пути - const currentPath = router.pathname; - - const menuItems = [ - { - icon: , - text: 'Панель управления', - href: '/admin', - active: currentPath === '/admin' - }, - { - icon: , - text: 'Товары', - href: '/admin/products', - active: currentPath.startsWith('/admin/products') - }, - { - icon: , - text: 'Категории', - href: '/admin/categories', - active: currentPath.startsWith('/admin/categories') - }, - { - icon: , - text: 'Заказы', - href: '/admin/orders', - active: currentPath.startsWith('/admin/orders') - }, - { - icon: , - text: 'Пользователи', - href: '/admin/users', - active: currentPath.startsWith('/admin/users') - }, - { - icon: , - text: 'Настройки', - href: '/admin/settings', - active: currentPath.startsWith('/admin/settings') - }, - { - icon: , - text: 'Коллекции', - href: '/admin/collections', - active: currentPath.startsWith('/admin/collections') - } - ]; - - if (loading) { - return ( -
-
-
- ); - } - - return ( -
- - {title} | Админ-панель - - - {/* Мобильная навигация */} -
-
- -
-
- Brand Logo -
- Админ -
-
-
- - {/* Боковое меню (мобильное) */} - {sidebarOpen && ( -
-
setSidebarOpen(false)}>
-
-
-
-
- Brand Logo -
- Админ -
- -
-
- -
-
- - - На главную - - -
-
-
- )} - - {/* Боковое меню (десктоп) */} -
-
-
-
- Brand Logo -
- Админ -
-
-
- -
-
- - - На главную - - -
-
- - {/* Основной контент */} -
-
-

{title}

-
-
- {children} -
-
- © {new Date().getFullYear()} Dressed for Success. Все права защищены. -
-
-
- ); -} \ No newline at end of file diff --git a/frontend old/components/admin/AdminSidebar.tsx b/frontend old/components/admin/AdminSidebar.tsx deleted file mode 100644 index c5bac14..0000000 --- a/frontend old/components/admin/AdminSidebar.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import Link from 'next/link'; -import { useRouter } from 'next/router'; -import { - LayoutDashboard, - ShoppingBag, - Tag, - FileText, - Settings, - Users, - ShoppingCart, - BarChart, - MessageSquare -} from 'lucide-react'; - -// Определение элементов меню -const menuItems = [ - { - title: 'Дашборд', - icon: , - href: '/admin/dashboard', - active: (path) => path === '/admin/dashboard' - }, - { - title: 'Заказы', - icon: , - href: '/admin/orders', - active: (path) => path.startsWith('/admin/orders') - }, - { - title: 'Клиенты', - icon: , - href: '/admin/customers', - active: (path) => path.startsWith('/admin/customers') - }, - { - title: 'Категории', - icon: , - href: '/admin/categories', - active: (path) => path.startsWith('/admin/categories') - }, - { - title: 'Товары', - icon: , - href: '/admin/products', - active: (path) => path.startsWith('/admin/products') - }, - { - title: 'Страницы', - icon: , - href: '/admin/pages', - active: (path) => path.startsWith('/admin/pages') - }, - { - title: 'Отзывы', - icon: , - href: '/admin/reviews', - active: (path) => path.startsWith('/admin/reviews') - }, - { - title: 'Аналитика', - icon: , - href: '/admin/analytics', - active: (path) => path.startsWith('/admin/analytics') - }, - { - title: 'Настройки', - icon: , - href: '/admin/settings', - active: (path) => path.startsWith('/admin/settings') - } -]; - -export default function AdminSidebar() { - const router = useRouter(); - const currentPath = router.pathname; - - return ( -
-
- - DressedForSuccess - -
- -
- ); -} \ No newline at end of file diff --git a/frontend old/components/admin/ProductForm.tsx b/frontend old/components/admin/ProductForm.tsx deleted file mode 100644 index ded27f9..0000000 --- a/frontend old/components/admin/ProductForm.tsx +++ /dev/null @@ -1,391 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; -import { Save, X } from 'lucide-react'; -import { Product, ProductVariant, Category, Collection, ProductImage } from '../../services/catalog'; -import { productService, categoryService, collectionService } from '../../services/catalog'; -import ProductImageUploader, { ProductImageWithFile } from './ProductImageUploader'; -import ProductVariantManager from './ProductVariantManager'; - -interface ProductFormProps { - product?: Product; - onSuccess?: () => void; -} - -const ProductForm: React.FC = ({ product, onSuccess }) => { - const router = useRouter(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [images, setImages] = useState([]); - const [variants, setVariants] = useState([]); - const [categories, setCategories] = useState([]); - const [collections, setCollections] = useState([]); - const [formData, setFormData] = useState({ - name: '', - slug: '', - description: '', - category_id: '', - collection_id: '', - is_active: true - }); - const [autoGenerateSlug, setAutoGenerateSlug] = useState(true); - - // Загрузка категорий и коллекций при монтировании компонента - useEffect(() => { - const fetchData = async () => { - try { - const [categoriesData, collectionsData] = await Promise.all([ - categoryService.getCategories(), - collectionService.getCollections() - ]); - setCategories(categoriesData); - setCollections(collectionsData); - } catch (err) { - console.error('Ошибка при загрузке данных:', err); - setError('Не удалось загрузить данные категорий и коллекций.'); - } - }; - - fetchData(); - - // Если передан продукт, заполняем форму его данными - if (product) { - setFormData({ - name: product.name || '', - slug: product.slug || '', - description: product.description || '', - category_id: product.category_id ? String(product.category_id) : '', - collection_id: product.collection_id ? String(product.collection_id) : '', - is_active: typeof product.is_active === 'boolean' ? product.is_active : true - }); - setAutoGenerateSlug(false); - - // Загружаем изображения - if (product.images && Array.isArray(product.images)) { - const productImages: ProductImageWithFile[] = product.images.map(img => ({ - id: img.id, - url: img.url, - is_primary: img.is_primary, - product_id: img.product_id - })); - setImages(productImages); - } - - // Загружаем варианты - if (product.variants && Array.isArray(product.variants)) { - setVariants(product.variants); - } - } - }, [product]); - - // Автоматическая генерация slug при изменении названия товара - useEffect(() => { - if (autoGenerateSlug && formData.name) { - setFormData(prev => ({ - ...prev, - slug: generateSlug(formData.name) - })); - } - }, [formData.name, autoGenerateSlug]); - - const handleChange = (e) => { - const { name, value, type, checked } = e.target; - setFormData({ - ...formData, - [name]: type === 'checkbox' ? checked : value - }); - - // Если пользователь изменяет slug вручную, отключаем автогенерацию - if (name === 'slug') { - setAutoGenerateSlug(false); - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!formData.name || !formData.category_id || variants.length === 0) { - setError('Пожалуйста, заполните все обязательные поля и добавьте хотя бы один вариант товара.'); - return; - } - - try { - setLoading(true); - setError(''); - - // Используем введенный slug или генерируем новый, если поле пустое - const slug = formData.slug || generateSlug(formData.name); - - // Подготавливаем данные продукта - const productData = { - name: formData.name, - slug, - description: formData.description, - category_id: parseInt(formData.category_id, 10), - collection_id: formData.collection_id ? parseInt(formData.collection_id, 10) : null, - is_active: formData.is_active - }; - - console.log('Отправляемые данные продукта:', productData); - - let productId: number; - - if (product) { - // Обновляем существующий продукт - console.log(`Обновление продукта с ID: ${product.id}`); - const updatedProduct = await productService.updateProduct(product.id, productData); - console.log('Обновленный продукт:', updatedProduct); - productId = updatedProduct.id; - } else { - // Создаем новый продукт - console.log('Создание нового продукта'); - const newProduct = await productService.createProduct(productData); - console.log('Созданный продукт:', newProduct); - productId = newProduct.id; - } - - // Загружаем изображения - console.log(`Загрузка ${images.length} изображений для продукта ${productId}`); - - // Используем Promise.all для параллельной загрузки изображений - const imagePromises = images.map(async (image) => { - try { - if (image.file) { - // Загружаем новое изображение - console.log(`Загрузка нового изображения: ${image.file.name}`); - const uploadedImage = await productService.uploadProductImage(productId, image.file, image.is_primary); - console.log('Загруженное изображение:', uploadedImage); - return uploadedImage; - } else if (image.id && product) { - // Обновляем существующее изображение - console.log(`Обновление существующего изображения с ID: ${image.id}`); - const updatedImage = await productService.updateProductImage(productId, image.id, { is_primary: image.is_primary }); - console.log('Обновленное изображение:', updatedImage); - return updatedImage; - } - return null; - } catch (imgError) { - console.error('Ошибка при обработке изображения:', imgError); - if (imgError.response) { - console.error('Данные ответа:', imgError.response.data); - console.error('Статус ответа:', imgError.response.status); - } - return null; - } - }); - - const uploadedImages = await Promise.all(imagePromises); - console.log('Все изображения обработаны:', uploadedImages.filter(Boolean)); - - // Обрабатываем варианты товара - if (product) { - // Для существующего продукта варианты уже обработаны через API в компоненте ProductVariantManager - console.log('Варианты для существующего продукта уже обработаны'); - } else { - // Для нового продукта создаем варианты - console.log(`Создание ${variants.length} вариантов для нового продукта ${productId}`); - - const variantPromises = variants.map(async (variant) => { - try { - console.log('Отправка данных варианта:', variant); - // Явно указываем все поля, чтобы избежать лишних данных - const variantData = { - name: variant.name, - sku: variant.sku, - price: Number(variant.price), // Убедимся, что это число - discount_price: variant.discount_price ? Number(variant.discount_price) : null, - stock: Number(variant.stock), - is_active: Boolean(variant.is_active || true) - }; - - const newVariant = await productService.addProductVariant(productId, variantData); - console.log('Созданный вариант:', newVariant); - return newVariant; - } catch (varError) { - console.error('Ошибка при создании варианта:', varError); - if (varError.response) { - console.error('Ответ сервера:', varError.response.data); - console.error('Статус ответа:', varError.response.status); - } - return null; - } - }); - - const createdVariants = await Promise.all(variantPromises); - console.log('Все варианты обработаны:', createdVariants.filter(Boolean)); - } - - // Вызываем колбэк успешного завершения или перенаправляем на страницу списка товаров - if (onSuccess) { - onSuccess(); - } else { - router.push('/admin/products'); - } - } catch (err) { - console.error('Ошибка при сохранении товара:', err); - if (err.response) { - console.error('Ответ сервера:', err.response.data); - console.error('Статус ответа:', err.response.status); - } - setError('Не удалось сохранить товар. Пожалуйста, проверьте введенные данные и попробуйте снова.'); - } finally { - setLoading(false); - } - }; - - const generateSlug = (name) => { - return name - .toLowerCase() - .replace(/[^\w\s-]/g, '') - .replace(/[\s_-]+/g, '-') - .replace(/^-+|-+$/g, ''); - }; - - return ( -
-
- {error && ( -
- Ошибка! - {error} -
- )} - -
-
- - -
- -
- -
- -
-
- -
-
- -
- - -
- -
- - -
- -
- -
-
- -
- -