Bladeren bron

add product

Gogs 3 weken geleden
bovenliggende
commit
ac5fe1adfe
5 gewijzigde bestanden met toevoegingen van 863 en 2156 verwijderingen
  1. 3 140
      README.md
  2. 1 2
      app/index.styl
  3. 434 1045
      app/pages/Admin/Products/index.coffee
  4. 159 475
      app/pages/Admin/Products/index.pug
  5. 266 494
      app/pages/Admin/Products/index.styl

+ 3 - 140
README.md

@@ -816,145 +816,8 @@ https://cdn1.ozone.ru/s3/multimedia-1-p/7663352533.jpg";;;ЭкоКрас;4673764
   в стил файлах не используй @import '../../index.styl', только стили текущего элемента,
   общие переменные определяй только в app/index.styl
   
-  Исправь файл app/pages/Admin/Products/index.coffee, идёт ошибка:
+  проанализируй промт, оптимизируй его, укажи что vuejs runtime версии, опиши интервейсы взаимодействия между страницами и компонентами. 
+  дай полную версию нового промта.
   
-  🔧 Обработка товара 38/227: Краска фасадная для наружных работ по бетону ЭкоКр...
-debug.coffee:8 🔄 Преобразование данных товара 38: 4673764201776
-debug.coffee:8 🔍 Поиск категории: Краска
-debug.coffee:8 ✅ Использована существующая категория: Краска
-debug.coffee:8 ✅ Данные товара полностью преобразованы: 4673764201776
-debug.coffee:8 ✅ Данные товара преобразованы: 4673764201776
-debug.coffee:8 💾 Попытка сохранения товара: 4673764201776
-debug.coffee:8 📡 Статус ответа XHR: 200
-debug.coffee:8 ✅ Blob получен:
-debug.coffee:8 🔁 Чтение blob как ArrayBuffer...
-debug.coffee:8 📄 Документ получен из локального кэша:
-debug.coffee:8 🔄 Обновление существующего товара: 4673764201776
-debug.coffee:8 📖 Начало чтения blob как ArrayBuffer
-debug.coffee:8 ✅ ArrayBuffer успешно прочитан
-debug.coffee:8 📊 ArrayBuffer:
-debug.coffee:8 🔍 Получение документа product:4673764201752 для _rev
-debug.coffee:8 📄 Документ получен из локального кэша:
-debug.coffee:8 ✅ Документ получен:
-debug.coffee:8 💾 Сохранение attachment...
-debug.coffee:8 ❌ Ошибка при работе с документом:
-debug.coffee:8 ❌ Другая ошибка при получении документа:
-debug.coffee:8 unhandledrejection
-index.coffee:474 Uncaught (in promise) n {stack: 'badarg\n    at Object.<anonymous> (https://unpkg.co…tps://unpkg.com/pouchdb/dist/pouchdb.min.js:7:271', status: 500, name: 'badarg', message: 'Some query argument is invalid', error: true, …}
-eval @ index.coffee:474
-Promise.catch
-n.status.e.onload @ index.coffee:443
-FileReader
-n.onload @ index.coffee:484
-XMLHttpRequest.send
-eval @ index.coffee:504
-downloadAndStoreImage @ index.coffee:393
-processProductImages @ index.coffee:667
-eval @ index.coffee:604
-Promise.then
-a @ index.coffee:601
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-Promise.catch
-a @ index.coffee:611
-eval @ index.coffee:618
-debug.coffee:8 🔄 Синхронизация:
-debug.coffee:8 💾 Документ обновлен в удаленной БД:
-debug.coffee:8 ✅ Товар сохранен, получение обновленной версии: 4673764201776
-debug.coffee:8 📄 Документ получен из локального кэша:
-debug.coffee:8 ✅ Документ получен с актуальным _rev: 21-a62c37e...
-debug.coffee:8 ✅ Товар сохранен в БД: 4673764201776
-debug.coffee:8 🖼️ Начало обработки изображений для товара: 4673764201776
-debug.coffee:8 📸 Загрузка основного изображения: https://cdn1.ozone.ru/s3/multimedia-1-v/7663370431.jpg
-debug.coffee:8 🔄 Начало загрузки изображения: https://cdn1.ozone.ru/s3/multimedia-1-v/7663370431.jpg
-debug.coffee:8 📁 Документ: product:4673764201776, Файл: main.jpg
-debug.coffee:8 🚀 Отправка XHR запроса...
-debug.coffee:8 ❌ Ошибка обработки товара 38:
-  
-
-    
-  
--  app/pages/Admin/Products/index.coffee
--  app/pages/Admin/Products/index.pug
--  app/pages/Admin/Products/index.styl
+- README.md
 

+ 1 - 2
app/index.styl

@@ -262,8 +262,7 @@ transition-all($duration = 0.3s)
   gap: 0.75rem
 
 .header__logo
-  width: 2rem
-  height: 2rem
+  height: 5rem
   object-fit: contain
 
 .header__nav-name

+ 434 - 1045
app/pages/Admin/Products/index.coffee

@@ -1,86 +1,42 @@
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
+document.head.insertAdjacentHTML('beforeend','<style type="text/css">'+stylFns['app/pages/Admin/Products/index.styl']+'</style>')
 
 PouchDB = require 'app/utils/pouch'
-Papa = require 'papaparse'
+
 
 module.exports =
   name: 'AdminProducts'
-  
   render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Products/index.pug'])()
   
   data: ->
     return {
       products: []
       categories: []
-      searchQuery: ''
-      selectedCategory: ''
-      selectedStatus: ''
       showProductModal: false
       showImportModal: false
-      showCategoriesModal: false
-      showCategoryModal: false
-      showMassActionsModal: false
-      showCategoryAssignModal: false
-      showMassCategoryAssign: false
-      showMassPriceModal: false
-      editingProduct: null
-      editingCategory: null
       selectedFile: null
-      selectedCategoriesFile: null
       importing: false
-      importingCategories: false
-      importResults: null
-      categoriesImportResults: null
-      availableDomains: []
-      categoriesActiveTab: 'list'
       importProgress: 0
-      processedCount: 0
-      totalCount: 0
-      uploadingImages: false
-      newImageUrl: ''
-      newAdditionalImageUrl: ''
-      newAttributeKey: ''
-      newAttributeValue: ''
-      newTag: ''
-      
-      # Mass actions data
-      selectedProducts: []
-      selectAll: false
-      massCategory: ''
-      massAllCategory: ''
-      removeExistingCategories: false
-      massRemoveAllCategories: false
-      priceChangeType: 'fixed'
-      priceChangeValue: null
-      applyToOldPrice: false
-      
-      productForm:
+      importResults: null
+      currentProduct: {
         _id: ''
+        type: 'product'
         name: ''
         sku: ''
-        category: ''
         price: 0
         oldPrice: 0
-        brand: ''
+        category: ''
         description: ''
-        image: ''
-        additionalImages: []
         active: true
         domains: []
         attributes: {}
-        tags: []
-      
-      categoryForm:
-        _id: ''
-        name: ''
-        slug: ''
-        description: ''
-        parentCategory: ''
-        sortOrder: 0
-        image: ''
-        icon: ''
-        active: true
-        domains: []
+        images: []
+        createdAt: ''
+        updatedAt: ''
+      }
+      searchQuery: ''
+      selectedCategory: ''
+      bulkActions: []
+      selectedProducts: []
     }
   
   computed:
@@ -97,1050 +53,483 @@ module.exports =
         products = products.filter (product) =>
           product.category == @selectedCategory
       
-      if @selectedStatus == 'active'
-        products = products.filter (product) => product.active
-      else if @selectedStatus == 'inactive'
-        products = products.filter (product) => !product.active
-      
       return products
     
-    isEditing: ->
-      @editingProduct != null
-    
-    attributesList: ->
-      list = []
-      for key, value of @productForm.attributes
-        list.push
-          key: key
-          value: value
-      return list
+    availableDomains: ->
+      @$root.currentDomainSettings?.domains or [@$root.currentDomain]
+  
+  mounted: ->
+    @loadProducts()
+    @loadCategories()
   
   methods:
     loadProducts: ->
+      debug.log '📥 Загрузка товаров...'
       PouchDB.queryView('admin', 'products', { include_docs: true })
-        .then (result) =>
-          @products = result.rows.map (row) -> row.doc
-        .catch (error) =>
-          debug.log 'Ошибка загрузки товаров:', error
-          @showNotification 'Ошибка загрузки товаров', 'error'
+      .then (result) =>
+        @products = result.rows.map (row) -> row.doc
+        debug.log "✅ Загружено #{@products.length} товаров"
+      .catch (error) =>
+        debug.log '❌ Ошибка загрузки товаров:', error
+        @showNotification 'Ошибка загрузки товаров', 'error'
     
     loadCategories: ->
+      debug.log '📥 Загрузка категорий...'
       PouchDB.queryView('admin', 'categories', { include_docs: true })
-        .then (result) =>
-          @categories = result.rows.map (row) -> row.doc
-        .catch (error) =>
-          debug.log 'Ошибка загрузки категорий:', error
-    
-    loadDomains: ->
-      PouchDB.queryView('admin', 'domain_settings', { include_docs: true })
-        .then (result) =>
-          @availableDomains = result.rows.map (row) -> row.doc
-        .catch (error) =>
-          debug.log 'Ошибка загрузки доменов:', error
-    
-    getCategoryName: (categoryId) ->
-      category = @categories.find (cat) -> cat._id == categoryId
-      category?.name or 'Без категории'
-    
-    getCategoryProductCount: (categoryId) ->
-      @products.filter((product) -> product.category == categoryId).length
-    
-    # Управление категориями
-    showCategoriesManager: ->
-      @showCategoriesModal = true
-    
-    createCategory: ->
-      @editingCategory = null
-      @resetCategoryForm()
-      @showCategoryModal = true
-    
-    editCategory: (category) ->
-      @editingCategory = category
-      @categoryForm = Object.assign {}, 
-        _id: category._id
-        name: category.name or ''
-        slug: category.slug or ''
-        description: category.description or ''
-        parentCategory: category.parentCategory or ''
-        sortOrder: category.sortOrder or 0
-        image: category.image or ''
-        icon: category.icon or ''
-        active: category.active != false
-        domains: category.domains or [window.location.hostname]
-      
-      @showCategoryModal = true
-    
-    saveCategory: ->
-      if !@categoryForm.name
-        @showNotification 'Введите название категории', 'error'
-        return
-      
-      categoryData = Object.assign {}, @categoryForm
-      delete categoryData._id
-      
-      if !categoryData.slug
-        categoryData.slug = @generateSlug(categoryData.name)
-      
-      if @editingCategory
-        # Обновление существующей категории
-        categoryData._id = @editingCategory._id
-        categoryData._rev = @editingCategory._rev
-        categoryData.updatedAt = new Date().toISOString()
-      else
-        # Создание новой категории
-        categoryData._id = "category:#{Date.now()}"
-        categoryData.type = 'category'
-        categoryData.createdAt = new Date().toISOString()
-        categoryData.updatedAt = categoryData.createdAt
-      
-      PouchDB.saveToRemote(categoryData)
-        .then (result) =>
-          @showCategoryModal = false
-          @resetCategoryForm()
-          @loadCategories()
-          @showNotification "Категория #{if @editingCategory then 'обновлена' else 'создана'}"
-        .catch (error) =>
-          debug.log 'Ошибка сохранения категории:', error
-          @showNotification 'Ошибка сохранения категории', 'error'
-    
-    deleteCategory: (category) ->
-      if !confirm("Удалить категорию \"#{category.name}\"? Товары в этой категории не будут удалены.")
-        return
-      
-      PouchDB.getDocument(category._id)
-        .then (doc) ->
-          PouchDB.saveToRemote(Object.assign {}, doc, { _deleted: true })
-        .then (result) =>
-          @loadCategories()
-          @showNotification 'Категория удалена'
-        .catch (error) =>
-          debug.log 'Ошибка удаления категории:', error
-          @showNotification 'Ошибка удаления категории', 'error'
-    
-    resetCategoryForm: ->
-      @categoryForm =
-        _id: ''
-        name: ''
-        slug: ''
-        description: ''
-        parentCategory: ''
-        sortOrder: 0
-        image: ''
-        icon: ''
+      .then (result) =>
+        @categories = result.rows.map (row) -> row.doc
+        debug.log "✅ Загружено #{@categories.length} категорий"
+      .catch (error) =>
+        debug.log '❌ Ошибка загрузки категорий:', error
+    
+    createCategory: (categoryName) ->
+      categorySlug = categoryName.toLowerCase().replace(/[^a-z0-9а-яё]/g, '-').replace(/-+/g, '-')
+      
+      categoryData = {
+        _id: "category:#{categorySlug}"
+        type: 'category'
+        name: categoryName
+        slug: categorySlug
         active: true
-        domains: [window.location.hostname]
-    
-    # Редактирование и создание товаров
-    createProduct: ->
-      @editingProduct = null
-      @resetProductForm()
-      @showProductModal = true
-    
-    editProduct: (product) ->
-      @editingProduct = product
-      @productForm = Object.assign {}, 
-        _id: product._id
-        name: product.name or ''
-        sku: product.sku or ''
-        category: product.category or ''
-        price: product.price or 0
-        oldPrice: product.oldPrice or 0
-        brand: product.brand or ''
-        description: product.description or ''
-        image: product.image or ''
-        additionalImages: product.additionalImages or []
-        active: product.active != false
-        domains: product.domains or [window.location.hostname]
-        attributes: product.attributes or {}
-        tags: product.tags or []
+        order: @categories.length
+        domains: @availableDomains
+        createdAt: new Date().toISOString()
+        updatedAt: new Date().toISOString()
+      }
       
