From e37432d812bb0218e9223fbdc54ba9662f335976 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 3 Jun 2026 14:09:52 +0300 Subject: [PATCH] =?UTF-8?q?feat(shop):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5/=D1=80=D0=B5=D0=B4=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=80=D0=B0=20=D0=B2=20=D0=BC=D0=BE=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D0=BE=D0=BA=D0=BD=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Инлайн-панель формы внизу страницы заменена на модалку через LS.modal: - shopAdminCreateItem/EditItem открывают окно openItemModal (create/edit) - валидация: обязательное название + проверка JSON в поле «Данные» - блокировка кнопки на время сохранения, ошибки через m.setError - удалены инлайн-форма из admin.html и неактуальные shopAdminSaveItem/shopAdminCancelForm/showShopForm + стейт Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/admin.html | 50 --------- frontend/js/admin/sections/shop.js | 175 ++++++++++++++++++----------- 2 files changed, 109 insertions(+), 116 deletions(-) diff --git a/frontend/admin.html b/frontend/admin.html index feb9fc9..3d19e9a 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -1337,56 +1337,6 @@
- - diff --git a/frontend/js/admin/sections/shop.js b/frontend/js/admin/sections/shop.js index bd18b57..7f86e9b 100644 --- a/frontend/js/admin/sections/shop.js +++ b/frontend/js/admin/sections/shop.js @@ -4,8 +4,6 @@ 'use strict'; let inited = false; let _shopItems = []; - let _shopEditId = null; - let _shopSaving = false; async function load() { try { @@ -67,73 +65,120 @@ if (window.lucide) lucide.createIcons(); } - function showShopForm() { - const form = document.getElementById('shop-item-form'); - form.style.display = ''; - form.scrollIntoView({ behavior: 'smooth', block: 'center' }); - document.getElementById('shop-f-name').focus(); + 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() { - _shopEditId = null; - document.getElementById('shop-form-title').textContent = 'Добавить товар'; - document.getElementById('shop-f-name').value = ''; - document.getElementById('shop-f-type').value = 'frame'; - document.getElementById('shop-f-price').value = '100'; - document.getElementById('shop-f-desc').value = ''; - document.getElementById('shop-f-icon').value = ''; - document.getElementById('shop-f-data').value = ''; - document.getElementById('shop-f-active').checked = true; - showShopForm(); - } + function shopAdminCreateItem() { openItemModal(null); } function shopAdminEditItem(id) { const it = _shopItems.find(i => i.id === id); - if (!it) return; - _shopEditId = id; - document.getElementById('shop-form-title').textContent = 'Редактировать товар #' + id; - document.getElementById('shop-f-name').value = it.name || ''; - document.getElementById('shop-f-type').value = it.type || 'frame'; - document.getElementById('shop-f-price').value = it.price ?? 100; - document.getElementById('shop-f-desc').value = it.description || ''; - document.getElementById('shop-f-icon').value = it.icon || ''; - document.getElementById('shop-f-data').value = it.data ? (typeof it.data === 'string' ? it.data : JSON.stringify(it.data)) : ''; - document.getElementById('shop-f-active').checked = !!it.is_active; - showShopForm(); - } - - function shopAdminCancelForm() { - document.getElementById('shop-item-form').style.display = 'none'; - _shopEditId = null; - } - - async function shopAdminSaveItem() { - if (_shopSaving) return; - _shopSaving = true; - const data = { - name: document.getElementById('shop-f-name').value.trim(), - type: document.getElementById('shop-f-type').value, - price: parseInt(document.getElementById('shop-f-price').value) || 0, - description: document.getElementById('shop-f-desc').value.trim(), - icon: document.getElementById('shop-f-icon').value.trim(), - data: document.getElementById('shop-f-data').value.trim() || null, - is_active: document.getElementById('shop-f-active').checked ? 1 : 0 - }; - if (!data.name) { LS.toast('Введите название', 'error'); _shopSaving = false; return; } - try { - if (_shopEditId) { - await LS.adminShopUpdateItem(_shopEditId, data); - LS.toast('Товар обновлён', 'success'); - } else { - await LS.adminShopCreateItem(data); - LS.toast('Товар создан', 'success'); - } - shopAdminCancelForm(); - inited = false; - await load(); - inited = true; - } catch(e) { LS.toast('Ошибка: ' + e.message, 'error'); } - finally { _shopSaving = false; } + if (it) openItemModal(it); } async function shopAdminDeleteItem(id) { @@ -157,8 +202,6 @@ // Expose onclick handlers window.shopAdminCreateItem = shopAdminCreateItem; window.shopAdminEditItem = shopAdminEditItem; - window.shopAdminCancelForm = shopAdminCancelForm; - window.shopAdminSaveItem = shopAdminSaveItem; window.shopAdminDeleteItem = shopAdminDeleteItem; window.shopAdminToggleActive = shopAdminToggleActive;