Kostenpositionen als Formular-Kapitel speichern

This commit is contained in:
gitea_admin committed 2026-09-20 12:06:20 +02:00
1 parent 6226f2a037
commit 313475afec
2 files changed
+24 -1

No files matched your search

+3 -1
View File
@@ -1186,6 +1186,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo " const municipalityReadObserver = new MutationObserver(() => syncMunicipalityReadValue(contentRoot)); municipalityReadObserver.observe(contentRoot, { childList: true, subtree: true }); syncMunicipalityReadValue(contentRoot);"; echo " const municipalityReadObserver = new MutationObserver(() => syncMunicipalityReadValue(contentRoot)); municipalityReadObserver.observe(contentRoot, { childList: true, subtree: true }); syncMunicipalityReadValue(contentRoot);";
echo " const enhanceMarketCostItems = (root) => { const drawer = getMarketDrawer(); const payload = drawer?.dataset.marketPayload ? JSON.parse(drawer.dataset.marketPayload) : null; const items = Array.isArray(payload?.occurrence?.cost_items) ? payload.occurrence.cost_items : []; root.querySelectorAll('[data-market-edit-form][data-market-edit-scope=participation]').forEach((form) => { if (form.dataset.marketCostItemsEnhanced === 'true') { return; } const field = form.querySelector('[name=cost_items_text]')?.closest('.sg-form-sections-card__field-group'); if (!field) { return; } const state = items.map((item) => Object.assign({}, item)); form._marketCostItems = state; field.innerHTML = '<label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Kostenpositionen</span></label><div data-market-cost-items-editor></div><button class=\"sg-interaction-element sg-button sg-button--active sg-form-sections-card__action\" type=\"button\" data-market-cost-item-add>Kostenposition hinzufügen</button><p class=\"sg-body\" data-market-cost-item-status aria-live=\"polite\"></p>'; const editor = field.querySelector('[data-market-cost-items-editor]'); const render = () => { editor.innerHTML = ''; form._marketCostItems.forEach((item, index) => { const row = document.createElement('div'); row.className = 'sg-form-sections-card__field-group'; row.dataset.marketCostItemRow = 'true'; row.dataset.marketCostItemId = item.id || ''; row.innerHTML = '<label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Art</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" data-market-cost-field=\"cost_type\" value=\"' + marketEscape(item.cost_type || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Beschreibung</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" data-market-cost-field=\"description\" value=\"' + marketEscape(item.description || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Brutto</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"number\" step=\"0.01\" data-market-cost-field=\"gross_amount\" value=\"' + marketEscape(item.gross_amount || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Steuerstatus</span><select class=\"sg-interaction-element sg-input-single-line\" data-market-cost-field=\"tax_status\"><option value=\"tax_free\"' + (item.tax_status === 'tax_free' ? ' selected' : '') + '>Steuerfrei</option><option value=\"taxable\"' + (item.tax_status === 'taxable' ? ' selected' : '') + '>Steuerpflichtig</option></select></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Steuersatz</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"number\" step=\"0.01\" data-market-cost-field=\"tax_rate\" value=\"' + marketEscape(item.tax_rate || '') + '\"></label><div class=\"sg-component-row\"><button class=\"sg-interaction-element sg-button sg-button--process\" type=\"button\" data-market-cost-item-save>Speichern</button>' + (item.id ? '<button class=\"sg-interaction-element sg-button sg-button--warning\" type=\"button\" data-market-cost-item-delete>Löschen</button>' : '<button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-market-cost-item-cancel>Abbrechen</button>') + '</div>'; editor.appendChild(row); }); }; form._marketCostItemRender = render; render(); form.dataset.marketCostItemsEnhanced = 'true'; }); };"; echo " const enhanceMarketCostItems = (root) => { const drawer = getMarketDrawer(); const payload = drawer?.dataset.marketPayload ? JSON.parse(drawer.dataset.marketPayload) : null; const items = Array.isArray(payload?.occurrence?.cost_items) ? payload.occurrence.cost_items : []; root.querySelectorAll('[data-market-edit-form][data-market-edit-scope=participation]').forEach((form) => { if (form.dataset.marketCostItemsEnhanced === 'true') { return; } const field = form.querySelector('[name=cost_items_text]')?.closest('.sg-form-sections-card__field-group'); if (!field) { return; } const state = items.map((item) => Object.assign({}, item)); form._marketCostItems = state; field.innerHTML = '<label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Kostenpositionen</span></label><div data-market-cost-items-editor></div><button class=\"sg-interaction-element sg-button sg-button--active sg-form-sections-card__action\" type=\"button\" data-market-cost-item-add>Kostenposition hinzufügen</button><p class=\"sg-body\" data-market-cost-item-status aria-live=\"polite\"></p>'; const editor = field.querySelector('[data-market-cost-items-editor]'); const render = () => { editor.innerHTML = ''; form._marketCostItems.forEach((item, index) => { const row = document.createElement('div'); row.className = 'sg-form-sections-card__field-group'; row.dataset.marketCostItemRow = 'true'; row.dataset.marketCostItemId = item.id || ''; row.innerHTML = '<label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Art</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" data-market-cost-field=\"cost_type\" value=\"' + marketEscape(item.cost_type || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Beschreibung</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" data-market-cost-field=\"description\" value=\"' + marketEscape(item.description || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Brutto</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"number\" step=\"0.01\" data-market-cost-field=\"gross_amount\" value=\"' + marketEscape(item.gross_amount || '') + '\"></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Steuerstatus</span><select class=\"sg-interaction-element sg-input-single-line\" data-market-cost-field=\"tax_status\"><option value=\"tax_free\"' + (item.tax_status === 'tax_free' ? ' selected' : '') + '>Steuerfrei</option><option value=\"taxable\"' + (item.tax_status === 'taxable' ? ' selected' : '') + '>Steuerpflichtig</option></select></label><label class=\"sg-labeled-input-row\"><span class=\"sg-label\">Steuersatz</span><input class=\"sg-interaction-element sg-input-single-line\" type=\"number\" step=\"0.01\" data-market-cost-field=\"tax_rate\" value=\"' + marketEscape(item.tax_rate || '') + '\"></label><div class=\"sg-component-row\"><button class=\"sg-interaction-element sg-button sg-button--process\" type=\"button\" data-market-cost-item-save>Speichern</button>' + (item.id ? '<button class=\"sg-interaction-element sg-button sg-button--warning\" type=\"button\" data-market-cost-item-delete>Löschen</button>' : '<button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-market-cost-item-cancel>Abbrechen</button>') + '</div>'; editor.appendChild(row); }); }; form._marketCostItemRender = render; render(); form.dataset.marketCostItemsEnhanced = 'true'; }); };";
echo " const marketCostItemObserver = new MutationObserver(() => enhanceMarketCostItems(contentRoot)); marketCostItemObserver.observe(contentRoot, { childList: true, subtree: true }); enhanceMarketCostItems(contentRoot);"; echo " const marketCostItemObserver = new MutationObserver(() => enhanceMarketCostItems(contentRoot)); marketCostItemObserver.observe(contentRoot, { childList: true, subtree: true }); enhanceMarketCostItems(contentRoot);";
echo " const normalizeMarketCostItems = (root) => { root.querySelectorAll('[data-market-edit-form][data-market-edit-scope=participation]').forEach((form) => { const field = form.querySelector('[data-market-cost-items-editor]')?.parentElement; if (!field || field.dataset.marketCostItemsNormalized === 'true') { return; } const chapter = document.createElement('section'); chapter.className = 'sg-form-sections-card__chapter'; const title = document.createElement('h2'); title.className = 'sg-strong sg-form-sections-card__chapter-title'; title.textContent = 'Kostenpositionen'; chapter.appendChild(title); while (field.firstChild) { chapter.appendChild(field.firstChild); } field.replaceWith(chapter); form._marketCostItemsField = chapter; const render = form._marketCostItemRender; form._marketCostItemRender = () => { render(); form.querySelectorAll('[data-market-cost-item-save], [data-market-cost-item-delete], [data-market-cost-item-cancel]').forEach((button) => button.remove()); }; form._marketCostItemRender(); chapter.dataset.marketCostItemsNormalized = 'true'; }); }; const collectMarketCostItems = (form) => Array.from(form.querySelectorAll('[data-market-cost-item-row]')).map((row) => { const item = Object.fromEntries(Array.from(row.querySelectorAll('[data-market-cost-field]')).map((input) => [input.dataset.marketCostField, input.value])); if (row.dataset.marketCostItemId) { item.id = Number(row.dataset.marketCostItemId); } return item; });";
echo " const marketCostItemsNormalizationObserver = new MutationObserver(() => normalizeMarketCostItems(contentRoot)); marketCostItemsNormalizationObserver.observe(contentRoot, { childList: true, subtree: true }); normalizeMarketCostItems(contentRoot);";
echo " if (!marketsBindingsInstalled) {"; echo " if (!marketsBindingsInstalled) {";
echo " marketsBindingsInstalled = true;"; echo " marketsBindingsInstalled = true;";
echo " document.addEventListener('click', async (event) => { const costAdd = event.target.closest('[data-market-cost-item-add]'); if (costAdd) { const form = costAdd.closest('[data-market-edit-form]'); if (form?._marketCostItems) { form._marketCostItems.push({ cost_type: '', description: '', gross_amount: '', tax_status: 'tax_free', tax_rate: '0.00' }); form._marketCostItemRender(); } return; } const costCancel = event.target.closest('[data-market-cost-item-cancel]'); if (costCancel) { const form = costCancel.closest('[data-market-edit-form]'); const row = costCancel.closest('[data-market-cost-item-row]'); const index = Array.from(form.querySelectorAll('[data-market-cost-item-row]')).indexOf(row); if (index >= 0) { form._marketCostItems.splice(index, 1); form._marketCostItemRender(); } return; } const costSave = event.target.closest('[data-market-cost-item-save]'); if (costSave) { const form = costSave.closest('[data-market-edit-form]'); const row = costSave.closest('[data-market-cost-item-row]'); const fields = Object.fromEntries(Array.from(row.querySelectorAll('[data-market-cost-field]')).map((input) => [input.dataset.marketCostField, input.value])); const itemId = row.dataset.marketCostItemId; const status = form.querySelector('[data-market-cost-item-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: itemId ? 'update_cost_item' : 'create_cost_item', id: itemId || form.dataset.marketEditId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } return; } const costDelete = event.target.closest('[data-market-cost-item-delete]'); if (costDelete) { const form = costDelete.closest('[data-market-edit-form]'); const row = costDelete.closest('[data-market-cost-item-row]'); const itemId = row?.dataset.marketCostItemId || ''; if (!itemId || !window.confirm('Diese Kostenposition löschen?')) { return; } const status = form.querySelector('[data-market-cost-item-status]'); if (status) { status.textContent = 'Löschen läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'delete_cost_item', id: itemId, confirmation: 'DELETE' }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Löschen fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } return; } const dayAdd = event.target.closest('[data-market-day-add]'); if (dayAdd) { event.preventDefault(); addMarketDayDraft(); return; } const editButton = event.target.closest('[data-shared-editable-card-edit]'); if (editButton) { window.setTimeout(() => syncMarketDayAddButton(contentRoot), 0); return; } const cancelButton = event.target.closest('[data-shared-editable-card-cancel]'); if (cancelButton) { if (cancelButton.closest('[data-market-edit-form][data-market-edit-draft=\"true\"]')) { window.setTimeout(discardMarketDayDraft, 0); } else { window.setTimeout(() => syncMarketDayAddButton(contentRoot), 0); } } });"; echo " document.addEventListener('click', async (event) => { const costAdd = event.target.closest('[data-market-cost-item-add]'); if (costAdd) { const form = costAdd.closest('[data-market-edit-form]'); if (form?._marketCostItems) { form._marketCostItems.push({ cost_type: '', description: '', gross_amount: '', tax_status: 'tax_free', tax_rate: '0.00' }); form._marketCostItemRender(); } return; } const costCancel = event.target.closest('[data-market-cost-item-cancel]'); if (costCancel) { const form = costCancel.closest('[data-market-edit-form]'); const row = costCancel.closest('[data-market-cost-item-row]'); const index = Array.from(form.querySelectorAll('[data-market-cost-item-row]')).indexOf(row); if (index >= 0) { form._marketCostItems.splice(index, 1); form._marketCostItemRender(); } return; } const costSave = event.target.closest('[data-market-cost-item-save]'); if (costSave) { const form = costSave.closest('[data-market-edit-form]'); const row = costSave.closest('[data-market-cost-item-row]'); const fields = Object.fromEntries(Array.from(row.querySelectorAll('[data-market-cost-field]')).map((input) => [input.dataset.marketCostField, input.value])); const itemId = row.dataset.marketCostItemId; const status = form.querySelector('[data-market-cost-item-status]'); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: itemId ? 'update_cost_item' : 'create_cost_item', id: itemId || form.dataset.marketEditId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } return; } const costDelete = event.target.closest('[data-market-cost-item-delete]'); if (costDelete) { const form = costDelete.closest('[data-market-edit-form]'); const row = costDelete.closest('[data-market-cost-item-row]'); const itemId = row?.dataset.marketCostItemId || ''; if (!itemId || !window.confirm('Diese Kostenposition löschen?')) { return; } const status = form.querySelector('[data-market-cost-item-status]'); if (status) { status.textContent = 'Löschen läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'delete_cost_item', id: itemId, confirmation: 'DELETE' }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Löschen fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } return; } const dayAdd = event.target.closest('[data-market-day-add]'); if (dayAdd) { event.preventDefault(); addMarketDayDraft(); return; } const editButton = event.target.closest('[data-shared-editable-card-edit]'); if (editButton) { window.setTimeout(() => syncMarketDayAddButton(contentRoot), 0); return; } const cancelButton = event.target.closest('[data-shared-editable-card-cancel]'); if (cancelButton) { if (cancelButton.closest('[data-market-edit-form][data-market-edit-draft=\"true\"]')) { window.setTimeout(discardMarketDayDraft, 0); } else { window.setTimeout(() => syncMarketDayAddButton(contentRoot), 0); } } });";
@@ -1193,7 +1195,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-create-form]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-create-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'create', fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const detailResponse = await fetch('/api/market-occurrence.php?id=' + encodeURIComponent(result.occurrence_id), { credentials: 'same-origin' }); const payload = await detailResponse.json(); if (!detailResponse.ok || !payload.ok) { throw new Error(payload.error || 'Die neue Durchführung konnte nicht geladen werden.'); } const drawer = getMarketDrawer(); const title = drawer?.querySelector('[data-market-drawer-title]'); const content = drawer?.querySelector('[data-market-drawer-content]'); if (drawer) { drawer.dataset.marketPayload = JSON.stringify(payload); } if (title) { title.textContent = marketDrawerTitle(payload); } if (content) { content.innerHTML = renderMarketDetailWithActions(payload); applyMarketTabs(content); syncMarketDayAddButton(content); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });"; echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-create-form]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-create-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'create', fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const detailResponse = await fetch('/api/market-occurrence.php?id=' + encodeURIComponent(result.occurrence_id), { credentials: 'same-origin' }); const payload = await detailResponse.json(); if (!detailResponse.ok || !payload.ok) { throw new Error(payload.error || 'Die neue Durchführung konnte nicht geladen werden.'); } const drawer = getMarketDrawer(); const title = drawer?.querySelector('[data-market-drawer-title]'); const content = drawer?.querySelector('[data-market-drawer-content]'); if (drawer) { drawer.dataset.marketPayload = JSON.stringify(payload); } if (title) { title.textContent = marketDrawerTitle(payload); } if (content) { content.innerHTML = renderMarketDetailWithActions(payload); applyMarketTabs(content); syncMarketDayAddButton(content); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });";
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-edit-form][data-market-edit-draft=\"true\"]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-edit-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'create_day', id: form.dataset.marketOccurrenceId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });"; echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-edit-form][data-market-edit-draft=\"true\"]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-edit-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ action: 'create_day', id: form.dataset.marketOccurrenceId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });";
echo " document.addEventListener('click', (event) => { const deleteSubmit = event.target.closest('[data-market-delete-submit]'); if (deleteSubmit) { void deleteMarketOccurrence(deleteSubmit); return; } const sortButton = event.target.closest('[data-markets-sort-button]'); if (sortButton) { event.preventDefault(); void loadFragment({ market_search: getSearchValue(), market_sort: sortButton.dataset.marketsSortColumn || getSortColumn(), market_dir: sortButton.dataset.marketsSortDirection || getSortDirection(), market_limit: getPageSize() }); return; } const loadMore = event.target.closest('[data-markets-load-more-trigger]'); if (loadMore) { event.preventDefault(); void loadFragment({ market_search: getSearchValue(), market_sort: getSortColumn(), market_dir: getSortDirection(), market_limit: getCurrentLimit() + getPageSize() }); return; } const trigger = event.target.closest('[data-market-drawer-open]'); if (trigger) { event.preventDefault(); if (window.matchMedia('(max-width: 767px)').matches && !marketOpeningMobileDetail) { openMarketMobileDetail(trigger); } else { void openMarketDrawer(trigger); } return; } if (event.target.closest('[data-market-drawer-close]')) { closeMarketDrawer(); return; } if (event.target.closest('[data-market-detail-back]')) { if (window.history.state?.marketView === 'detail') { window.history.back(); } else { restoreMarketList(); } return; } });"; echo " document.addEventListener('click', (event) => { const deleteSubmit = event.target.closest('[data-market-delete-submit]'); if (deleteSubmit) { void deleteMarketOccurrence(deleteSubmit); return; } const sortButton = event.target.closest('[data-markets-sort-button]'); if (sortButton) { event.preventDefault(); void loadFragment({ market_search: getSearchValue(), market_sort: sortButton.dataset.marketsSortColumn || getSortColumn(), market_dir: sortButton.dataset.marketsSortDirection || getSortDirection(), market_limit: getPageSize() }); return; } const loadMore = event.target.closest('[data-markets-load-more-trigger]'); if (loadMore) { event.preventDefault(); void loadFragment({ market_search: getSearchValue(), market_sort: getSortColumn(), market_dir: getSortDirection(), market_limit: getCurrentLimit() + getPageSize() }); return; } const trigger = event.target.closest('[data-market-drawer-open]'); if (trigger) { event.preventDefault(); if (window.matchMedia('(max-width: 767px)').matches && !marketOpeningMobileDetail) { openMarketMobileDetail(trigger); } else { void openMarketDrawer(trigger); } return; } if (event.target.closest('[data-market-drawer-close]')) { closeMarketDrawer(); return; } if (event.target.closest('[data-market-detail-back]')) { if (window.history.state?.marketView === 'detail') { window.history.back(); } else { restoreMarketList(); } return; } });";
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-edit-form]'); if (!form) { return; } event.preventDefault(); const status = form.querySelector('[data-market-edit-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ scope: form.dataset.marketEditScope, id: form.dataset.marketEditId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });"; echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-edit-form]'); if (!form) { return; } event.preventDefault(); const status = form.querySelector('[data-market-edit-status]'); const fields = Object.fromEntries(new FormData(form).entries()); if (form.dataset.marketEditScope === 'participation') { fields.cost_items = JSON.stringify(collectMarketCostItems(form)); } if (status) { status.textContent = 'Speichern läuft …'; } try { const response = await fetch('/api/market-occurrence.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': contentRoot.querySelector('[data-market-csrf-token]')?.dataset.marketCsrfToken || '' }, body: JSON.stringify({ scope: form.dataset.marketEditScope, id: form.dataset.marketEditId, fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } const trigger = contentRoot.querySelector('[data-market-drawer-open][aria-expanded=\"true\"]'); if (trigger) { await openMarketDrawer(trigger); } } catch (error) { if (status) { status.textContent = marketEscape(error.message); } } });";
echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-market-drawer-backdrop]')) { closeMarketDrawer(); } });"; echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-market-drawer-backdrop]')) { closeMarketDrawer(); } });";
echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeMarketDrawer(); restoreMarketList(); } });"; echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeMarketDrawer(); restoreMarketList(); } });";
echo " window.addEventListener('popstate', () => { if (marketDetailOpen) { restoreMarketList(); } });"; echo " window.addEventListener('popstate', () => { if (marketDetailOpen) { restoreMarketList(); } });";
+21
View File
@@ -98,6 +98,27 @@ if ($requestMethod === 'POST') {
market_management_set_occurrence_contact($pdo, $contactFields); market_management_set_occurrence_contact($pdo, $contactFields);
} }
if ($fields !== []) { if ($fields !== []) {
$costItems = null;
if (array_key_exists('cost_items', $fields)) {
$costItems = is_string($fields['cost_items']) ? json_decode($fields['cost_items'], true) : $fields['cost_items'];
if (!is_array($costItems)) {
throw new InvalidArgumentException('Kostenpositionen sind ungültig.');
}
unset($fields['cost_items']);
foreach ($costItems as $costItem) {
if (!is_array($costItem)) {
throw new InvalidArgumentException('Kostenposition ist ungültig.');
}
if (isset($costItem['id'])) {
$costItemId = market_management_require_positive_id($costItem['id'], 'cost_item_id');
unset($costItem['id']);
market_management_update_cost_item($pdo, $costItemId, $costItem);
} else {
$costItem['occurrence_id'] = $id;
market_management_create_cost_item($pdo, $costItem);
}
}
}
market_management_update_occurrence($pdo, $id, $fields); market_management_update_occurrence($pdo, $id, $fields);
} }
} else { } else {