-      @showProductModal = true
-    
-    saveProduct: ->
-      if !@productForm.name or !@productForm.sku or !@productForm.price
-        @showNotification 'Заполните обязательные поля: название, артикул и цена', 'error'
-        return
+      PouchDB.saveToRemote(categoryData)
+      .then (result) =>
+        debug.log "✅ Создана категория: #{categoryName}"
+        @loadCategories()
+        return categorySlug
+      .catch (error) =>
+        debug.log '❌ Ошибка создания категории:', error
+        throw error
+    
+    transformProductData: (csvData, index) ->
+      debug.log "🔄 Преобразование данных товара #{index + 1}: #{csvData['Артикул*']}"
+      
+      sku = csvData['Артикул*']?.toString().trim()
+      return null unless sku
+      
+      # Определяем категорию
+      categoryName = csvData['Тип*']?.trim() or 'Другое'
+      debug.log "🔍 Поиск категории: #{categoryName}"
       
-      productData = Object.assign {}, @productForm
-      delete productData._id
+      # Ищем существующую категорию
+      existingCategory = @categories.find (cat) -> cat.name == categoryName
       
-      if @isEditing
-        # Обновление существующего товара
-        productData._id = @editingProduct._id
-        productData._rev = @editingProduct._rev
-        productData.updatedAt = new Date().toISOString()
+      if existingCategory
+        categorySlug = existingCategory.slug
+        debug.log "✅ Использована существующая категория: #{categoryName}"
       else
-        # Создание нового товара
-        productData._id = "product:#{@productForm.sku}"
-        productData.type = 'product'
-        productData.createdAt = new Date().toISOString()
-        productData.updatedAt = productData.createdAt
-      
-      PouchDB.saveToRemote(productData)
-        .then (result) =>
-          @showProductModal = false
-          @resetProductForm()
-          @loadProducts()
-          @showNotification "Товар #{if @isEditing then 'обновлен' else 'создан'}"
-        .catch (error) =>
-          debug.log 'Ошибка сохранения товара:', error
-          @showNotification 'Ошибка сохранения товара', 'error'
-    
-    resetProductForm: ->
-      @productForm =
-        _id: ''
-        name: ''
-        sku: ''
-        category: ''
-        price: 0
-        oldPrice: 0
-        brand: ''
-        description: ''
-        image: ''
-        additionalImages: []
+        debug.log "🆕 Создание новой категории: #{categoryName}"
+        categorySlug = categoryName.toLowerCase().replace(/[^a-z0-9а-яё]/g, '-').replace(/-+/g, '-')
+        # Категория будет создана позже в процессе импорта
+      
+      # Базовые данные товара
+      productData = {
+        _id: "product:#{sku}"
+        type: 'product'
+        name: csvData['Название товара']?.trim() or "Товар #{sku}"
+        sku: sku
+        price: parseFloat(csvData['Цена, руб.*']?.replace(/\s/g, '')?.replace(',', '.') or 0)
+        oldPrice: parseFloat(csvData['Цена до скидки, руб.']?.replace(/\s/g, '')?.replace(',', '.') or 0)
+        category: categorySlug
+        brand: csvData['Бренд*']?.trim()
+        description: csvData['Аннотация']?.trim() or ''
         active: true
-        domains: [window.location.hostname]
+        domains: @availableDomains
         attributes: {}
-        tags: []
-    
-    # Управление атрибутами товара
-    addAttribute: ->
-      if !@newAttributeKey
-        @showNotification 'Введите название атрибута', 'error'
-        return
-      
-      if @productForm.attributes[@newAttributeKey]
-        @showNotification 'Атрибут с таким названием уже существует', 'error'
-        return
-      
-      @productForm.attributes[@newAttributeKey] = @newAttributeValue or ''
-      @newAttributeKey = ''
-      @newAttributeValue = ''
-      @showNotification 'Атрибут добавлен'
-    
-    removeAttribute: (key) ->
-      delete @productForm.attributes[key]
-      @showNotification 'Атрибут удален'
-    
-    updateAttribute: (key, value) ->
-      @productForm.attributes[key] = value
-    
-    # Управление тегами
-    addTag: ->
-      if !@newTag
-        @showNotification 'Введите тег', 'error'
-        return
-      
-      if @productForm.tags.includes(@newTag)
-        @showNotification 'Такой тег уже существует', 'error'
-        return
-      
-      @productForm.tags.push(@newTag)
-      @newTag = ''
-      @showNotification 'Тег добавлен'
-    
-    removeTag: (index) ->
-      @productForm.tags.splice(index, 1)
-      @showNotification 'Тег удален'
-    
-    # Загрузка изображений для редактирования
-    uploadMainImage: ->
-      if !@newImageUrl
-        @showNotification 'Введите URL изображения', 'error'
-        return
+        images: []
+        createdAt: new Date().toISOString()
+        updatedAt: new Date().toISOString()
+      }
       
-      @uploadingImages = true
-      productId = if @isEditing then @editingProduct._id else "product:#{@productForm.sku}"
+      # Дополнительные атрибуты
+      additionalAttributes = {}
+      for key, value of csvData
+        if value and not key in ['Артикул*', 'Название товара', 'Цена, руб.*', 'Цена до скидки, руб.', 'Тип*', 'Бренд*', 'Аннотация', 'Rich-контент JSON', 'Ссылка на главное фото', 'Ссылки на дополнительные фото']
+          additionalAttributes[key] = value
       
-      @downloadAndStoreImage(@newImageUrl, productId, 'main.jpg')
-        .then (attachmentInfo) =>
-          @productForm.image = "/d/braer_color_shop/#{productId}/main.jpg"
-          @newImageUrl = ''
-          @showNotification 'Основное изображение загружено'
-        .catch (error) =>
-          debug.log 'Ошибка загрузки основного изображения:', error
-          @showNotification 'Ошибка загрузки изображения', 'error'
-        .finally =>
-          @uploadingImages = false
-    
-    uploadAdditionalImage: ->
-      if !@newAdditionalImageUrl
-        @showNotification 'Введите URL изображения', 'error'
-        return
+      productData.attributes = additionalAttributes
       
-      @uploadingImages = true
-      productId = if @isEditing then @editingProduct._id else "product:#{@productForm.sku}"
-      index = @productForm.additionalImages.length
+      # Rich-контент
+      if csvData['Rich-контент JSON']
+        try
+          richContent = JSON.parse(csvData['Rich-контент JSON'])
+          productData.richContent = richContent
+        catch error
+          debug.log '⚠️ Ошибка парсинга Rich-контента:', error
       
-      @downloadAndStoreImage(@newAdditionalImageUrl, productId, "additional-#{index}.jpg")
-        .then (attachmentInfo) =>
-          imagePath = "/d/braer_color_shop/#{productId}/additional-#{index}.jpg"
-          @productForm.additionalImages.push(imagePath)
-          @newAdditionalImageUrl = ''
-          @showNotification 'Дополнительное изображение загружено'
-        .catch (error) =>
-          debug.log 'Ошибка загрузки дополнительного изображения:', error
-          @showNotification 'Ошибка загрузки изображения', 'error'
-        .finally =>
-          @uploadingImages = false
-    
-    removeAdditionalImage: (index) ->
-      @productForm.additionalImages.splice(index, 1)
-      @showNotification 'Изображение удалено'
+      debug.log "✅ Данные товара полностью преобразованы: #{sku}"
+      return productData
     
-    # ИСПРАВЛЕННЫЙ метод загрузки и сохранения изображения как attachment
     downloadAndStoreImage: (imageUrl, docId, filename) ->
       return new Promise (resolve, reject) =>
         debug.log "🔄 Начало загрузки изображения: #{imageUrl}"
-        debug.log "📁 Документ: #{docId}, Файл: #{filename}"
         
-        try
-          # Создаем XMLHttpRequest для загрузки изображения
-          xhr = new XMLHttpRequest()
-          xhr.open('GET', imageUrl, true)
-          xhr.responseType = 'blob'
-          
-          xhr.onload = =>
-            debug.log "📡 Статус ответа XHR: #{xhr.status}"
-            if xhr.status == 200
-              blob = xhr.response
-              debug.log "✅ Blob получен:", 
-                type: blob.type
-                size: blob.size
-                isBlob: blob instanceof Blob
-              
-              # Читаем blob как ArrayBuffer
-              reader = new FileReader()
-              
-              reader.onloadstart = ->
-                debug.log "📖 Начало чтения blob как ArrayBuffer"
-              
-              reader.onload = (e) =>
-                debug.log "✅ ArrayBuffer успешно прочитан"
-                arrayBuffer = e.target.result
-                debug.log "📊 ArrayBuffer:",
-                  byteLength: arrayBuffer.byteLength
-                  isArrayBuffer: arrayBuffer instanceof ArrayBuffer
-                
-                # Получаем текущий документ для правильного _rev
-                debug.log "🔍 Получение документа #{docId} для _rev"
-                PouchDB.getDocument(docId)
-                  .then (doc) =>
-                    debug.log "✅ Документ получен:", 
-                      _id: doc._id
-                      _rev: doc._rev?.substring(0, 10) + '...'
-                    
-                    # Сохраняем как attachment в PouchDB
-                    debug.log "💾 Сохранение attachment..."
-                    return PouchDB.putAttachment(docId, filename, doc._rev, arrayBuffer, blob.type)
-                  .then (result) =>
-                    debug.log "✅ Attachment успешно сохранен:", result
-                    resolve
-                      filename: filename
-                      contentType: blob.type
-                      size: blob.size
-                      url: "/d/braer_color_shop/#{docId}/#{filename}"
-                  .catch (error) =>
-                    debug.log "❌ Ошибка при работе с документом:", error
-                    if error.status == 404
-                      debug.log "📄 Документ не найден, создаем временный"
-                      # Документа нет - создаем временный для attachment
-                      tempDoc = 
-                        _id: docId
-                        type: 'product'
-                        name: 'Temp'
-                        sku: 'temp'
-                        price: 0
-                        active: false
-                        createdAt: new Date().toISOString()
-                        updatedAt: new Date().toISOString()
-                      
-                      PouchDB.saveToRemote(tempDoc)
-                        .then =>
-                          debug.log "✅ Временный документ создан"
-                          PouchDB.putAttachment(docId, filename, tempDoc._rev, arrayBuffer, blob.type)
-                        .then (result) =>
-                          debug.log "✅ Attachment сохранен во временный документ:", result
-                          resolve
-                            filename: filename
-                            contentType: blob.type
-                            size: blob.size
-                            url: "/d/braer_color_shop/#{docId}/#{filename}"
-                        .catch (err) ->
-                          debug.log "❌ Ошибка сохранения attachment во временный документ:", err
-                          reject(err)
-                    else
-                      debug.log "❌ Другая ошибка при получении документа:", error
-                      reject(error)
-              
-              reader.onerror = (error) =>
-                debug.log "❌ Ошибка чтения blob:", error
-                reject(new Error("Ошибка чтения blob: #{error}"))
-              
-              reader.onabort = ->
-                debug.log "⚠️ Чтение blob прервано"
-              
-              debug.log "🔁 Чтение blob как ArrayBuffer..."
-              reader.readAsArrayBuffer(blob)
-            else
-              errorMsg = "Ошибка загрузки изображения: #{xhr.status}"
-              debug.log "❌ #{errorMsg}"
-              reject(new Error(errorMsg))
+        # Проверяем валидность URL
+        unless imageUrl and imageUrl.startsWith('http')
+          debug.log '⚠️ Невалидный URL изображения:', imageUrl
+          return resolve(null)
+        
+        # Создаем уникальное имя файла
+        fileExtension = imageUrl.split('.').pop()?.split('?')[0] or 'jpg'
+        uniqueFilename = "#{filename}.#{fileExtension}"
+        
+        debug.log "📁 Документ: #{docId}, Файл: #{uniqueFilename}"
+        
+        # Используем fetch вместо XMLHttpRequest для лучшей обработки ошибок
+        fetch(imageUrl)
+        .then (response) =>
+          unless response.ok
+            throw new Error("HTTP #{response.status}: #{response.statusText}")
+          return response.blob()
+        .then (blob) =>
+          debug.log "✅ Blob получен, размер: #{blob.size} байт"
           
-          xhr.onerror = =>
-            errorMsg = 'Ошибка сети при загрузке изображения'
-            debug.log "❌ #{errorMsg}"
-            reject(new Error(errorMsg))
+          if blob.size == 0
+            throw new Error('Пустой blob')
           
-          xhr.ontimeout = =>
-            errorMsg = 'Таймаут загрузки изображения'
-            debug.log "❌ #{errorMsg}"
-            reject(new Error(errorMsg))
+          # Читаем blob как ArrayBuffer
+          reader = new FileReader()
+          reader.onload = (event) =>
+            try
+              arrayBuffer = event.target.result
+              debug.log "✅ ArrayBuffer успешно прочитан, размер: #{arrayBuffer.byteLength} байт"
+              
+              # Сохраняем attachment в PouchDB
+              PouchDB.localDb.putAttachment(
+                docId,
+                uniqueFilename,
+                @currentProduct._rev,
+                blob,
+                blob.type
+              )
+              .then (result) =>
+                debug.log "✅ Attachment сохранен: #{uniqueFilename}"
+                resolve({
+                  filename: uniqueFilename
+                  contentType: blob.type
+                  size: blob.size
+                })
+              .catch (attachmentError) =>
+                debug.log "❌ Ошибка сохранения attachment:", attachmentError
+                reject(attachmentError)
+            catch readError
+              debug.log "❌ Ошибка чтения blob:", readError
+              reject(readError)
           
-          xhr.onabort = =>
-            debug.log "⚠️ Загрузка изображения прервана"
+          reader.onerror = (error) =>
+            debug.log "❌ Ошибка FileReader:", error
+            reject(error)
           
-          debug.log "🚀 Отправка XHR запроса..."
-          xhr.send()
+          reader.readAsArrayBuffer(blob)
+        .catch (fetchError) =>
+          debug.log "❌ Ошибка загрузки изображения:", fetchError
+          reject(fetchError)
+    
+    processProductImages: (productData, csvData) ->
+      debug.log "🖼️ Начало обработки изображений для товара: #{productData.sku}"
+      
+      imagePromises = []
+      
+      # Основное изображение
+      mainImageUrl = csvData['Ссылка на главное фото']?.trim()
+      if mainImageUrl
+        imagePromises.push(
+          @downloadAndStoreImage(mainImageUrl, productData._id, 'main')
+          .then (imageInfo) =>
+            if imageInfo
+              productData.mainImage = imageInfo.filename
+              return imageInfo
+            return null
+          .catch (error) =>
+            debug.log "⚠️ Не удалось загрузить основное изображение:", error
+            return null
+        )
+      
+      # Дополнительные изображения
+      additionalImages = csvData['Ссылки на дополнительные фото']
+      if additionalImages
+        # Разделяем строку по переносам и фильтруем пустые значения
+        imageUrls = additionalImages.split('\n')
+          .map((url) -> url.trim())
+          .filter((url) -> url and url.startsWith('http'))
+          .slice(0, 5) # Ограничиваем 5 изображениями
         
-        catch error
-          debug.log "💥 Критическая ошибка в downloadAndStoreImage:", error
-          reject(error)
-    
-    # Импорт товаров из CSV
-    onFileSelect: (event) ->
-      @selectedFile = event.target.files[0]
-      @importResults = null
-      @importProgress = 0
-      @processedCount = 0
+        imageUrls.forEach (imageUrl, index) =>
+          imagePromises.push(
+            @downloadAndStoreImage(imageUrl, productData._id, "additional-#{index + 1}")
+            .then (imageInfo) =>
+              if imageInfo
+                return imageInfo.filename
+              return null
+            .catch (error) =>
+              debug.log "⚠️ Не удалось загрузить дополнительное изображение:", error
+              return null
+          )
+      
+      return Promise.allSettled(imagePromises)
+      .then (results) =>
+        # Фильтруем успешно загруженные изображения
+        successfulResults = results.filter (r) -> r.status == 'fulfilled' and r.value
+        additionalFilenames = successfulResults.slice(1).map (r) -> r.value?.filename
+        productData.additionalImages = additionalFilenames.filter (filename) -> filename
+        debug.log "✅ Обработано изображений: #{successfulResults.length}"
+        return productData
+      .catch (error) =>
+        debug.log "❌ Ошибка обработки изображений:", error
+        return productData
+    
+    saveProduct: (productData) ->
+      debug.log "💾 Попытка сохранения товара: #{productData.sku}"
+      
+      return new Promise (resolve, reject) =>
+        # Сначала пытаемся получить существующий документ для получения _rev
+        PouchDB.getDocument(productData._id)
+        .then (existingDoc) =>
+          debug.log "🔄 Обновление существующего товара: #{productData.sku}"
+          productData._rev = existingDoc._rev
+          productData.updatedAt = new Date().toISOString()
+          
+          # Сохраняем в удаленную БД
+          PouchDB.saveToRemote(productData)
+          .then (result) =>
+            debug.log "✅ Товар сохранен, получение обновленной версии: #{productData.sku}"
+            # Получаем обновленный документ
+            PouchDB.getDocument(productData._id)
+            .then (updatedDoc) =>
+              debug.log "✅ Документ получен с актуальным _rev: #{updatedDoc._rev?.substring(0, 10)}..."
+              resolve(updatedDoc)
+            .catch (getError) =>
+              debug.log "⚠️ Не удалось получить обновленный документ:", getError
+              resolve(result)
+          .catch (saveError) =>
+            debug.log "❌ Ошибка сохранения товара:", saveError
+            reject(saveError)
+        .catch (getError) =>
+          if getError.status == 404
+            debug.log "🆕 Создание нового товара: #{productData.sku}"
+            productData.createdAt = new Date().toISOString()
+            productData.updatedAt = productData.createdAt
+            
+            PouchDB.saveToRemote(productData)
+            .then (result) =>
+              debug.log "✅ Товар сохранен в БД: #{productData.sku}"
+              resolve(result)
+            .catch (saveError) =>
+              debug.log "❌ Ошибка создания товара:", saveError
+              reject(saveError)
+          else
+            debug.log "❌ Ошибка при получении документа:", getError
+            reject(getError)
+    
+    readFile: (file) ->
+      return new Promise (resolve, reject) =>
+        reader = new FileReader()
+        reader.onload = (event) -> resolve(event.target.result)
+        reader.onerror = (error) -> reject(error)
+        reader.readAsText(file, 'UTF-8')
     
     importProducts: ->
-      if !@selectedFile
+      unless @selectedFile
         @showNotification 'Выберите файл для импорта', 'error'
         return
       
       @importing = true
-      @importResults = null
       @importProgress = 0
-      @processedCount = 0
+      @importResults = null
       
-      reader = new FileReader()
-      reader.onload = (e) =>
-        try
-          results = Papa.parse e.target.result,
-            header: true
-            delimiter: ';'
-            skipEmptyLines: true
-            encoding: 'UTF-8'
-          
-          products = results.data.filter (row) => 
-            row and row['Артикул*'] and row['Название товара'] and row['Цена, руб.*']
-          
-          @totalCount = products.length
-          debug.log "📊 Найдено товаров для импорта: #{@totalCount}"
-          
-          # Обрабатываем товары последовательно
-          @processProductsSequentially(products)
-            .then (results) =>
-              successCount = results.filter((r) -> r.success).length
-              errorCount = results.filter((r) -> !r.success).length
-              
-              @importResults = 
-                success: true
-                processed: successCount
-                errors: results.filter((r) -> !r.success).map((r) -> r.error)
-                total: @totalCount
-              
-              @importing = false
-              @loadProducts()
-              @loadCategories()
-              @showNotification "Импортировано #{successCount} товаров (#{errorCount} ошибок)"
-            .catch (error) =>
-              @importResults = 
-                success: false
-                error: error.message
-                processed: 0
-                errors: [error.message]
-              
-              @importing = false
-              @showNotification "Ошибка импорта: #{error.message}", 'error'
+      debug.log '📦 Начало импорта товаров...'
+      
+      @readFile(@selectedFile)
+      .then (text) =>
+        # Парсим CSV
+        results = Papa.parse(text, {
+          header: true
+          delimiter: ';'
+          skipEmptyLines: true
+          encoding: 'UTF-8'
+        })
         
-        catch error
-          @importResults = 
-            success: false
-            error: error.message
-            processed: 0
-            errors: [error.message]
-          
-          @importing = false
-          @showNotification "Ошибка обработки файла: #{error.message}", 'error'
-      
-      reader.readAsText(@selectedFile, 'UTF-8')
-    
-    # Исправленная последовательная обработка товаров
-    processProductsSequentially: (products) ->
-      return new Promise (resolve, reject) =>
-        results = []
-        currentIndex = 0
+        # Фильтруем валидные строки
+        validProducts = results.data.filter (row, index) =>
+          row and row['Артикул*'] and row['Название товара'] and row['Цена, руб.*']
         
-        processNextProduct = =>
-          if currentIndex >= products.length
-            debug.log "✅ Все товары обработаны. Успешно: #{results.filter((r) -> r.success).length}, Ошибок: #{results.filter((r) -> !r.success).length}"
-            resolve(results)
-            return
-          
-          product = products[currentIndex]
-          currentIndex++
-          debug.log "🔧 Обработка товара #{currentIndex}/#{products.length}: #{product['Название товара']?.substring(0, 50)}..."
-          
-          @transformProductData(product, currentIndex)
-            .then (productData) =>
-              debug.log "✅ Данные товара преобразованы: #{productData.sku}"
-              # Сохраняем каждый товар в отдельный документ
-              return @saveProductToDB(productData)
-            .then (savedProduct) =>
-              debug.log "✅ Товар сохранен в БД: #{savedProduct.sku}"
-              # Затем обрабатываем изображения для сохраненного товара
-              return @processProductImages(product, savedProduct)
-            .then (finalProduct) =>
-              @processedCount = currentIndex
-              @importProgress = Math.round((currentIndex / products.length) * 100)
-              results.push(success: true, product: finalProduct)
-              debug.log "✅ Товар полностью обработан: #{finalProduct.sku}"
-              processNextProduct()
-            .catch (error) =>
-              debug.log "❌ Ошибка обработки товара #{currentIndex}:", error
-              @processedCount = currentIndex
-              @importProgress = Math.round((currentIndex / products.length) * 100)
-              results.push(success: false, error: error.message, product: product)
-              # Продолжаем обработку следующих товаров даже при ошибке
-              debug.log "➡️ Продолжение обработки следующих товаров..."
-              processNextProduct()
+        debug.log "📊 Найдено валидных товаров: #{validProducts.length}"
         
-        debug.log "🚀 Запуск последовательной обработки #{products.length} товаров"
-        processNextProduct()
-    
-    # Сохранение товара в БД с правильной обработкой ревизий
-    saveProductToDB: (productData) ->
-      return new Promise (resolve, reject) =>
-        debug.log "💾 Попытка сохранения товара: #{productData.sku}"
-        # Сначала пытаемся получить существующий документ
-        PouchDB.getDocument(productData._id)
-          .then (existingDoc) =>
-            # Документ существует - обновляем
-            debug.log "🔄 Обновление существующего товара: #{productData.sku}"
-            # Сохраняем только данные, без _rev (PouchDB сам обработает)
-            updatedData = Object.assign {}, productData
-            delete updatedData._rev
-            updatedData.updatedAt = new Date().toISOString()
-            return PouchDB.saveToRemote(updatedData)
-          .catch (error) =>
-            if error.status == 404
-              # Документ не существует - создаем новый
-              debug.log "🆕 Создание нового товара: #{productData.sku}"
-              productData.createdAt = new Date().toISOString()
-              productData.updatedAt = productData.createdAt
-              return PouchDB.saveToRemote(productData)
-            else
-              throw error
-          .then (result) =>
-            # Получаем обновленный документ с правильным _rev
-            debug.log "✅ Товар сохранен, получение обновленной версии: #{productData.sku}"
-            return PouchDB.getDocument(productData._id)
-          .then (savedDoc) =>
-            debug.log "✅ Документ получен с актуальным _rev: #{savedDoc._rev?.substring(0, 10)}..."
-            resolve(savedDoc)
-          .catch (error) =>
-            debug.log "❌ Ошибка сохранения товара #{productData.sku}:", error
-            reject(error)
-    
-    # Обработка изображений после сохранения основного документа
-    processProductImages: (product, savedProduct) ->
-      debug.log "🖼️ Начало обработки изображений для товара: #{savedProduct.sku}"
-      promises = []
-      
-      # Обработка основного изображения
-      if product['Ссылка на главное фото*']
-        imageUrl = product['Ссылка на главное фото*'].trim()
-        if imageUrl
-          debug.log "📸 Загрузка основного изображения: #{imageUrl}"
-          promises.push @downloadAndStoreImage(imageUrl, savedProduct._id, 'main.jpg')
-            .then (attachmentInfo) =>
-              debug.log "✅ Основное изображение загружено, обновление товара"
-              savedProduct.image = "/d/braer_color_shop/#{savedProduct._id}/main.jpg"
-              return PouchDB.saveToRemote(savedProduct)
-            .catch (error) =>
-              debug.log "❌ Ошибка загрузки основного изображения:", error
-              return savedProduct
-      else
-        debug.log "⏭️ Основное изображение отсутствует"
-      
-      # Обработка дополнительных изображений
-      if product['Ссылки на дополнительные фото']
-        additionalImages = product['Ссылки на дополнительные фото']
-        if typeof additionalImages == 'string'
-          imageUrls = additionalImages.split('\n').filter((url) -> url.trim())
-        else
-          imageUrls = []
+        if validProducts.length == 0
+          throw new Error('Не найдено валидных товаров для импорта')
         
-        debug.log "🖼️ Найдено дополнительных изображений: #{imageUrls.length}"
+        # Создаем массив для обещаний
+        importPromises = []
+        processedCount = 0
+        errors = []
         
-        if imageUrls.length > 0
-          savedProduct.additionalImages = []
-          for imageUrl, i in imageUrls.slice(0, 3)
-            do (imageUrl, i) =>
-              debug.log "📸 Загрузка дополнительного изображения #{i}: #{imageUrl}"
-              promise = @downloadAndStoreImage(imageUrl.trim(), savedProduct._id, "additional-#{i}.jpg")
-                .then (attachmentInfo) =>
-                  imagePath = "/d/braer_color_shop/#{savedProduct._id}/additional-#{i}.jpg"
-                  savedProduct.additionalImages.push(imagePath)
-                  debug.log "✅ Дополнительное изображение #{i} загружено"
-                  return savedProduct
-                .catch (error) =>
-                  debug.log "❌ Ошибка загрузки дополнительного изображения #{i}:", error
-                  return savedProduct
+        # Обрабатываем каждый товар
+        validProducts.forEach (product, index) =>
+          promise = =>
+            debug.log "🔧 Обработка товара #{index + 1}/#{validProducts.length}: #{product['Название товара']?.substring(0, 50)}..."
+            
+            try
+              # Преобразуем данные CSV в объект товара
+              productData = @transformProductData(product, index)
+              return Promise.resolve(null) unless productData
               
-              promises.push(promise)
-      
-      if promises.length == 0
-        debug.log "⏭️ Нет изображений для загрузки"
-        return Promise.resolve(savedProduct)
-      
-      debug.log "⏳ Ожидание загрузки #{promises.length} изображений..."
-      return Promise.all(promises)
-        .then =>
-          debug.log "✅ Все изображения загружены, обновление документа"
-          # Обновляем документ с информацией об изображениях
-          return PouchDB.saveToRemote(savedProduct)
-        .then (result) =>
-          debug.log "✅ Документ обновлен с информацией об изображениях"
-          return PouchDB.getDocument(savedProduct._id)
-        .catch (error) =>
-          debug.log "❌ Ошибка обновления товара с изображениями:", error
-          return savedProduct
-    
-    # Полное преобразование данных товара с правильной структурой
-    transformProductData: (product, index) ->
-      return new Promise (resolve, reject) =>
-        try
-          # Генерируем ID на основе артикула для постоянства
-          sku = product['Артикул*']?.trim() or "SKU-#{Date.now()}-#{index}"
-          productId = "product:#{sku}"
-          
-          debug.log "🔄 Преобразование данных товара #{index}: #{sku}"
+              # Обрабатываем категорию
+              categoryName = product['Тип*']?.trim() or 'Другое'
+              existingCategory = @categories.find (cat) -> cat.name == categoryName
+              
+              if not existingCategory
+                debug.log "🏷️ Создание категории: #{categoryName}"
+                return @createCategory(categoryName)
+                .then (categorySlug) =>
+                  productData.category = categorySlug
+                  # Перезагружаем категории
+                  @loadCategories()
+                  return productData
+                .catch (categoryError) =>
+                  debug.log "⚠️ Не удалось создать категорию, используется 'Другое'"
+                  productData.category = 'drugoe'
+                  return productData
+              else
+                return Promise.resolve(productData)
+            catch transformError
+              debug.log "❌ Ошибка преобразования товара:", transformError
+              errors.push("Товар #{index + 1}: #{transformError.message}")
+              return Promise.resolve(null)
+          .then (productData) =>
+            return null unless productData
+            
+            # Обрабатываем изображения
+            return @processProductImages(productData, product)
+            .then (productWithImages) =>
+              # Сохраняем товар
+              return @saveProduct(productWithImages)
+            .then (savedProduct) =>
+              processedCount++
+              @importProgress = Math.round((processedCount / validProducts.length) * 100)
+              debug.log "✅ Обработан товар #{processedCount}/#{validProducts.length}: #{savedProduct.sku}"
+              return savedProduct
+            .catch (saveError) =>
+              errorMsg = "Товар #{index + 1} (#{productData.sku}): #{saveError.message}"
+              debug.log "❌ Ошибка обработки товара #{index + 1}:", saveError
+              errors.push(errorMsg)
+              return null
+        
+          importPromises.push(promise())
+        
+        # Ожидаем завершения всех операций
+        return Promise.allSettled(importPromises)
+        .then (results) =>
+          successfulImports = results.filter((r) -> r.status == 'fulfilled' and r.value).length
+          failedImports = results.filter((r) -> r.status == 'rejected').length
           
-          # Базовые поля согласно design документам
-          productData =
-            _id: productId
-            type: 'product'
-            name: product['Название товара']?.trim() or 'Без названия'
-            sku: sku
-            price: @parsePrice(product['Цена, руб.*'])
-            active: true
-            createdAt: new Date().toISOString()
-            updatedAt: new Date().toISOString()
-            domains: [window.location.hostname]
-            additionalImages: []
-            attributes: {}
-            tags: []
+          @importResults = {
+            success: true
+            processed: validProducts.length
+            successful: successfulImports
+            failed: failedImports
+            errors: errors
+          }
           
-          # Обработка всех полей CSV в единую структуру attributes
-          @processAllCSVFields(product, productData)
+          debug.log "🎉 Импорт завершен: #{successfulImports} успешно, #{failedImports} с ошибками"
           
-          # Обработка категории
-          @processCategory(product, productData, index)
-            .then =>
-              # Обработка Rich-контента
-              @processRichContent(product, productData)
-              debug.log "✅ Данные товара полностью преобразованы: #{productData.sku}"
-              resolve(productData)
-            .catch (error) =>
-              debug.log "⚠️ Ошибка обработки товара, возвращаем частичные данные:", error
-              # Возвращаем товар даже с ошибками обработки
-              resolve(productData)
-        
-        catch error
-          debug.log "❌ Критическая ошибка преобразования данных товара:", error
-          reject(error)
-    
-    # Парсинг цены
-    parsePrice: (priceString) ->
-      return 0 if !priceString
-      try
-        # Удаляем пробелы и заменяем запятые на точки
-        cleanPrice = priceString.toString().replace(/\s/g, '').replace(',', '.')
-        price = parseFloat(cleanPrice)
-        return if isNaN(price) then 0 else Math.round(price * 100) / 100
-      catch
-        return 0
-    
-    # Обработка всех полей CSV в единую структуру attributes
-    processAllCSVFields: (product, productData) ->
-      # Базовые поля
-      if product['Бренд*']?.trim()
-        productData.brand = product['Бренд*']?.trim()
-      
-      if product['Аннотация']?.trim()
-        productData.description = product['Аннотация']?.trim()
-      
-      # Цены
-      if product['Цена до скидки, руб.']
-        productData.oldPrice = @parsePrice(product['Цена до скидки, руб.'])
-      
-      # Основные характеристики с оригинальными названиями
-      @setAttribute productData, 'Вес товара, г', product
-      @setAttribute productData, 'Объем, л', product
-      @setAttribute productData, 'Страна-изготовитель', product
-      @setAttribute productData, 'Гарантия', product
-      @setAttribute productData, 'Цвет товара', product
-      @setAttribute productData, 'Название цвета', product
-      @setAttribute productData, 'Класс опасности товара*', product
-      @setAttribute productData, 'Степень блеска покрытия', product
-      @setAttribute productData, 'Работы', product
-      @setAttribute productData, 'Количество товара в УЕИ', product
-      
-      # Технические характеристики
-      @setAttribute productData, 'Расход, л/м2', product
-      @setAttribute productData, 'Время высыхания, часов', product
-      @setAttribute productData, 'Вид краски', product
-      @setAttribute productData, 'Основа краски', product
-      @setAttribute productData, 'Способ нанесения', product
-      @setAttribute productData, 'Область применения состава', product
-      @setAttribute productData, 'Назначение грунтовки', product
-      @setAttribute productData, 'Рекомендуемое количество слоев', product
-      @setAttribute productData, 'Расход, кг/м2', product
-      @setAttribute productData, 'Количество компонентов', product
-      @setAttribute productData, 'Особенности ЛКМ', product
-      @setAttribute productData, 'Макс. температура эксплуатации, С°', product
-      @setAttribute productData, 'Материал основания', product
-      @setAttribute productData, 'Основа грунтовки', product
-      @setAttribute productData, 'Форма выпуска средства', product
-      @setAttribute productData, 'Назначение', product
-      @setAttribute productData, 'Тип помещения', product
-      @setAttribute productData, 'Вид выпуска товара', product
-      @setAttribute productData, 'Тип растворителя', product
-      @setAttribute productData, 'Эффект краски', product
-      @setAttribute productData, 'Марка эмали', product
-      @setAttribute productData, 'Базис', product
-      @setAttribute productData, 'Помещение', product
-      
-      # Флаги и булевы значения (объединены с attributes)
-      @setBooleanAttribute productData, 'Рассрочка', product
-      @setBooleanAttribute productData, 'Баллы за отзывы', product
-      @setBooleanAttribute productData, 'Возможность колеровки', product
-      @setBooleanAttribute productData, 'Аэрозоль', product
-      @setBooleanAttribute productData, 'Можно мыть', product
-      
-      # Мета-данные
-      if product['#Хештеги']
-        tags = product['#Хештеги']?.split('#').filter((tag) -> tag.trim()).map((tag) -> tag.trim())
-        productData.tags = tags or []
+          if successfulImports > 0
+            @showNotification "Импортировано #{successfulImports} товаров"
+            @loadProducts() # Перезагружаем список
+          else
+            @showNotification 'Не удалось импортировать ни одного товара', 'error'
+      .catch (error) =>
+        debug.log '❌ Ошибка импорта:', error
+        @importResults = {
+          success: false
+          error: error.message
+          processed: 0
+          successful: 0
+          failed: 0
+          errors: [error.message]
+        }
+        @showNotification "Ошибка импорта: #{error.message}", 'error'
+      .finally =>
+        @importing = false
+        @selectedFile = null
     
-    # Вспомогательный метод для установки атрибута
-    setAttribute: (productData, fieldName, product) ->
-      if product[fieldName]?.trim()
-        # Сохраняем оригинальное название поля
-        productData.attributes[fieldName] = product[fieldName]?.trim()
+    editProduct: (product) ->
+      @currentProduct = Object.assign({}, product)
+      @showProductModal = true
     
-    # Вспомогательный метод для установки булевых атрибутов
-    setBooleanAttribute: (productData, fieldName, product) ->
-      if product[fieldName]
-        value = product[fieldName]?.toLowerCase()
-        productData.attributes[fieldName] = value == 'да'
+    deleteProduct: (product) ->
+      if confirm("Удалить товар \"#{product.name}\"?")
+        PouchDB.localDb.remove(product)
+        .then =>
+          PouchDB.saveToRemote(product) # Удаляем из удаленной БД
+          .then =>
+            @showNotification 'Товар удален'
+            @loadProducts()
+          .catch (error) =>
+            debug.log '❌ Ошибка удаления товара из удаленной БД:', error
+            @showNotification 'Ошибка удаления товара', 'error'
+        .catch (error) =>
+          debug.log '❌ Ошибка удаления товара:', error
+          @showNotification 'Ошибка удаления товара', 'error'
     
-    # Обработка категории с проверкой дубликатов
-    processCategory: (product, productData, index) ->
-      return Promise.resolve() if !product['Тип*']
-      
-      categoryName = product['Тип*'].trim()
-      return Promise.resolve() if !categoryName
-      
-      debug.log "🔍 Поиск категории: #{categoryName}"
+    saveProductForm: ->
+      unless @currentProduct.name and @currentProduct.sku and @currentProduct.price
+        @showNotification 'Заполните обязательные поля', 'error'
+        return
       
