document.head.insertAdjacentHTML('beforeend','') 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' # Mass actions data selectedProducts: [] selectAll: false massCategory: '' massAllCategory: '' removeExistingCategories: false massRemoveAllCategories: false priceChangeType: 'fixed' priceChangeValue: null applyToOldPrice: false productForm: { name: '' sku: '' category: '' price: 0 oldPrice: 0 brand: '' description: '' image: '' active: true domains: [] } categoryForm: { name: '' slug: '' description: '' parentCategory: '' sortOrder: 0 image: '' icon: '' active: true domains: [] } } computed: filteredProducts: -> products = @products if @searchQuery query = @searchQuery.toLowerCase() products = products.filter (product) => product.name?.toLowerCase().includes(query) || product.sku?.toLowerCase().includes(query) if @selectedCategory 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 methods: getStatusClass: (isActive) -> baseClass = 'admin-products__status' if isActive return "#{baseClass} admin-products__status--active" else return "#{baseClass} admin-products__status--inactive" formatPrice: (price) -> return '0 ₽' if !price new Intl.NumberFormat('ru-RU', { style: 'currency' currency: 'RUB' minimumFractionDigits: 0 }).format(price) getCategoryName: (categoryId) -> category = @categories.find (cat) -> cat._id == categoryId category?.name || 'Без категории' getCategoryProductCount: (categoryId) -> @products.filter((product) -> product.category == categoryId).length loadProducts: -> PouchDB.queryView('admin', 'products', { include_docs: true }) .then (result) => @products = result.rows.map (row) -> row.doc .catch (error) => debug.log 'Ошибка загрузки товаров:', error @showNotification 'Ошибка загрузки товаров', 'error' loadCategories: -> 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 # Mass actions methods toggleSelectAll: -> if @selectAll @selectedProducts = @filteredProducts.map (product) -> product._id else @selectedProducts = [] isProductSelected: (productId) -> @selectedProducts.includes(productId) clearSelection: -> @selectedProducts = [] @selectAll = false activateSelected: -> if @selectedProducts.length == 0 @showNotification 'Выберите товары для активации', 'error' return promises = @selectedProducts.map (productId) => product = @products.find (p) -> p._id == productId if product && !product.active updatedProduct = { ...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 && product.active updatedProduct = { ...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({ ...doc, _deleted: true }) Promise.all(promises) .then (results) => @loadProducts() @clearSelection() @showNotification "Удалено #{results.length} товаров" .catch (error) => debug.log 'Ошибка удаления товаров:', error @showNotification 'Ошибка удаления товаров', 'error' assignCategoryToSelected: -> if @selectedProducts.length == 0 || !@massCategory @showNotification 'Выберите товары и категорию', 'error' return promises = @selectedProducts.map (productId) => product = @products.find (p) -> p._id == productId if product updatedProduct = { ...product updatedAt: new Date().toISOString() } if @removeExistingCategories updatedProduct.category = @massCategory else # Если категория уже есть, не перезаписываем updatedProduct.category = @massCategory PouchDB.saveToRemote(updatedProduct) Promise.all(promises) .then (results) => @loadProducts() @clearSelection() @showCategoryAssignModal = false @massCategory = '' @removeExistingCategories = false @showNotification "Категория назначена для #{results.length} товаров" .catch (error) => debug.log 'Ошибка назначения категории:', error @showNotification 'Ошибка назначения категории', 'error' assignCategoryToAll: -> if !@massAllCategory @showNotification 'Выберите категорию', 'error' return promises = @products.map (product) => updatedProduct = { ...product updatedAt: new Date().toISOString() } if @massRemoveAllCategories updatedProduct.category = @massAllCategory else if !updatedProduct.category updatedProduct.category = @massAllCategory PouchDB.saveToRemote(updatedProduct) Promise.all(promises) .then (results) => @loadProducts() @showMassCategoryAssign = false @massAllCategory = '' @massRemoveAllCategories = false @showNotification "Категория назначена для всех товаров" .catch (error) => debug.log 'Ошибка назначения категории:', error @showNotification 'Ошибка назначения категории', 'error' massChangeStatus: (status) -> promises = @products.map (product) => if product.active != status updatedProduct = { ...product active: status updatedAt: new Date().toISOString() } PouchDB.saveToRemote(updatedProduct) else Promise.resolve() Promise.all(promises) .then (results) => @loadProducts() @showNotification "Статус всех товаров изменен" .catch (error) => debug.log 'Ошибка изменения статуса:', error @showNotification 'Ошибка изменения статуса', 'error' massRemoveCategories: -> if !confirm("Удалить категории у всех товаров?") return promises = @products.map (product) => if product.category updatedProduct = { ...product category: '' updatedAt: new Date().toISOString() } PouchDB.saveToRemote(updatedProduct) else Promise.resolve() Promise.all(promises) .then (results) => @loadProducts() @showNotification "Категории удалены у всех товаров" .catch (error) => debug.log 'Ошибка удаления категорий:', error @showNotification 'Ошибка удаления категорий', 'error' applyMassPriceChange: -> if !@priceChangeValue @showNotification 'Введите значение изменения', 'error' return promises = @products.map (product) => updatedProduct = { ...product, updatedAt: new Date().toISOString() } switch @priceChangeType when 'fixed' if @applyToOldPrice updatedProduct.oldPrice = parseFloat(@priceChangeValue) else updatedProduct.price = parseFloat(@priceChangeValue) when 'percent' if @applyToOldPrice && updatedProduct.oldPrice updatedProduct.oldPrice = updatedProduct.oldPrice * (1 + parseFloat(@priceChangeValue) / 100) else updatedProduct.price = updatedProduct.price * (1 + parseFloat(@priceChangeValue) / 100) when 'increase' if @applyToOldPrice && updatedProduct.oldPrice updatedProduct.oldPrice = updatedProduct.oldPrice + parseFloat(@priceChangeValue) else updatedProduct.price = updatedProduct.price + parseFloat(@priceChangeValue) when 'decrease' if @applyToOldPrice && updatedProduct.oldPrice updatedProduct.oldPrice = Math.max(0, updatedProduct.oldPrice - parseFloat(@priceChangeValue)) else updatedProduct.price = Math.max(0, updatedProduct.price - parseFloat(@priceChangeValue)) PouchDB.saveToRemote(updatedProduct) Promise.all(promises) .then (results) => @loadProducts() @showMassPriceModal = false @priceChangeValue = null @showNotification "Цены успешно обновлены" .catch (error) => debug.log 'Ошибка изменения цен:', error @showNotification 'Ошибка изменения цен', 'error' exportProducts: -> csvData = @products.map (product) => categoryName = @getCategoryName(product.category) return { 'Название товара': product.name 'Артикул': product.sku 'Цена, руб.': product.price 'Старая цена, руб.': product.oldPrice || '' 'Категория': categoryName 'Бренд': product.brand || '' 'Статус': if product.active then 'Активен' else 'Неактивен' 'Описание': product.description || '' } csv = Papa.unparse(csvData, { delimiter: ';' encoding: 'UTF-8' }) blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }) link = document.createElement('a') url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', 'products_export.csv') link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) @showNotification 'Экспорт завершен' exportSelectedProducts: -> if @selectedProducts.length == 0 @showNotification 'Выберите товары для экспорта', 'error' return selectedProductsData = @products.filter (product) => @selectedProducts.includes(product._id) csvData = selectedProductsData.map (product) => categoryName = @getCategoryName(product.category) return { 'Название товара': product.name 'Артикул': product.sku 'Цена, руб.': product.price 'Старая цена, руб.': product.oldPrice || '' 'Категория': categoryName 'Бренд': product.brand || '' 'Статус': if product.active then 'Активен' else 'Неактивен' 'Описание': product.description || '' } csv = Papa.unparse(csvData, { delimiter: ';' encoding: 'UTF-8' }) blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }) link = document.createElement('a') url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', 'selected_products_export.csv') link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) @showNotification 'Экспорт выбранных товаров завершен' # Updated category creation with duplicate check transformProductData: (product, index) -> productData = { _id: "product:#{Date.now()}-#{index}" type: 'product' name: product['Название товара'] sku: product['Артикул*'] price: parseFloat(product['Цена, руб.*'].replace(/\s/g, '').replace(',', '.')) || 0 active: true createdAt: new Date().toISOString() updatedAt: new Date().toISOString() } # Improved category handling with duplicate check if product['Тип*'] categoryName = product['Тип*'].trim() # Check for existing category by name (case insensitive) existingCategory = @categories.find (cat) -> cat.name?.toLowerCase() == categoryName.toLowerCase() if existingCategory productData.category = existingCategory._id debug.log "Использована существующая категория: #{categoryName}" else # Create new category only if it doesn't exist 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: @availableDomains?.map((d) -> d.domain) || [] } # Save new category and add to local categories array PouchDB.saveToRemote(newCategory) .then (result) => debug.log "Создана новая категория: #{categoryName}" @categories.push(newCategory) .catch (error) -> debug.log "Ошибка создания категории #{categoryName}:", error productData.category = categoryId # Rest of the method remains the same... if product['Цена до скидки, руб.'] productData.oldPrice = parseFloat(product['Цена до скидки, руб.'].replace(/\s/g, '').replace(',', '.')) if product['Ссылка на главное фото*'] productData.image = product['Ссылка на главное фото*'] if product['Бренд*'] productData.brand = product['Бренд*'] if product['Тип*'] productData.productType = product['Тип*'] if product['Rich-контент JSON'] try richContent = JSON.parse(product['Rich-контент JSON']) productData.description = @richContentToMarkdown(richContent) catch productData.description = product['Аннотация'] || '' else productData.description = product['Аннотация'] || '' productData.domains = @availableDomains?.map((d) -> d.domain) || [] return productData # Updated category creation method with duplicate prevention saveCategory: -> if !@categoryForm.name || !@categoryForm.slug @showNotification 'Заполните обязательные поля (Название, URL slug)', 'error' return # Check for duplicate category name duplicateCategory = @categories.find (cat) => cat.name?.toLowerCase() == @categoryForm.name.toLowerCase() && (!@editingCategory || cat._id != @editingCategory._id) if duplicateCategory @showNotification 'Категория с таким названием уже существует', 'error' return categoryData = { type: 'category' ...@categoryForm updatedAt: new Date().toISOString() } if @editingCategory categoryData._id = @editingCategory._id categoryData._rev = @editingCategory._rev categoryData.createdAt = @editingCategory.createdAt else categoryData._id = "category:#{Date.now()}" categoryData.createdAt = new Date().toISOString() PouchDB.saveToRemote(categoryData) .then (result) => @showCategoryModal = false @resetCategoryForm() @loadCategories() @showNotification 'Категория успешно сохранена' .catch (error) => debug.log 'Ошибка сохранения категории:', error @showNotification 'Ошибка сохранения категории', 'error' # Rest of the methods remain the same... showNotification: (message, type = 'success') -> @$root.showNotification?(message, type) || debug.log("#{type}: #{message}") mounted: -> @loadProducts() @loadCategories() @loadDomains()