| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+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'
-
- # 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()
|