-      # Поиск существующей категории (регистронезависимо)
-      existingCategory = @categories.find (cat) -> 
-        cat.name?.toLowerCase() == categoryName.toLowerCase()
+      productData = Object.assign({}, @currentProduct)
       
-      if existingCategory
-        productData.category = existingCategory._id
-        debug.log "✅ Использована существующая категория: #{categoryName}"
-        return Promise.resolve()
+      if productData._id
+        # Обновление существующего товара
+        productData.updatedAt = new Date().toISOString()
       else
-        # Создание новой категории
-        categoryId = "category:#{Date.now()}-#{index}"
-        newCategory =
-          _id: categoryId
-          type: 'category'
-          name: categoryName
-          slug: @generateSlug(categoryName)
-          sortOrder: @categories.length
-          active: true
-          createdAt: new Date().toISOString()
-          updatedAt: new Date().toISOString()
-          domains: [window.location.hostname]
-        
-        productData.category = categoryId
-        
-        debug.log "🆕 Создание новой категории: #{categoryName}"
-        return PouchDB.saveToRemote(newCategory)
-          .then (result) =>
-            debug.log "✅ Создана новая категория: #{categoryName}"
-            @categories.push(newCategory)
-            return Promise.resolve()
-          .catch (error) ->
-            debug.log "❌ Ошибка создания категории #{categoryName}:", error
-            # Продолжаем без категории
-            return Promise.resolve()
-    
-    # Обработка Rich-контента JSON и преобразование в Markdown
-    processRichContent: (product, productData) ->
-      # Сначала пробуем Rich-контент JSON
-      if product['Rich-контент JSON'] and product['Rich-контент JSON'].trim()
-        try
-          richContent = JSON.parse(product['Rich-контент JSON'])
-          markdownDescription = @richContentToMarkdown(richContent)
-          # Если получили Markdown, используем его как описание
-          if markdownDescription and markdownDescription != 'Описание товара'
-            productData.description = markdownDescription
-          productData.attributes['Rich-контент JSON'] = product['Rich-контент JSON']
-          return
-        catch error
-          debug.log "❌ Ошибка парсинга Rich-контента:", error
-      
-      # Если Rich-контент невалиден или отсутствует, используем аннотацию
-      if product['Аннотация'] and product['Аннотация'].trim() and !productData.description
-        productData.description = product['Аннотация'].trim()
-    
-    # Преобразование Rich-контента JSON в Markdown
-    richContentToMarkdown: (richContent) ->
-      return '' if !richContent
-      
-      try
-        markdownParts = []
-        
-        if richContent.content and Array.isArray(richContent.content)
-          for item in richContent.content
-            markdownParts.push(@processContentItem(item))
-        
-        result = markdownParts.filter((part) -> part).join('\n\n')
-        return result or 'Описание товара'
-      catch error
-        debug.log "❌ Ошибка преобразования Rich-контента в Markdown:", error
-        return ''
-    
-    # Обработка отдельного элемента контента
-    processContentItem: (item) ->
-      return '' if !item
-      
-      switch item.widgetName
-        when 'raTextBlock'
-          return @processTextBlock(item)
-        when 'raHeader'
-          return @processHeader(item)
-        when 'raImage'
-          return @processImage(item)
-        when 'raList'
-          return @processList(item)
-        else
-          return ''
-    
-    # Обработка текстового блока
-    processTextBlock: (item) ->
-      textParts = []
-      
-      # Заголовок
-      if item.title and item.title.items
-        titleText = @processTextItems(item.title.items)
-        if titleText
-          level = item.title.size or 'size3'
-          hashes = @getHeadingLevel(level)
-          textParts.push("#{hashes} #{titleText}")
-      
-      # Основной текст
-      if item.text and item.text.items
-        bodyText = @processTextItems(item.text.items)
-        if bodyText
-          textParts.push(bodyText)
-      
-      return textParts.join('\n\n')
-    
-    # Обработка заголовка
-    processHeader: (item) ->
-      if item.text and item.text.items
-        headerText = @processTextItems(item.text.items)
-        if headerText
-          level = item.size or 'size2'
-          hashes = @getHeadingLevel(level)
-          return "#{hashes} #{headerText}"
-      return ''
-    
-    # Обработка изображения
-    processImage: (item) ->
-      if item.url
-        altText = item.alt or 'Изображение товара'
-        return "![#{altText}](#{item.url})"
-      return ''
-    
-    # Обработка списка
-    processList: (item) ->
-      return '' if !item.items or !Array.isArray(item.items)
-      
-      listItems = []
-      for listItem in item.items
-        if listItem.content
-          listItems.push("- #{listItem.content}")
-      
-      return listItems.join('\n')
-    
-    # Обработка текстовых элементов
-    processTextItems: (items) ->
-      return '' if !items or !Array.isArray(items)
-      
-      textParts = []
-      for textItem in items
-        if textItem.type == 'text' and textItem.content
-          content = textItem.content
-          
-          # Обработка форматирования
-          if textItem.formatting
-            if textItem.formatting.bold
-              content = "**#{content}**"
-            if textItem.formatting.italic
-              content = "*#{content}*"
-          
-          textParts.push(content)
-        
-        else if textItem.type == 'br'
-          textParts.push('\n')
+        # Создание нового товара
+        productData._id = "product:#{productData.sku}"
+        productData.type = 'product'
+        productData.createdAt = new Date().toISOString()
+        productData.updatedAt = productData.createdAt
       
-      return textParts.join('')
+      @saveProduct(productData)
+      .then (result) =>
+        @showProductModal = false
+        @resetCurrentProduct()
+        @showNotification 'Товар сохранен'
+        @loadProducts()
+      .catch (error) =>
+        debug.log '❌ Ошибка сохранения товара:', error
+        @showNotification 'Ошибка сохранения товара', 'error'
+    
+    resetCurrentProduct: ->
+      @currentProduct = {
+        _id: ''
+        type: 'product'
+        name: ''
+        sku: ''
+        price: 0
+        oldPrice: 0
+        category: ''
+        description: ''
+        active: true
+        domains: @availableDomains
+        attributes: {}
+        images: []
+        createdAt: ''
+        updatedAt: ''
+      }
     
-    # Получение уровня заголовка Markdown
-    getHeadingLevel: (size) ->
-      switch size
-        when 'size1', 'size5' then '#'     # H1
-        when 'size2', 'size4' then '##'    # H2
-        when 'size3' then '###'            # H3
-        else '##'                          # H2 по умолчанию
+    showNotification: (message, type = 'success') ->
+      @$root.showNotification(message, type)
     
-    # Генерация slug
-    generateSlug: (text) ->
-      return '' if !text
-      text.toLowerCase()
-        .replace(/\s+/g, '-')
-        .replace(/[^\w\-]+/g, '')
-        .replace(/\-\-+/g, '-')
-        .replace(/^-+/, '')
-        .replace(/-+$/, '')
+    handleFileSelect: (event) ->
+      @selectedFile = event.target.files[0]
+      debug.log "📁 Выбран файл: #{@selectedFile?.name}"
     
-    # Массовые действия
-    toggleSelectAll: ->
-      if @selectAll
+    toggleAllProducts: (event) ->
+      if event.target.checked
         @selectedProducts = @filteredProducts.map (product) -> product._id
       else
         @selectedProducts = []
-    
-    isProductSelected: (productId) ->
-      @selectedProducts.includes(productId)
-    
-    toggleProductSelection: (productId) ->
-      if @isProductSelected(productId)
-        @selectedProducts = @selectedProducts.filter (id) -> id != productId
-      else
-        @selectedProducts.push(productId)
-    
-    clearSelection: ->
-      @selectedProducts = []
-      @selectAll = false
-    
-    toggleProductStatus: (product) ->
-      updatedProduct = Object.assign {}, product,
-        active: !product.active
-        updatedAt: new Date().toISOString()
-      
-      PouchDB.saveToRemote(updatedProduct)
-        .then (result) =>
-          @loadProducts()
-          @showNotification "Товар #{if product.active then 'деактивирован' else 'активирован'}"
-        .catch (error) =>
-          debug.log 'Ошибка изменения статуса товара:', error
-          @showNotification 'Ошибка изменения статуса товара', 'error'
-    
-    activateSelected: ->
-      if @selectedProducts.length == 0
-        @showNotification 'Выберите товары для активации', 'error'
-        return
-      
-      promises = @selectedProducts.map (productId) =>
-        product = @products.find (p) -> p._id == productId
-        if product and !product.active
-          updatedProduct = Object.assign {}, product,
-            active: true
-            updatedAt: new Date().toISOString()
-          
-          PouchDB.saveToRemote(updatedProduct)
-    
-      Promise.all(promises)
-        .then (results) =>
-          @loadProducts()
-          @clearSelection()
-          @showNotification "Активировано #{results.length} товаров"
-        .catch (error) =>
-          debug.log 'Ошибка активации товаров:', error
-          @showNotification 'Ошибка активации товаров', 'error'
-    
-    deactivateSelected: ->
-      if @selectedProducts.length == 0
-        @showNotification 'Выберите товары для деактивации', 'error'
-        return
-      
-      promises = @selectedProducts.map (productId) =>
-        product = @products.find (p) -> p._id == productId
-        if product and product.active
-          updatedProduct = Object.assign {}, product,
-            active: false
-            updatedAt: new Date().toISOString()
-          
-          PouchDB.saveToRemote(updatedProduct)
-    
-      Promise.all(promises)
-        .then (results) =>
-          @loadProducts()
-          @clearSelection()
-          @showNotification "Деактивировано #{results.length} товаров"
-        .catch (error) =>
-          debug.log 'Ошибка деактивации товаров:', error
-          @showNotification 'Ошибка деактивации товаров', 'error'
-    
-    deleteSelected: ->
-      if @selectedProducts.length == 0
-        @showNotification 'Выберите товары для удаления', 'error'
-        return
-      
-      if !confirm("Вы уверены, что хотите удалить #{@selectedProducts.length} товаров?")
-        return
-      
-      promises = @selectedProducts.map (productId) =>
-        PouchDB.getDocument(productId)
-          .then (doc) ->
-            PouchDB.saveToRemote(Object.assign {}, doc, { _deleted: true })
-    
-      Promise.all(promises)
-        .then (results) =>
-          @loadProducts()
-          @clearSelection()
-          @showNotification "Удалено #{results.length} товаров"
-        .catch (error) =>
-          debug.log 'Ошибка удаления товаров:', error
-          @showNotification 'Ошибка удаления товаров', 'error'
-    
-    showNotification: (message, type = 'success') ->
-      if @$root.showNotification?
-        @$root.showNotification(message, type)
-      else
-        debug.log("#{type}: #{message}")
-  
-  mounted: ->
-    @loadProducts()
-    @loadCategories()
-    @loadDomains()

+ 159 - 475
app/pages/Admin/Products/index.pug

