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
editingProduct: null
editingCategory: null
selectedFile: null
selectedCategoriesFile: null
importing: false
importingCategories: false
importResults: null
categoriesImportResults: null
availableDomains: []
categoriesActiveTab: 'list'
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:
loadProducts: ->
PouchDB.queryView('admin', 'products', { include_docs: true })
.then (result) =>
@products = result.rows.map (row) -> row.doc
.catch (error) =>
console.error 'Ошибка загрузки товаров:', error
@showNotification 'Ошибка загрузки товаров', 'error'
loadCategories: ->
PouchDB.queryView('admin', 'categories', { include_docs: true })
.then (result) =>
@categories = result.rows.map (row) -> row.doc
.catch (error) =>
console.error 'Ошибка загрузки категорий:', error
loadDomains: ->
PouchDB.queryView('admin', 'domain_settings', { include_docs: true })
.then (result) =>
@availableDomains = result.rows.map (row) -> row.doc
.catch (error) =>
console.error 'Ошибка загрузки доменов:', error
getCategoryName: (categoryId) ->
category = @categories.find (cat) -> cat._id == categoryId
category?.name || 'Без категории'
getCategoryProductCount: (categoryId) ->
@products.filter((product) -> product.category == categoryId).length
# Управление товарами
editProduct: (product) ->
@editingProduct = product
@productForm = {
name: product.name || ''
sku: product.sku || ''
category: product.category || ''
price: product.price || 0
oldPrice: product.oldPrice || 0
brand: product.brand || ''
description: product.description || ''
image: product.image || ''
active: product.active != false
domains: product.domains || []
}
@showProductModal = true
saveProduct: ->
if !@productForm.name || !@productForm.sku || !@productForm.price
@showNotification 'Заполните обязательные поля (Название, Артикул, Цена)', 'error'
return
productData = {
type: 'product'
...@productForm
updatedAt: new Date().toISOString()
}
if @editingProduct
productData._id = @editingProduct._id
productData._rev = @editingProduct._rev
productData.createdAt = @editingProduct.createdAt
else
productData._id = "product:#{Date.now()}"
productData.createdAt = new Date().toISOString()
PouchDB.saveToRemote(productData)
.then (result) =>
@showProductModal = false
@resetProductForm()
@loadProducts()
@showNotification 'Товар успешно сохранен'
.catch (error) =>
console.error 'Ошибка сохранения товара:', error
@showNotification 'Ошибка сохранения товара', 'error'
removeProductImage: ->
@productForm.image = ''
onProductImageUpload: (event) ->
file = event.target.files[0]
if file
reader = new FileReader()
reader.onload = (e) =>
@productForm.image = e.target.result
reader.readAsDataURL(file)
# Управление категориями
editCategory: (category) ->
@editingCategory = category
@categoryForm = {
name: category.name || ''
slug: category.slug || ''
description: category.description || ''
parentCategory: category.parentCategory || ''
sortOrder: category.sortOrder || 0
image: category.image || ''
icon: category.icon || ''
active: category.active != false
domains: category.domains || []
}
@showCategoryModal = true
saveCategory: ->
if !@categoryForm.name || !@categoryForm.slug
@showNotification 'Заполните обязательные поля (Название, URL slug)', '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) =>
console.error 'Ошибка сохранения категории:', error
@showNotification 'Ошибка сохранения категории', 'error'
removeCategoryImage: ->
@categoryForm.image = ''
removeCategoryIcon: ->
@categoryForm.icon = ''
onCategoryImageUpload: (event) ->
file = event.target.files[0]
if file
reader = new FileReader()
reader.onload = (e) =>
@categoryForm.image = e.target.result
reader.readAsDataURL(file)
onCategoryIconUpload: (event) ->
file = event.target.files[0]
if file
reader = new FileReader()
reader.onload = (e) =>
@categoryForm.icon = e.target.result
reader.readAsDataURL(file)
deleteCategory: (categoryId) ->
if confirm('Вы уверены, что хотите удалить эту категорию?')
PouchDB.getDocument(categoryId)
.then (doc) ->
PouchDB.saveToRemote({ ...doc, _deleted: true })
.then (result) =>
@loadCategories()
@showNotification 'Категория удалена'
.catch (error) =>
console.error 'Ошибка удаления категории:', error
@showNotification 'Ошибка удаления категории', 'error'
# Импорт товаров
onFileSelect: (event) ->
@selectedFile = event.target.files[0]
@importResults = null
importProducts: ->
if !@selectedFile
@showNotification 'Выберите файл для импорта', 'error'
return
@importing = true
@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 && row['Артикул*'] && row['Название товара'] && row['Цена, руб.*']
couchProducts = products.map (product, index) =>
@transformProductData(product, index)
# Пакетное сохранение
PouchDB.bulkDocs(couchProducts)
.then (result) =>
@importResults = {
success: true,
processed: couchProducts.length,
errors: []
}
@importing = false
@loadProducts()
@loadCategories() # Перезагружаем категории, т.к. могли добавиться новые
@showNotification "Импортировано #{couchProducts.length} товаров"
.catch (error) =>
@importResults = {
success: false,
error: error.message,
processed: 0,
errors: [error.message]
}
@importing = false
@showNotification "Ошибка импорта: #{error.message}", 'error'
catch error
@importResults = {
success: false,
error: error.message,
processed: 0,
errors: [error.message]
}
@importing = false
@showNotification "Ошибка обработки файла: #{error.message}", 'error'
reader.readAsText(@selectedFile, 'UTF-8')
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()
}
# Обработка категории из поля "Тип*"
if product['Тип*']
categoryName = product['Тип*'].trim()
# Ищем существующую категорию
existingCategory = @categories.find (cat) ->
cat.name?.toLowerCase() == categoryName.toLowerCase()
if existingCategory
productData.category = existingCategory._id
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: @availableDomains?.map((d) -> d.domain) || []
}
# Сохраняем новую категорию
PouchDB.saveToRemote(newCategory)
productData.category = categoryId
# Дополнительные поля
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['Тип*']
# Rich content преобразование
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
# Импорт категорий
onCategoriesFileSelect: (event) ->
@selectedCategoriesFile = event.target.files[0]
@categoriesImportResults = null
importCategories: ->
if !@selectedCategoriesFile
@showNotification 'Выберите файл категорий для импорта', 'error'
return
@importingCategories = true
@categoriesImportResults = null
reader = new FileReader()
reader.onload = (e) =>
try
results = Papa.parse e.target.result, {
header: true
delimiter: ','
skipEmptyLines: true
encoding: 'UTF-8'
}
categories = results.data.filter (row) =>
row && row.name && row.slug
couchCategories = categories.map (category, index) =>
@transformCategoryData(category, index)
# Пакетное сохранение категорий
PouchDB.bulkDocs(couchCategories)
.then (result) =>
@categoriesImportResults = {
success: true,
processed: couchCategories.length,
errors: []
}
@importingCategories = false
@loadCategories()
@showNotification "Импортировано #{couchCategories.length} категорий"
.catch (error) =>
@categoriesImportResults = {
success: false,
error: error.message,
processed: 0,
errors: [error.message]
}
@importingCategories = false
@showNotification "Ошибка импорта категорий: #{error.message}", 'error'
catch error
@categoriesImportResults = {
success: false,
error: error.message,
processed: 0,
errors: [error.message]
}
@importingCategories = false
@showNotification "Ошибка обработки файла категорий: #{error.message}", 'error'
reader.readAsText(@selectedCategoriesFile, 'UTF-8')
transformCategoryData: (category, index) ->
categoryData = {
_id: "category:import-#{Date.now()}-#{index}"
type: 'category'
name: category.name
slug: category.slug
description: category.description || ''
parentCategory: category.parentCategory || ''
sortOrder: parseInt(category.sortOrder) || @categories.length + index
active: category.active != 'false'
createdAt: new Date().toISOString()
updatedAt: new Date().toISOString()
domains: @availableDomains?.map((d) -> d.domain) || []
}
if category.image
categoryData.image = category.image
if category.icon
categoryData.icon = category.icon
return categoryData
# Вспомогательные методы
generateSlug: (text) ->
text.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w\-]+/g, '')
.replace(/\-\-+/g, '-')
.replace(/^-+/, '')
.replace(/-+$/, '')
richContentToMarkdown: (richContent) ->
# Простое преобразование rich content в markdown
return JSON.stringify(richContent) # Временная реализация
toggleProductStatus: (product) ->
updatedProduct = {
...product
active: !product.active
updatedAt: new Date().toISOString()
}
PouchDB.saveToRemote(updatedProduct)
.then (result) =>
@loadProducts()
@showNotification 'Статус товара обновлен'
.catch (error) =>
console.error 'Ошибка обновления статуса:', error
@showNotification 'Ошибка обновления статуса', 'error'
deleteProduct: (productId) ->
if confirm('Вы уверены, что хотите удалить этот товар?')
PouchDB.getDocument(productId)
.then (doc) ->
PouchDB.saveToRemote({ ...doc, _deleted: true })
.then (result) =>
@loadProducts()
@showNotification 'Товар удален'
.catch (error) =>
console.error 'Ошибка удаления товара:', error
@showNotification 'Ошибка удаления товара', 'error'
resetProductForm: ->
@editingProduct = null
@productForm = {
name: ''
sku: ''
category: ''
price: 0
oldPrice: 0
brand: ''
description: ''
image: ''
active: true
domains: []
}
resetCategoryForm: ->
@editingCategory = null
@categoryForm = {
name: ''
slug: ''
description: ''
parentCategory: ''
sortOrder: 0
image: ''
icon: ''
active: true
domains: []
}
getCategoriesTabClass: (tabId) ->
baseClass = 'admin-products__categories-tab'
isActive = @categoriesActiveTab == tabId
if isActive
return "#{baseClass} admin-products__categories-tab--active"
else
return baseClass
formatPrice: (price) ->
return '0 ₽' if !price
new Intl.NumberFormat('ru-RU', {
style: 'currency'
currency: 'RUB'
minimumFractionDigits: 0
}).format(price)
getStatusClass: (isActive) ->
baseClass = 'admin-products__status'
if isActive
return "#{baseClass} admin-products__status--active"
else
return "#{baseClass} admin-products__status--inactive"
showNotification: (message, type = 'success') ->
@$root.showNotification?(message, type) || debug.log("#{type}: #{message}")
mounted: ->
@loadProducts()
@loadCategories()
@loadDomains()