From 4e17211cf0facd17ab12401d0bddde67c1410f32 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Mon, 26 Jan 2026 00:40:04 +0100 Subject: [PATCH] adasd --- public/assets/js/ui-list.js | 183 ++++++++++++++++++++++++++++++------ public/index.php | 57 +++++++---- src/ApiKernel.php | 35 +++++++ 3 files changed, 229 insertions(+), 46 deletions(-) diff --git a/public/assets/js/ui-list.js b/public/assets/js/ui-list.js index 067246f..6f53805 100644 --- a/public/assets/js/ui-list.js +++ b/public/assets/js/ui-list.js @@ -59,21 +59,79 @@ async function openContentEditor(item, section) { } } -async function openTemplateEditor(item, section) { - const dlg = document.getElementById('editTemplateDialog'); - const form = document.getElementById('editTemplateForm'); - const inpName = document.getElementById('edit_tpl_name'); - const inpApiName = document.getElementById('edit_tpl_api_name'); - const apiWarn = document.getElementById('edit_tpl_api_warn'); - const btnCancel = document.getElementById('editTemplateCancel'); +async function openTemplateManager(item, section) { + const dlg = document.getElementById('manageTemplateDialog'); + const inpName = document.getElementById('manage_tpl_name'); + const inpApiName = document.getElementById('manage_tpl_api_name'); + const apiWrap = document.getElementById('manage_tpl_api_wrap'); + const apiWarn = document.getElementById('manage_tpl_api_warn'); + const badge = document.getElementById('manage_tpl_badge'); + const versionsWrap = document.getElementById('manage_tpl_versions'); + const previewFrame = document.getElementById('manage_tpl_preview'); + const btnClose = document.getElementById('manageTemplateClose'); + const btnSave = document.getElementById('manageTemplateSave'); + const btnDelete = document.getElementById('manageTemplateDelete'); + const deleteHint = document.getElementById('manage_tpl_delete_hint'); const detail = await fetchContentItem(item.id, section.id).catch(() => ({})); const row = detail?.item || detail?.data || detail || {}; const initialApi = row.api_name || ''; + if (badge) badge.textContent = `ID ${item.id}`; if (inpName) inpName.value = row.name || ''; if (inpApiName) inpApiName.value = initialApi; if (apiWarn) apiWarn.classList.add('hidden'); + if (apiWrap) apiWrap.classList.toggle('hidden', !section?.is_template); + + let versions = []; + let activeId = 0; + + const renderPreview = (html) => { + if (!previewFrame) return; + previewFrame.srcdoc = '' + (html || '(leer)') + ''; + }; + + const updateDeleteState = () => { + const hasActive = !!activeId; + if (btnDelete) btnDelete.disabled = hasActive; + if (deleteHint) { + deleteHint.textContent = hasActive + ? 'Aktive Version vorhanden – Löschen deaktiviert.' + : 'Nur möglich, wenn keine aktive Version existiert.'; + } + }; + + const loadVersions = async () => { + const res = await apiAction('content_versions.list', { method: 'GET', data: { content_id: item.id } }).catch(() => ({})); + versions = Array.isArray(res?.items) ? res.items : []; + activeId = 0; + versions.forEach(v => { + if (Number(v.is_active) === 1) activeId = Number(v.id || 0); + }); + renderVersions(); + updateDeleteState(); + }; + + const renderVersions = () => { + if (!versionsWrap) return; + if (!versions.length) { + versionsWrap.innerHTML = '
Keine Versionen vorhanden
'; + return; + } + versionsWrap.innerHTML = versions.map(v => { + const isActive = Number(v.is_active) === 1; + const label = `${isActive ? '✓ ' : ''}#${v.version_no} – ${formatVersionDate(v.created_at)}` + (isActive ? ' (aktiv)' : ''); + return `
+
${label}
+
+ + + ${isActive ? `` : ``} + +
+
`; + }).join(''); + }; const onApiInput = () => { if (!inpApiName) return; @@ -84,33 +142,97 @@ async function openTemplateEditor(item, section) { } }; - function cleanup() { - form && form.removeEventListener('submit', onSubmit); - btnCancel && (btnCancel.onclick = null); + const cleanup = () => { inpApiName && inpApiName.removeEventListener('input', onApiInput); - } + versionsWrap && versionsWrap.removeEventListener('click', onVersionsClick); + if (btnClose) btnClose.onclick = null; + if (btnSave) btnSave.onclick = null; + if (btnDelete) btnDelete.onclick = null; + }; - async function onSubmit(ev) { - ev.preventDefault(); + const onSave = async () => { try { - const res = await apiUpdate('content', item.id, { + const payload = { name: inpName ? inpName.value : '', - api_name: inpApiName ? inpApiName.value : '', section_id: section.id, - }); - toast(res && res.ok ? 'Template gespeichert' : 'Speichern fehlgeschlagen', !!(res && res.ok)); - dlg && dlg.close(); - cleanup(); - if (typeof window.loadList === 'function') window.loadList(section); + }; + if (section?.is_template) { + payload.api_name = inpApiName ? inpApiName.value : ''; + } + const res = await apiUpdate('content', item.id, payload); + toast(res && res.ok ? 'Gespeichert' : 'Speichern fehlgeschlagen', !!(res && res.ok)); + if (res?.ok && typeof window.loadList === 'function') window.loadList(section); } catch { toast('Speichern fehlgeschlagen', false); } - } + }; + + const onDeleteItem = async () => { + if (activeId) return; + if (!confirm('Template wirklich löschen?')) return; + const res = await apiAction('content.delete', { method: 'POST', data: { id: item.id, section_id: section.id } }); + toast(res && res.ok ? 'Gelöscht' : 'Löschen fehlgeschlagen', !!(res && res.ok), { duration: 3000 }); + dlg && dlg.close(); + if (typeof window.loadList === 'function') window.loadList(section); + }; + + const onVersionsClick = async (ev) => { + const target = ev.target; + if (!target || !target.dataset) return; + const vid = Number(target.dataset.versionPreview || target.dataset.versionEdit || target.dataset.versionActivate || target.dataset.versionDeactivate || target.dataset.versionDelete || 0); + if (!vid) return; + + if (target.dataset.versionPreview !== undefined) { + const res = await apiAction('content_versions.get', { method: 'GET', data: { id: vid, content_id: item.id } }).catch(() => ({})); + const html = res?.item?.html || res?.item?.content || res?.html || res?.content || ''; + renderPreview(html); + return; + } + if (target.dataset.versionEdit !== undefined) { + dlg && dlg.close(); + const versionItem = { ...item, version_id: vid }; + openContentEditor(versionItem, section); + return; + } + if (target.dataset.versionActivate !== undefined) { + const res = await apiAction('content_versions.activate', { method: 'POST', data: { id: vid } }); + toast(res && res.ok ? 'Version aktiviert' : 'Aktivieren fehlgeschlagen', !!(res && res.ok)); + await loadVersions(); + return; + } + if (target.dataset.versionDeactivate !== undefined) { + const res = await apiAction('content_versions.deactivate', { method: 'POST', data: { content_id: item.id } }); + toast(res && res.ok ? 'Aktive Version deaktiviert' : 'Deaktivieren fehlgeschlagen', !!(res && res.ok)); + await loadVersions(); + return; + } + if (target.dataset.versionDelete !== undefined) { + const versionRow = versions.find(v => Number(v.id) === vid); + if (versionRow && Number(versionRow.is_active) === 1) return; + if (!confirm('Version wirklich löschen?')) return; + const res = await apiAction('content_versions.delete', { method: 'POST', data: { id: vid, content_id: item.id } }); + toast(res && res.ok ? 'Version gelöscht' : 'Löschen fehlgeschlagen', !!(res && res.ok)); + await loadVersions(); + return; + } + }; inpApiName && inpApiName.addEventListener('input', onApiInput); - form && form.addEventListener('submit', onSubmit, { once: false }); - btnCancel && (btnCancel.onclick = () => { dlg && dlg.close(); cleanup(); }); + btnClose && (btnClose.onclick = () => { dlg && dlg.close(); cleanup(); }); + btnSave && (btnSave.onclick = onSave); + btnDelete && (btnDelete.onclick = onDeleteItem); + versionsWrap && versionsWrap.addEventListener('click', onVersionsClick); + await loadVersions(); + if (activeId) { + const res = await apiAction('content_versions.get', { method: 'GET', data: { id: activeId, content_id: item.id } }).catch(() => ({})); + const html = res?.item?.html || res?.item?.content || res?.html || res?.content || ''; + renderPreview(html); + } else { + renderPreview(''); + } + + dlg && dlg.addEventListener('close', cleanup, { once: true }); dlg && dlg.showModal(); } @@ -262,8 +384,8 @@ export async function loadList(section) {
${name || '(ohne Name)'}
${apiLine} `; - const openBtn = ``; - const editTplBtn = isTemplate ? `` : ''; + const openBtn = ``; + const editTplBtn = isTemplate ? `` : ''; const testBtn = isTemplate ? `` : ''; const prevBtn = ``; const delBtn = ``; @@ -341,26 +463,31 @@ export async function loadList(section) { scope.querySelectorAll('[data-edit]').forEach(btn => btn.addEventListener('click', () => { const id = Number(btn.dataset.edit || 0); const item = data.find(it => Number(it.id) === id); - if (item) openTemplateEditor(item, section); + if (item) openTemplateManager(item, section); })); const prevDlg = document.getElementById('previewDialog'); const prevFrame = document.getElementById('previewFrame'); scope.querySelectorAll('[data-preview]').forEach(btn => btn.addEventListener('click', async () => { const id = Number(btn.dataset.preview || 0); - const obj = await fetchContentItem(id, section.id); + const obj = await apiAction('content.get', { method: 'GET', data: { id, section_id: section.id, active_only: 1 } }); const html = (obj?.html || obj?.content || '(leer)'); prevFrame.srcdoc = '' + html + ''; prevDlg.showModal(); })); - scope.querySelectorAll('[data-test]').forEach(btn => btn.addEventListener('click', () => { + scope.querySelectorAll('[data-test]').forEach(btn => btn.addEventListener('click', async () => { const id = Number(btn.dataset.test || 0); const nm = btn.dataset.name || ''; if (!id) { toast('Testversand: Ungültige ID', false); return; } + const activeCheck = await apiAction('content.get', { method: 'GET', data: { id, section_id: section.id, active_only: 1 } }).catch(() => ({})); + if (!activeCheck?.active_version_id && !activeCheck?.item?.active_version_id) { + toast('Testversand nur mit aktiver Version möglich.', false); + return; + } if (window.AdminTestSend && typeof window.AdminTestSend.open === 'function') { window.AdminTestSend.open({ id, name: nm }); } else { diff --git a/public/index.php b/public/index.php index 7ecf57f..eaff8a5 100644 --- a/public/index.php +++ b/public/index.php @@ -161,26 +161,47 @@ require __DIR__ . '/../partials/structure/layout_start.php'; - - -
-

Template bearbeiten

-
- - + + +
+
+

Template Verwaltung

+ ID – +
+ + +
-
- - +
+
+ + + +
+
Versionen
+
+
+
+ + +
+
+ +
+
Vorschau (gewählte Version)
+
+ +
+
- +
diff --git a/src/ApiKernel.php b/src/ApiKernel.php index 71d0ad1..c3378c3 100644 --- a/src/ApiKernel.php +++ b/src/ApiKernel.php @@ -1315,6 +1315,38 @@ class ApiKernel $this->respond(['ok' => true, 'deactivated' => true, 'content_id' => $contentId]); } + private function handleContentVersionsDelete(): void + { + $auth = $this->requireAuth(); + $customerId = (int)($auth['customer_id'] ?? 0); + if ($customerId <= 0) $this->fail('Customer context missing', null, 500); + $versionId = (int)$this->val($this->in, ['id', 'version_id', 'version'], 0); + if ($versionId <= 0) $this->fail('version id required', null, 422); + $contentId = (int)$this->val($this->in, ['content_id', 'content'], 0); + + $table = $this->contentVersionsTable(); + if (!$this->tableExists($table)) $this->fail('Versions table not available', null, 500); + + $sql = "SELECT `id`,`content_id`,`customer_id`,`is_active` FROM `$table` WHERE `id` = :id AND `customer_id` = :cid"; + $params = [':id' => $versionId, ':cid' => $customerId]; + if ($contentId > 0) { + $sql .= " AND `content_id` = :content"; + $params[':content'] = $contentId; + } + $sql .= " LIMIT 1"; + $stmt = $this->pdo->prepare($sql); + $stmt->execute($params); + $row = $stmt->fetch(); + if (!$row) $this->fail('Not found', ['id' => $versionId], 404); + if ((int)($row['is_active'] ?? 0) === 1) { + $this->fail('Active versions cannot be deleted', ['id' => $versionId], 422); + } + + $stmt = $this->pdo->prepare("DELETE FROM `$table` WHERE `id` = :id AND `customer_id` = :cid LIMIT 1"); + $stmt->execute([':id' => $versionId, ':cid' => $customerId]); + $this->respond(['ok' => true, 'deleted' => true, 'id' => $versionId]); + } + private function handleSectionsConfigList(): void { $auth = $this->requireAuth(); @@ -2516,6 +2548,9 @@ class ApiKernel case 'content_versions.deactivate': $this->handleContentVersionsDeactivate(); break; + case 'content_versions.delete': + $this->handleContentVersionsDelete(); + break; /* ---------- CRUD HANDLER ---------- */ default: