'use strict'; /* admin → shop section: items + purchases */ (function () { 'use strict'; let inited = false; let _shopItems = []; let _filterType = ''; let _search = ''; const TYPE_ICONS = { frame:'square', title:'award', background:'image', effect:'sparkles', theme:'palette' }; const typeIcon = t => TYPE_ICONS[t] || 'tag'; const GROUP_ORDER = [ { type: 'frame', label: 'Рамки' }, { type: 'title', label: 'Титулы' }, { type: 'background', label: 'Фоны' }, { type: 'effect', label: 'Эффекты' }, ]; function parseData(it) { if (!it || it.data == null) return {}; if (typeof it.data === 'object') return it.data; try { return JSON.parse(it.data); } catch { return {}; } } /* Real visual preview by item type (uses the same data the client renders). */ function itemPreview(it) { const d = parseData(it); if (it.type === 'background') { const slug = String(d.slug || 'none').replace(/[^a-z0-9_-]/gi, ''); return `
`; } if (it.type === 'title') { const color = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(d.color || '') ? d.color : '#9B5DE5'; return `
${esc(d.text || it.name || '')}
`; } if (it.type === 'frame') { const css = String(d.css || '').replace(/"/g, '"'); return `
`; } if (it.type === 'effect') { const fx = d.effect === 'pulse' ? ' fx-pulse' : ''; const ic = d.effect === 'pulse' ? '' : ``; return `
${ic}
`; } return `
`; } function cardHTML(it) { return `
${itemPreview(it)}
${esc(it.name)}
${it.price} продано: ${it.sold_count || 0}
`; } async function load() { try { const [stats, items] = await Promise.all([ LS.adminShopStats(), LS.adminShopGetItems() ]); const topName = stats.topItems?.[0]?.name || '—'; const stat = (cls, icon, ic, val, label, sm) => `
${val} ${label}
`; document.getElementById('shop-stats-grid').innerHTML = stat('var(--violet)', 'shopping-bag', 'rgba(155,93,229,0.12)', `${stats.activeItems}/${stats.totalItems}`, 'товаров активно') + stat('var(--cyan)', 'receipt', 'rgba(6,214,224,0.12)', stats.totalPurchases, 'покупок') + stat('var(--green)', 'coins', 'rgba(6,214,100,0.12)', stats.totalCoinsInCirculation, 'монет в обороте') + stat('#d98a17', 'star', 'rgba(255,179,71,0.16)', esc(topName), 'топ-товар', true); _shopItems = items; renderShopItems(); if (window.lucide) lucide.createIcons(); } catch(e) { document.getElementById('shop-stats-grid').innerHTML = `
Ошибка: ${esc(e.message)}
`; } } function shopApplyFilters() { _filterType = document.getElementById('shop-filter-type')?.value || ''; _search = (document.getElementById('shop-search')?.value || '').trim().toLowerCase(); renderShopItems(); } function renderShopItems() { const wrap = document.getElementById('shop-items-body'); const countEl = document.getElementById('shop-count'); const filtered = _shopItems.filter(it => (!_filterType || it.type === _filterType) && (!_search || (it.name || '').toLowerCase().includes(_search)) ); if (countEl) countEl.textContent = filtered.length === _shopItems.length ? `${_shopItems.length} товаров` : `${filtered.length} из ${_shopItems.length}`; if (!_shopItems.length) { wrap.innerHTML = '
Нет товаров
'; return; } if (!filtered.length) { wrap.innerHTML = '
Ничего не найдено
'; return; } const groups = []; const seen = new Set(); for (const g of GROUP_ORDER) { const items = filtered.filter(it => it.type === g.type); if (items.length) { groups.push({ label: g.label, items }); seen.add(g.type); } } const other = filtered.filter(it => !seen.has(it.type)); if (other.length) groups.push({ label: 'Прочее', items: other }); wrap.innerHTML = groups.map(g => `
${g.label}${g.items.length}
${g.items.map(cardHTML).join('')}
`).join(''); if (window.lucide) lucide.createIcons(); } const TYPE_OPTIONS = [ { v: 'frame', l: 'Рамка' }, { v: 'title', l: 'Титул' }, { v: 'background', l: 'Фон' }, { v: 'effect', l: 'Эффект' }, ]; /* Open the add/edit item modal. item = null → create, object → edit. */ function openItemModal(item) { const isEdit = !!item; const dataStr = item && item.data ? (typeof item.data === 'string' ? item.data : JSON.stringify(item.data)) : ''; const body = document.createElement('div'); body.innerHTML = `
`; const $ = sel => body.querySelector(sel); $('#shop-f-name').value = item?.name || ''; $('#shop-f-type').value = item?.type || 'frame'; $('#shop-f-price').value = item?.price ?? 100; $('#shop-f-desc').value = item?.description || ''; $('#shop-f-icon').value = item?.icon || ''; $('#shop-f-data').value = dataStr; $('#shop-f-active').checked = item ? !!item.is_active : true; let saving = false; const m = LS.modal({ title: isEdit ? ('Редактировать товар #' + item.id) : 'Добавить товар', content: body, size: 'md', actions: [ { label: 'Отмена', onClick: () => m.close() }, { label: 'Сохранить', primary: true, id: 'shop-save-btn', onClick: async () => { if (saving) return; const payload = { name: $('#shop-f-name').value.trim(), type: $('#shop-f-type').value, price: parseInt($('#shop-f-price').value, 10) || 0, description: $('#shop-f-desc').value.trim(), icon: $('#shop-f-icon').value.trim(), data: $('#shop-f-data').value.trim() || null, is_active: $('#shop-f-active').checked ? 1 : 0, }; if (!payload.name) { m.setError('Введите название'); return; } if (payload.data) { try { JSON.parse(payload.data); } catch { m.setError('Поле «Данные» — некорректный JSON'); return; } } saving = true; const btn = document.getElementById('shop-save-btn'); if (btn) { btn.disabled = true; btn.textContent = 'Сохранение…'; } try { if (isEdit) { await LS.adminShopUpdateItem(item.id, payload); LS.toast('Товар обновлён', 'success'); } else { await LS.adminShopCreateItem(payload); LS.toast('Товар создан', 'success'); } m.close(); inited = false; await load(); inited = true; } catch(e) { m.setError('Ошибка: ' + e.message); saving = false; if (btn) { btn.disabled = false; btn.textContent = 'Сохранить'; } } } }, ], }); setTimeout(() => $('#shop-f-name')?.focus(), 80); } function shopAdminCreateItem() { openItemModal(null); } function shopAdminEditItem(id) { const it = _shopItems.find(i => i.id === id); if (it) openItemModal(it); } async function shopAdminDeleteItem(id) { if (!await LS.confirm('Все покупки этого товара будут удалены.', { title: 'Удалить товар?', confirmText: 'Удалить', danger: true })) return; try { await LS.adminShopDeleteItem(id); LS.toast('Товар удалён', 'success'); inited = false; await load(); inited = true; } catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); } } async function shopAdminToggleActive(id, active) { try { await LS.adminShopUpdateItem(id, { is_active: active ? 1 : 0 }); LS.toast(active ? 'Товар активирован' : 'Товар деактивирован', 'success'); } catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); } } // Expose onclick handlers window.shopAdminCreateItem = shopAdminCreateItem; window.shopAdminEditItem = shopAdminEditItem; window.shopAdminDeleteItem = shopAdminDeleteItem; window.shopAdminToggleActive = shopAdminToggleActive; window.shopApplyFilters = shopApplyFilters; window.AdminSections = window.AdminSections || {}; window.AdminSections.shop = { init: async () => { if (inited) return; inited = true; await load(); }, reload: load, }; })();