@@ -1,531 +1,215 @@
-div(class="admin-products")
-  div(class="admin-products__header")
-    h1(class="admin-products__title") Управление товарами
-    div(class="admin-products__actions")
-      button(
-        @click="createProduct"
-        class="admin-products__button admin-products__button--primary"
+.admin-products
+  .admin-products__header
+    h1.admin-products__title Управление товарами
+    .admin-products__actions
+      button.admin-products__btn.admin-products__btn--primary(
+        @click="showProductModal = true"
       ) Добавить товар
-      button(
+      button.admin-products__btn.admin-products__btn--secondary(
         @click="showImportModal = true"
-        class="admin-products__button admin-products__button--secondary"
       ) Импорт из CSV
-      button(
-        @click="showCategoriesManager"
-        class="admin-products__button admin-products__button--secondary"
-      ) Управление категориями
-      button(
-        v-if="selectedProducts.length > 0"
-        @click="showMassActionsModal = true"
-        class="admin-products__button admin-products__button--warning"
-      ) Массовые действия ({{ selectedProducts.length }})
 
-  div(class="admin-products__filters")
-    div(class="admin-products__search")
-      input(
+  .admin-products__filters
+    .admin-products__search
+      input.admin-products__search-input(
         type="text"
-        v-model="searchQuery"
         placeholder="Поиск по названию или артикулу..."
-        class="admin-products__search-input"
+        v-model="searchQuery"
       )
-    div(class="admin-products__filter-group")
-      select(v-model="selectedCategory" class="admin-products__select")
+    .admin-products__category-filter
+      select.admin-products__category-select(v-model="selectedCategory")
         option(value="") Все категории
         option(
           v-for="category in categories"
-          :value="category._id"
-        ) {{ category.name }} ({{ getCategoryProductCount(category._id) }})
-      select(v-model="selectedStatus" class="admin-products__select")
-        option(value="") Все статусы
-        option(value="active") Активные
-        option(value="inactive") Неактивные
+          :value="category.slug"
+        ) {{ category.name }}
 
-  div(class="admin-products__content")
-    div(class="admin-products__table-container")
-      table(class="admin-products__table")
+  .admin-products__content
+    .admin-products__table-container
+      table.admin-products__table
         thead
           tr
-            th(class="admin-products__th admin-products__th--checkbox")
+            th.admin-products__th-checkbox
               input(
                 type="checkbox"
-                v-model="selectAll"
-                @change="toggleSelectAll"
-                class="admin-products__checkbox"
-              )
-            th(class="admin-products__th") Артикул
-            th(class="admin-products__th") Название
-            th(class="admin-products__th") Категория
-            th(class="admin-products__th") Цена
-            th(class="admin-products__th") Статус
-            th(class="admin-products__th") Действия
+                @change="toggleAllProducts"
+                :checked="selectedProducts.length === filteredProducts.length"
+              )
+            th.admin-products__th Название
+            th.admin-products__th Артикул
+            th.admin-products__th Категория
+            th.admin-products__th Цена
+            th.admin-products__th Статус
+            th.admin-products__th Действия
         tbody
-          tr(
+          tr.admin-products__tr(
             v-for="product in filteredProducts"
             :key="product._id"
-            :class="['admin-products__tr', { 'admin-products__tr--inactive': !product.active }]"
           )
-            td(class="admin-products__td admin-products__td--checkbox")
+            td.admin-products__td-checkbox
               input(
                 type="checkbox"
-                :checked="isProductSelected(product._id)"
-                @change="toggleProductSelection(product._id)"
-                class="admin-products__checkbox"
-              )
-            td(class="admin-products__td admin-products__td--sku") {{ product.sku }}
-            td(class="admin-products__td admin-products__td--name") {{ product.name }}
-            td(class="admin-products__td") {{ getCategoryName(product.category) }}
-            td(class="admin-products__td admin-products__td--price")
-              span(v-if="product.oldPrice" class="admin-products__old-price") {{ product.oldPrice }} ₽
-              span(class="admin-products__current-price") {{ product.price }} ₽
-            td(class="admin-products__td")
-              span(
-                :class="['admin-products__status', product.active ? 'admin-products__status--active' : 'admin-products__status--inactive']"
+                :value="product._id"
+                v-model="selectedProducts"
+              )
+            td.admin-products__td {{ product.name }}
+            td.admin-products__td {{ product.sku }}
+            td.admin-products__td {{ product.category }}
+            td.admin-products__td {{ product.price }} ₽
+            td.admin-products__td
+              span.admin-products__status(
+                :class="{'admin-products__status--active': product.active, 'admin-products__status--inactive': !product.active}"
               ) {{ product.active ? 'Активен' : 'Неактивен' }}
-            td(class="admin-products__td admin-products__td--actions")
-              button(
-                @click="editProduct(product)"
-                class="admin-products__action-button admin-products__action-button--edit"
-              ) Редактировать
-              button(
-                @click="toggleProductStatus(product)"
-                :class="['admin-products__action-button', product.active ? 'admin-products__action-button--deactivate' : 'admin-products__action-button--activate']"
-              ) {{ product.active ? 'Деактивировать' : 'Активировать' }}
+            td.admin-products__td
+              .admin-products__actions
+                button.admin-products__action-btn.admin-products__action-btn--edit(
+                  @click="editProduct(product)"
+                ) Редактировать
+                button.admin-products__action-btn.admin-products__action-btn--delete(
+                  @click="deleteProduct(product)"
+                ) Удалить
 
-    div(v-if="filteredProducts.length === 0" class="admin-products__empty")
-      p(class="admin-products__empty-text") Товары не найдены
+    .admin-products__empty(v-if="filteredProducts.length === 0")
+      p.admin-products__empty-text Товары не найдены
 
-  //- Модальное окно редактирования/создания товара
-  div(
-    v-if="showProductModal"
-    class="admin-products__modal"
-  )
-    div(class="admin-products__modal-content admin-products__modal-content--large")
-      div(class="admin-products__modal-header")
-        h2(class="admin-products__modal-title") {{ isEditing ? 'Редактирование товара' : 'Создание товара' }}
-        button(
-          @click="showProductModal = false"
-          class="admin-products__modal-close"
-        ) ×
+  //- Модальное окно товара
+  .admin-modal(v-if="showProductModal")
+    .admin-modal__overlay(@click="showProductModal = false")
+    .admin-modal__content
+      .admin-modal__header
+        h2.admin-modal__title {{ currentProduct._id ? 'Редактировать товар' : 'Добавить товар' }}
+        button.admin-modal__close(@click="showProductModal = false") ×
       
-      div(class="admin-products__modal-body")
-        div(class="admin-products__form")
-          div(class="admin-products__form-row")
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Название товара *
-              input(
-                type="text"
-                v-model="productForm.name"
-                class="admin-products__input"
-                placeholder="Введите название товара"
-              )
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Артикул *
-              input(
-                type="text"
-                v-model="productForm.sku"
-                class="admin-products__input"
-                placeholder="Введите артикул"
-              )
+      .admin-modal__body
+        .admin-form
+          .admin-form__group
+            label.admin-form__label Название товара *
+            input.admin-form__input(
+              type="text"
+              v-model="currentProduct.name"
+              placeholder="Введите название товара"
+            )
           
-          div(class="admin-products__form-row")
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Категория
-              select(v-model="productForm.category" class="admin-products__select")
-                option(value="") Без категории
-                option(
-                  v-for="category in categories"
-                  :value="category._id"
-                ) {{ category.name }}
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Бренд
-              input(
-                type="text"
-                v-model="productForm.brand"
-                class="admin-products__input"
-                placeholder="Введите бренд"
-              )
+          .admin-form__group
+            label.admin-form__label Артикул *
+            input.admin-form__input(
+              type="text"
+              v-model="currentProduct.sku"
+              placeholder="Введите артикул"
+            )
           
-          div(class="admin-products__form-row")
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Цена *
-              input(
+          .admin-form__group
+            label.admin-form__label Категория
+            select.admin-form__select(v-model="currentProduct.category")
+              option(value="") Выберите категорию
+              option(
+                v-for="category in categories"
+                :value="category.slug"
+              ) {{ category.name }}
+          
+          .admin-form__row
+            .admin-form__group
+              label.admin-form__label Цена *
+              input.admin-form__input(
                 type="number"
-                v-model="productForm.price"
-                class="admin-products__input"
+                v-model.number="currentProduct.price"
                 placeholder="0.00"
                 step="0.01"
-                min="0"
               )
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Старая цена
-              input(
+            
+            .admin-form__group
+              label.admin-form__label Старая цена
+              input.admin-form__input(
                 type="number"
-                v-model="productForm.oldPrice"
-                class="admin-products__input"
+                v-model.number="currentProduct.oldPrice"
                 placeholder="0.00"
                 step="0.01"
-                min="0"
               )
           
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Описание
-            textarea(
-              v-model="productForm.description"
-              class="admin-products__textarea"
+          .admin-form__group
+            label.admin-form__label Описание
+            textarea.admin-form__textarea(
+              v-model="currentProduct.description"
               placeholder="Введите описание товара"
               rows="4"
             )
           
-          //- Управление атрибутами
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Атрибуты
-            div(class="admin-products__attributes")
-              div(
-                v-for="attr in attributesList"
-                :key="attr.key"
-                class="admin-products__attribute"
-              )
-                div(class="admin-products__attribute-key") {{ attr.key }}
-                input(
-                  type="text"
-                  :value="attr.value"
-                  @input="updateAttribute(attr.key, $event.target.value)"
-                  class="admin-products__input admin-products__input--small"
-                  placeholder="Значение"
-                )
-                button(
-                  @click="removeAttribute(attr.key)"
-                  class="admin-products__attribute-remove"
-                ) ×
-            
-            div(class="admin-products__attribute-add")
-              input(
-                type="text"
-                v-model="newAttributeKey"
-                class="admin-products__input admin-products__input--small"
-                placeholder="Название атрибута"
-                @keyup.enter="addAttribute"
-              )
-              input(
-                type="text"
-                v-model="newAttributeValue"
-                class="admin-products__input admin-products__input--small"
-                placeholder="Значение"
-                @keyup.enter="addAttribute"
-              )
-              button(
-                @click="addAttribute"
-                class="admin-products__button admin-products__button--secondary"
-              ) Добавить
-          
-          //- Управление тегами
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Теги
-            div(class="admin-products__tags")
-              span(
-                v-for="(tag, index) in productForm.tags"
-                :key="index"
-                class="admin-products__tag"
-              )
-                | {{ tag }}
-                button(
-                  @click="removeTag(index)"
-                  class="admin-products__tag-remove"
-                ) ×
-            
-            div(class="admin-products__tag-add")
-              input(
-                type="text"
-                v-model="newTag"
-                class="admin-products__input admin-products__input--small"
-                placeholder="Новый тег"
-                @keyup.enter="addTag"
-              )
-              button(
-                @click="addTag"
-                class="admin-products__button admin-products__button--secondary"
-              ) Добавить
-          
-          //- Загрузка основного изображения
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Основное изображение
-            div(class="admin-products__image-upload")
-              div(v-if="productForm.image" class="admin-products__image-preview")
-                img(:src="productForm.image" class="admin-products__preview-img")
-                div(class="admin-products__image-info") Основное изображение
-              div(class="admin-products__upload-controls")
-                input(
-                  type="text"
-                  v-model="newImageUrl"
-                  class="admin-products__input"
-                  placeholder="Введите URL изображения"
-                )
-                button(
-                  @click="uploadMainImage"
-                  :disabled="uploadingImages || !newImageUrl"
-                  class="admin-products__button admin-products__button--secondary"
-                ) {{ uploadingImages ? 'Загрузка...' : 'Загрузить' }}
-          
-          //- Загрузка дополнительных изображений
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Дополнительные изображения
-            div(class="admin-products__additional-images")
-              div(
-                v-for="(image, index) in productForm.additionalImages"
-                :key="index"
-                class="admin-products__additional-image"
-              )
-                img(:src="image" class="admin-products__preview-img")
-                button(
-                  @click="removeAdditionalImage(index)"
-                  class="admin-products__image-remove"
-                ) ×
-            
-            div(class="admin-products__upload-controls")
-              input(
-                type="text"
-                v-model="newAdditionalImageUrl"
-                class="admin-products__input"
-                placeholder="Введите URL дополнительного изображения"
+          .admin-form__group
+            label.admin-form__label
+              input.admin-form__checkbox(
+                type="checkbox"
+                v-model="currentProduct.active"
               )
-              button(
-                @click="uploadAdditionalImage"
-                :disabled="uploadingImages || !newAdditionalImageUrl"
-                class="admin-products__button admin-products__button--secondary"
-              ) {{ uploadingImages ? 'Загрузка...' : 'Добавить' }}
-          
-          div(class="admin-products__form-row")
-            div(class="admin-products__form-group")
-              label(class="admin-products__label admin-products__label--checkbox")
-                input(
-                  type="checkbox"
-                  v-model="productForm.active"
-                  class="admin-products__checkbox"
-                )
-                span Активный товар
+              span Активный товар
       
-      div(class="admin-products__modal-footer")
-        button(
-          @click="saveProduct"
-          :disabled="!productForm.name || !productForm.sku || !productForm.price"
-          class="admin-products__button admin-products__button--primary"
-        ) {{ isEditing ? 'Обновить' : 'Создать' }}
-        button(
+      .admin-modal__footer
+        button.admin-modal__btn.admin-modal__btn--secondary(
           @click="showProductModal = false"
-          class="admin-products__button admin-products__button--secondary"
         ) Отмена
+        button.admin-modal__btn.admin-modal__btn--primary(
+          @click="saveProductForm"
+        ) Сохранить
 
-  //- Модальное окно управления категориями
-  div(
-    v-if="showCategoriesModal"
-    class="admin-products__modal"
-  )
-    div(class="admin-products__modal-content admin-products__modal-content--large")
-      div(class="admin-products__modal-header")
-        h2(class="admin-products__modal-title") Управление категориями
-        button(
-          @click="showCategoriesModal = false"
-          class="admin-products__modal-close"
-        ) ×
-      
-      div(class="admin-products__modal-body")
-        div(class="admin-products__categories-header")
-          button(
-            @click="createCategory"
-            class="admin-products__button admin-products__button--primary"
-          ) Создать категорию
-        
-        div(class="admin-products__categories-list")
-          div(
-            v-for="category in categories"
-            :key="category._id"
-            class="admin-products__category-item"
-          )
-            div(class="admin-products__category-info")
-              div(class="admin-products__category-name") {{ category.name }}
-              div(class="admin-products__category-meta")
-                span Товаров: {{ getCategoryProductCount(category._id) }}
-                span(v-if="category.slug") Slug: {{ category.slug }}
-            div(class="admin-products__category-actions")
-              button(
-                @click="editCategory(category)"
-                class="admin-products__action-button admin-products__action-button--edit"
-              ) Редактировать
-              button(
-                @click="deleteCategory(category)"
-                class="admin-products__action-button admin-products__action-button--danger"
-              ) Удалить
-
-  //- Модальное окно редактирования/создания категории
-  div(
-    v-if="showCategoryModal"
-    class="admin-products__modal"
-  )
-    div(class="admin-products__modal-content")
-      div(class="admin-products__modal-header")
-        h2(class="admin-products__modal-title") {{ editingCategory ? 'Редактирование категории' : 'Создание категории' }}
-        button(
-          @click="showCategoryModal = false"
-          class="admin-products__modal-close"
-        ) ×
+  //- Модальное окно импорта
+  .admin-modal(v-if="showImportModal")
+    .admin-modal__overlay(@click="showImportModal = false")
+    .admin-modal__content
+      .admin-modal__header
+        h2.admin-modal__title Импорт товаров из CSV
+        button.admin-modal__close(@click="showImportModal = false") ×
       
-      div(class="admin-products__modal-body")
-        div(class="admin-products__form")
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Название категории *
-            input(
-              type="text"
-              v-model="categoryForm.name"
-              class="admin-products__input"
-              placeholder="Введите название категории"
+      .admin-modal__body
+        .admin-import
+          .admin-import__upload
+            input.admin-import__file-input(
+              type="file"
+              accept=".csv"
+              @change="handleFileSelect"
             )
-          
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Slug
-            input(
-              type="text"
-              v-model="categoryForm.slug"
-              class="admin-products__input"
-              placeholder="Автоматически сгенерируется из названия"
+            .admin-import__upload-area(
+              :class="{'admin-import__upload-area--dragover': dragOver}"
+              @drop="handleDrop"
+              @dragover="handleDragOver"
+              @dragleave="dragOver = false"
             )
+              p.admin-import__upload-text
+                span Выберите CSV файл
+                | или перетащите его сюда
           
-          div(class="admin-products__form-group")
-            label(class="admin-products__label") Описание
-            textarea(
-              v-model="categoryForm.description"
-              class="admin-products__textarea"
-              placeholder="Введите описание категории"
-              rows="3"
-            )
+          .admin-import__info(v-if="selectedFile")
+            .admin-import__file-info
+              span.admin-import__file-name {{ selectedFile.name }}
+              span.admin-import__file-size ({{ (selectedFile.size / 1024).toFixed(2) }} KB)
           
-          div(class="admin-products__form-row")
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Родительская категория
-              select(v-model="categoryForm.parentCategory" class="admin-products__select")
-                option(value="") Нет
-                option(
-                  v-for="category in categories"
-                  :value="category._id"
-                  v-if="category._id != editingCategory?._id"
-                ) {{ category.name }}
-            
-            div(class="admin-products__form-group")
-              label(class="admin-products__label") Порядок сортировки
-              input(
-                type="number"
-                v-model="categoryForm.sortOrder"
-                class="admin-products__input"
-                placeholder="0"
-              )
+          .admin-import__progress(v-if="importing")
+            .admin-import__progress-bar
+              .admin-import__progress-fill(:style="{width: importProgress + '%'}")
+            .admin-import__progress-text {{ importProgress }}%
           
-          div(class="admin-products__form-group")
-            label(class="admin-products__label admin-products__label--checkbox")
-              input(
-                type="checkbox"
-                v-model="categoryForm.active"
-                class="admin-products__checkbox"
-              )
-              span Активная категория
-      
-      div(class="admin-products__modal-footer")
-        button(
-          @click="saveCategory"
-          :disabled="!categoryForm.name"
-          class="admin-products__button admin-products__button--primary"
-        ) {{ editingCategory ? 'Обновить' : 'Создать' }}
-        button(
-          @click="showCategoryModal = false"
-          class="admin-products__button admin-products__button--secondary"
-        ) Отмена
-
-  //- Модальное окно импорта
-  div(
-    v-if="showImportModal"
-    class="admin-products__modal"
-  )
-    div(class="admin-products__modal-content")
-      div(class="admin-products__modal-header")
-        h2(class="admin-products__modal-title") Импорт товаров из CSV
-        button(
-          @click="showImportModal = false"
-          class="admin-products__modal-close"
-        ) ×
-      
-      div(class="admin-products__modal-body")
-        div(v-if="importing" class="admin-products__import-progress")
-          div(class="admin-products__progress-bar")
-            div(
-              :style="{ width: importProgress + '%' }"
-              class="admin-products__progress-fill"
+          .admin-import__results(v-if="importResults")
+            .admin-import__result(
+              :class="{'admin-import__result--success': importResults.success, 'admin-import__result--error': !importResults.success}"
             )
-          p(class="admin-products__progress-text") Обработано: {{ processedCount }} из {{ totalCount }} ({{ importProgress }}%)
-        
-        div(v-else class="admin-products__import-form")
-          input(
-            type="file"
-            @change="onFileSelect"
-            accept=".csv"
-            class="admin-products__file-input"
-          )
-          p(class="admin-products__help-text") Поддерживается формат CSV с разделителем ";" и кодировкой UTF-8
-        
-        div(v-if="importResults" class="admin-products__import-results")
-          div(
-            v-if="importResults.success"
-            class="admin-products__result admin-products__result--success"
-          )
-            h3 Успешно импортировано!
-            p Обработано товаров: {{ importResults.processed }} из {{ importResults.total }}
-            ul(v-if="importResults.errors.length > 0")
-              li(v-for="error in importResults.errors") {{ error }}
-          div(
-            v-else
-            class="admin-products__result admin-products__result--error"
-          )
-            h3 Ошибка импорта!
-            p {{ importResults.error }}
+              h4.admin-import__result-title {{ importResults.success ? 'Импорт завершен' : 'Ошибка импорта' }}
+              p.admin-import__result-text
+                | Обработано: {{ importResults.processed }},
+                | Успешно: {{ importResults.successful }},
+                | С ошибками: {{ importResults.failed }}
+              
+              .admin-import__errors(v-if="importResults.errors && importResults.errors.length > 0")
+                h5.admin-import__errors-title Ошибки:
+                ul.admin-import__errors-list
+                  li.admin-import__error(
+                    v-for="error in importResults.errors"
+                    :key="error"
+                  ) {{ error }}
       
-      div(class="admin-products__modal-footer")
-        button(
+      .admin-modal__footer
+        button.admin-modal__btn.admin-modal__btn--secondary(
+          @click="showImportModal = false"
+          :disabled="importing"
+        ) Отмена
+        button.admin-modal__btn.admin-modal__btn--primary(
           @click="importProducts"
           :disabled="!selectedFile || importing"
-          class="admin-products__button admin-products__button--primary"
         ) {{ importing ? 'Импорт...' : 'Начать импорт' }}
-        button(
-          @click="showImportModal = false"
-          class="admin-products__button admin-products__button--secondary"
-        ) Отмена
-
-  //- Модальное окно массовых действий
-  div(
-    v-if="showMassActionsModal"
-    class="admin-products__modal"
-  )
-    div(class="admin-products__modal-content")
-      div(class="admin-products__modal-header")
-        h2(class="admin-products__modal-title") Массовые действия ({{ selectedProducts.length }} товаров)
-        button(
-          @click="showMassActionsModal = false"
-          class="admin-products__modal-close"
-        ) ×
-      
-      div(class="admin-products__modal-body")
-        div(class="admin-products__mass-actions")
-          button(
-            @click="activateSelected"
-            class="admin-products__button admin-products__button--success"
-          ) Активировать
-          button(
-            @click="deactivateSelected"
-            class="admin-products__button admin-products__button--warning"
-          ) Деактивировать
-          button(
-            @click="deleteSelected"
-            class="admin-products__button admin-products__button--danger"
-          ) Удалить
-      
-      div(class="admin-products__modal-footer")
-        button(
-          @click="showMassActionsModal = false"
-          class="admin-products__button admin-products__button--secondary"
-        ) Закрыть

+ 266 - 494
app/pages/Admin/Products/index.styl

@@ -8,8 +8,8 @@
     justify-content: space-between
     align-items: center
     margin-bottom: 24px
-    padding: 24px
     background: white
+    padding: 20px
     border-radius: 8px
     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1)
 
@@ -23,100 +23,68 @@
     display: flex
     gap: 12px
 
-  &__button
-    padding: 10px 16px
+  &__btn
+    padding: 10px 20px
     border: none
     border-radius: 6px
     font-size: 14px
     font-weight: 500
     cursor: pointer
     transition: all 0.2s ease
-    text-decoration: none
-    display: inline-flex
-    align-items: center
-    gap: 8px
-
+    
     &--primary
       background: #3b82f6
       color: white
-
+      
       &:hover
         background: #2563eb
-
+    
     &--secondary
       background: #6b7280
       color: white
-
+      
       &:hover
         background: #4b5563
 
-    &--success
-      background: #10b981
-      color: white
-
-      &:hover
-        background: #059669
-
-    &--warning
-      background: #f59e0b
-      color: white
-
-      &:hover
-        background: #d97706
-
-    &--danger
-      background: #ef4444
-      color: white
-
-      &:hover
-        background: #dc2626
-
-    &:disabled
-      opacity: 0.6
-      cursor: not-allowed
-
   &__filters
     display: flex
     gap: 16px
-    margin-bottom: 24px
-    padding: 20px
+    margin-bottom: 20px
     background: white
+    padding: 20px
     border-radius: 8px
     box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1)
 
   &__search
     flex: 1
-
+    
     &-input
       width: 100%
       padding: 10px 12px
       border: 1px solid #d1d5db
       border-radius: 6px
       font-size: 14px
-      transition: border-color 0.2s ease
-
+      
       &:focus
         outline: none
         border-color: #3b82f6
         box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
 
-  &__filter-group
-    display: flex
-    gap: 12px
-
-  &__select
-    padding: 10px 12px
-    border: 1px solid #d1d5db
-    border-radius: 6px
-    font-size: 14px
-    background: white
-    cursor: pointer
-    transition: border-color 0.2s ease
-
-    &:focus
-      outline: none
-      border-color: #3b82f6
-      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
+  &__category-filter
+    min-width: 200px
+    
+    &-select
+      width: 100%
+      padding: 10px 12px
+      border: 1px solid #d1d5db
+      border-radius: 6px
+      font-size: 14px
+      background: white
+      
+      &:focus
+        outline: none
+        border-color: #3b82f6
+        box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
 
   &__content
     background: white
@@ -131,80 +99,50 @@
     width: 100%
     border-collapse: collapse
 
-  &__th
-    padding: 16px
+  &__th, &__td
+    padding: 12px 16px
     text-align: left
-    font-weight: 600
-    color: #374151
     border-bottom: 1px solid #e5e7eb
-    background: #f9fafb
+    font-size: 14px
 
-    &--checkbox
+  &__th
+    background: #f9fafb
+    font-weight: 600
+    color: #374151
+    
+    &-checkbox
       width: 40px
 
   &__tr
-    border-bottom: 1px solid #e5e7eb
     transition: background-color 0.2s ease
-
+    
     &:hover
       background: #f9fafb
 
-    &--inactive
-      opacity: 0.6
-
   &__td
-    padding: 16px
-    border-bottom: 1px solid #e5e7eb
-
-    &--checkbox
+    &-checkbox
       width: 40px
-
-    &--sku
-      font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace
-      font-size: 12px
-      color: #6b7280
-
-    &--name
-      font-weight: 500
-      color: #1f2937
-
-    &--price
-      text-align: right
-
-    &--actions
-      display: flex
-      gap: 8px
-
-  &__checkbox
-    width: 16px
-    height: 16px
-    cursor: pointer
-
-  &__old-price
-    text-decoration: line-through
-    color: #6b7280
-    font-size: 12px
-    margin-right: 8px
-
-  &__current-price
-    font-weight: 600
-    color: #1f2937
+      text-align: center
 
   &__status
     padding: 4px 8px
     border-radius: 12px
     font-size: 12px
     font-weight: 500
-
+    
     &--active
-      background: #d1fae5
-      color: #065f46
-
+      background: #dcfce7
+      color: #166534
+    
     &--inactive
-      background: #fee2e2
+      background: #fef2f2
       color: #991b1b
 
-  &__action-button
+  &__actions
+    display: flex
+    gap: 8px
+
+  &__action-btn
     padding: 6px 12px
     border: none
     border-radius: 4px
@@ -212,71 +150,73 @@
     font-weight: 500
     cursor: pointer
     transition: all 0.2s ease
-
+    
     &--edit
       background: #dbeafe
       color: #1e40af
-
+      
       &:hover
         background: #bfdbfe
-
-    &--activate
-      background: #d1fae5
-      color: #065f46
-
-      &:hover
-        background: #a7f3d0
-
-    &--deactivate
-      background: #fef3c7
-      color: #92400e
-
+    
+    &--delete
+      background: #fef2f2
+      color: #dc2626
+      
       &:hover
-        background: #fde68a
+        background: #fecaca
 
   &__empty
-    padding: 60px 20px
+    padding: 40px 20px
     text-align: center
-
+    
     &-text
       color: #6b7280
       font-size: 16px
-      margin: 0
 
-  &__modal
-    position: fixed
+// Модальные окна
+.admin-modal
+  position: fixed
+  top: 0
+  left: 0
+  right: 0
+  bottom: 0
+  z-index: 1000
+  display: flex
+  align-items: center
+  justify-content: center
+  
+  &__overlay
+    position: absolute
     top: 0
     left: 0
     right: 0
     bottom: 0
     background: rgba(0, 0, 0, 0.5)
-    display: flex
-    align-items: center
-    justify-content: center
-    z-index: 1000
-
-  &__modal-content
+  
+  &__content
+    position: relative
     background: white
     border-radius: 8px
-    width: 90%
+    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2)
     max-width: 600px
+    width: 90%
     max-height: 90vh
     overflow-y: auto
-
-  &__modal-header
+  
+  &__header
     display: flex
     justify-content: space-between
     align-items: center
-    padding: 24px
+    padding: 20px 24px
     border-bottom: 1px solid #e5e7eb
-
-  &__modal-title
-    font-size: 20px
+  
+  &__title
+    font-size: 18px
     font-weight: 600
     color: #1f2937
     margin: 0
-
-  &__modal-close
+  
+  &__close
     background: none
     border: none
     font-size: 24px
@@ -288,393 +228,225 @@
     display: flex
     align-items: center
     justify-content: center
-
+    
     &:hover
       color: #374151
-
-  &__modal-body
+  
+  &__body
     padding: 24px
-
-  &__modal-footer
+  
+  &__footer
     display: flex
     justify-content: flex-end
     gap: 12px
-    padding: 24px
+    padding: 20px 24px
     border-top: 1px solid #e5e7eb
+  
+  &__btn
+    padding: 10px 20px
+    border: none
+    border-radius: 6px
+    font-size: 14px
+    font-weight: 500
+    cursor: pointer
+    transition: all 0.2s ease
+    
+    &:disabled
+      opacity: 0.6
+      cursor: not-allowed
+    
+    &--primary
+      background: #3b82f6
+      color: white
+      
+      &:hover:not(:disabled)
+        background: #2563eb
+    
+    &--secondary
+      background: #6b7280
+      color: white
+      
+      &:hover:not(:disabled)
+        background: #4b5563
 
-  &__import-progress
-    text-align: center
+// Формы
+.admin-form
+  &__group
+    margin-bottom: 20px
+  
+  &__label
+    display: block
+    margin-bottom: 6px
+    font-weight: 500
+    color: #374151
+    font-size: 14px
+  
+  &__input, &__select, &__textarea
+    width: 100%
+    padding: 10px 12px
+    border: 1px solid #d1d5db
+    border-radius: 6px
+    font-size: 14px
+    transition: all 0.2s ease
+    
+    &:focus
+      outline: none
+      border-color: #3b82f6
+      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
+  
+  &__textarea
+    resize: vertical
+    min-height: 80px
+  
+  &__checkbox
+    margin-right: 8px
+  
+  &__row
+    display: grid
+    grid-template-columns: 1fr 1fr
+    gap: 16px
 
+// Импорт
+.admin-import
+  &__upload
+    margin-bottom: 20px
+  
+  &__file-input
+    display: none
+  
+  &__upload-area
+    border: 2px dashed #d1d5db
+    border-radius: 8px
+    padding: 40px 20px
+    text-align: center
+    cursor: pointer
+    transition: all 0.2s ease
+    
+    &:hover, &--dragover
+      border-color: #3b82f6
+      background: #f0f9ff
+  
+  &__upload-text
+    color: #6b7280
+    font-size: 16px
+    
+    span
+      color: #3b82f6
+      text-decoration: underline
+  
+  &__file-info
+    display: flex
+    justify-content: space-between
+    align-items: center
+    background: #f3f4f6
+    padding: 12px 16px
+    border-radius: 6px
+    margin-top: 12px
+  
+  &__file-name
+    font-weight: 500
+    color: #374151
+  
+  &__file-size
+    color: #6b7280
+    font-size: 14px
+  
+  &__progress
+    margin: 20px 0
+  
   &__progress-bar
-    width: 100%
     height: 8px
     background: #e5e7eb
     border-radius: 4px
     overflow: hidden
-    margin-bottom: 12px
-
+  
   &__progress-fill
     height: 100%
-    background: #3b82f6
+    background: #10b981
     transition: width 0.3s ease
-
+  
   &__progress-text
-    color: #6b7280
-    font-size: 14px
-    margin: 0
-
-  &__import-form
     text-align: center
-
-  &__file-input
-    width: 100%
-    padding: 12px
-    border: 2px dashed #d1d5db
-    border-radius: 6px
-    margin-bottom: 12px
-
-    &:focus
-      outline: none
-      border-color: #3b82f6
-
-  &__help-text
-    color: #6b7280
+    margin-top: 8px
     font-size: 14px
-    margin: 0
-
-  &__import-results
+    color: #6b7280
+    font-weight: 500
+  
+  &__results
     margin-top: 20px
-
+  
   &__result
     padding: 16px
     border-radius: 6px
-
+    
     &--success
-      background: #d1fae5
-      border: 1px solid #a7f3d0
-
+      background: #f0fdf4
+      border: 1px solid #bbf7d0
+    
     &--error
-      background: #fee2e2
+      background: #fef2f2
       border: 1px solid #fecaca
-
-    h3
-      margin: 0 0 8px 0
-      font-size: 16px
+  
+  &__result-title
+    font-size: 16px
+    font-weight: 600
+    margin: 0 0 8px 0
+    
+    .admin-import__result--success &
+      color: #166534
+    
+    .admin-import__result--error &
+      color: #991b1b
+  
+  &__result-text
+    margin: 0 0 12px 0
+    color: #6b7280
+    font-size: 14px
+  
+  &__errors
+    &-title
+      font-size: 14px
       font-weight: 600
-
-    p
       margin: 0 0 8px 0
-      font-size: 14px
-
-    ul
-      margin: 8px 0 0 0
+      color: #374151
+    
+    &-list
+      margin: 0
       padding-left: 20px
+  
+  &__error
+    color: #dc2626
+    font-size: 14px
+    margin-bottom: 4px
 
-      li
-        font-size: 12px
-        color: #6b7280
-
-  &__mass-actions
-    display: grid
-    grid-template-columns: 1fr 1fr
-    gap: 12px
-
-// Адаптивные стили
+// Адаптивность
 @media (max-width: 768px)
   .admin-products
     padding: 12px
-
+    
     &__header
       flex-direction: column
       gap: 16px
       align-items: stretch
-
+    
     &__actions
-      flex-wrap: wrap
-      justify-content: center
-
+      justify-content: stretch
+      
+      .admin-products__btn
+        flex: 1
+    
     &__filters
       flex-direction: column
-
-    &__filter-group
-      flex-direction: column
-
-    &__td--actions
-      flex-direction: column
-
-    &__mass-actions
-      grid-template-columns: 1fr
-
-    &__modal-content
-      width: 95%
-      margin: 20px
-
-    &__modal-footer
-      flex-direction: column
-// ... предыдущие стилы остаются без изменений ...
-
-// Стили для формы редактирования
-.admin-products
-  &__modal-content--large
-    max-width: 800px
-
-  &__form
-    display: flex
-    flex-direction: column
-    gap: 16px
-
-  &__form-row
-    display: grid
-    grid-template-columns: 1fr 1fr
-    gap: 16px
-
-    @media (max-width: 768px)
-      grid-template-columns: 1fr
-
-  &__form-group
-    display: flex
-    flex-direction: column
-    gap: 8px
-
-  &__label
-    font-weight: 500
-    color: #374151
-    font-size: 14px
-
-    &--checkbox
-      display: flex
-      align-items: center
-      gap: 8px
-      cursor: pointer
-
-  &__input
-    padding: 10px 12px
-    border: 1px solid #d1d5db
-    border-radius: 6px
-    font-size: 14px
-    transition: border-color 0.2s ease
-
-    &:focus
-      outline: none
-      border-color: #3b82f6
-      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
-
-  &__textarea
-    padding: 10px 12px
-    border: 1px solid #d1d5db
-    border-radius: 6px
-    font-size: 14px
-    resize: vertical
-    min-height: 80px
-    transition: border-color 0.2s ease
-
-    &:focus
-      outline: none
-      border-color: #3b82f6
-      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
-
-  // Стили для загрузки изображений
-  &__image-upload
-    display: flex
-    flex-direction: column
-    gap: 12px
-
-  &__image-preview
-    display: flex
-    align-items: center
-    gap: 12px
-    padding: 12px
-    border: 1px solid #e5e7eb
-    border-radius: 6px
-    background: #f9fafb
-
-  &__preview-img
-    width: 60px
-    height: 60px
-    object-fit: cover
-    border-radius: 4px
-
-  &__image-info
-    color: #6b7280
-    font-size: 14px
-
-  &__upload-controls
-    display: flex
-    gap: 8px
-
-    @media (max-width: 768px)
-      flex-direction: column
-
-  &__additional-images
-    display: grid
-    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr))
-    gap: 8px
-    margin-bottom: 12px
-
-  &__additional-image
-    position: relative
-    width: 80px
-    height: 80px
-
-  &__image-remove
-    position: absolute
-    top: -8px
-    right: -8px
-    width: 24px
-    height: 24px
-    background: #ef4444
-    color: white
-    border: none
-    border-radius: 50%
-    cursor: pointer
-    display: flex
-    align-items: center
-    justify-content: center
-    font-size: 14px
-    line-height: 1
-
-    &:hover
-      background: #dc2626
-// ... предыдущие стили остаются без изменений ...
-
-// Стили для управления атрибутами
-.admin-products
-  &__attributes
-    display: flex
-    flex-direction: column
-    gap: 8px
-    margin-bottom: 12px
-
-  &__attribute
-    display: flex
-    align-items: center
-    gap: 8px
-    padding: 8px
-    border: 1px solid #e5e7eb
-    border-radius: 4px
-    background: #f9fafb
-
-  &__attribute-key
-    flex: 1
-    font-weight: 500
-    color: #374151
-    font-size: 14px
-
-  &__input--small
-    flex: 2
-    padding: 6px 8px
-    font-size: 13px
-
-  &__attribute-remove
-    width: 24px
-    height: 24px
-    background: #ef4444
-    color: white
-    border: none
-    border-radius: 4px
-    cursor: pointer
-    display: flex
-    align-items: center
-    justify-content: center
-    font-size: 14px
-    line-height: 1
-
-    &:hover
-      background: #dc2626
-
-  &__attribute-add
-    display: flex
-    gap: 8px
-    align-items: center
-
-    @media (max-width: 768px)
-      flex-direction: column
-
-// Стили для управления тегами
-.admin-products
-  &__tags
-    display: flex
-    flex-wrap: wrap
-    gap: 6px
-    margin-bottom: 12px
-
-  &__tag
-    display: inline-flex
-    align-items: center
-    gap: 4px
-    padding: 4px 8px
-    background: #dbeafe
-    color: #1e40af
-    border-radius: 12px
-    font-size: 12px
-    font-weight: 500
-
-  &__tag-remove
-    width: 16px
-    height: 16px
-    background: #93c5fd
-    color: #1e40af
-    border: none
-    border-radius: 50%
-    cursor: pointer
-    display: flex
-    align-items: center
-    justify-content: center
-    font-size: 10px
-    line-height: 1
-
-    &:hover
-      background: #60a5fa
-
-  &__tag-add
-    display: flex
-    gap: 8px
-    align-items: center
-
-    @media (max-width: 768px)
-      flex-direction: column
-
-// Стили для управления категориями
-.admin-products
-  &__categories-header
-    margin-bottom: 16px
-
-  &__categories-list
-    display: flex
-    flex-direction: column
-    gap: 8px
-
-  &__category-item
-    display: flex
-    justify-content: space-between
-    align-items: center
-    padding: 12px
-    border: 1px solid #e5e7eb
-    border-radius: 6px
-    background: white
-
-    @media (max-width: 768px)
-      flex-direction: column
-      align-items: stretch
-      gap: 8px
-
-  &__category-info
-    flex: 1
-
-  &__category-name
-    font-weight: 500
-    color: #1f2937
-    margin-bottom: 4px
-
-  &__category-meta
-    display: flex
-    gap: 12px
-    font-size: 12px
-    color: #6b7280
-
-  &__category-actions
-    display: flex
-    gap: 8px
-
-    @media (max-width: 768px)
-      justify-content: flex-end
+    
+    &__table-container
+      font-size: 12px
+    
+    &__th, &__td
+      padding: 8px 12px
+  
+  .admin-modal__content
+    width: 95%
+    margin: 20px
+  
+  .admin-form__row
+    grid-template-columns: 1fr