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()