Centralize editable card visibility toggle

This commit is contained in:
gitea_admin committed 2026-09-30 12:13:01 +02:00
1 parent cc534b0ce4
commit dea0cd9e39
4 files changed
+76 -11

No files matched your search

@@ -13,6 +13,7 @@ Das Pattern kapselt nur die technische Interaktion:
- optionale Loeschbestaetigung mit konfigurierbarem Token
- Deaktivieren der Loeschaktion bis zur gueltigen Bestaetigung
- gleichzeitiges Bearbeiten mehrerer Karten
- optionales Ein- und Ausblenden des Karteninhalts
Fachliche Aktionen, API-Aufrufe, Validierung und die Aktualisierung fachlicher
Daten bleiben beim owning Modul.
@@ -26,6 +27,9 @@ Bedienelemente verwenden folgende Attribute:
- `data-shared-editable-card-cancel`
- `data-shared-editable-card-read`
- `data-shared-editable-card-form`
- optional `data-shared-editable-card-collapsible`
- optional `data-shared-editable-card-collapsed`
- optional `data-shared-editable-card-toggle`
- optional `data-shared-editable-card-delete`
- optional `data-shared-editable-card-delete-confirmation`
- optional `data-shared-editable-card-delete-input`
@@ -41,6 +45,20 @@ Das Loesch-Token wird je Karte mit
Loeschen bleibt im owning Modul und verwendet den bestehenden fachlichen
Handler.
## Optionales Ein- und Ausblenden
Eine Karte mit `data-shared-editable-card-collapsible` kann ihren Inhalt
ein- und ausblenden. Mit `data-shared-editable-card-collapsed` wird der initial
eingeklappte Zustand markiert. Der Toggle verwendet die Beschriftungen
„Anzeigen“ und „Verbergen“. Beim Wechsel in den Bearbeitungsmodus wird der
Inhalt eingeblendet und der Toggle deaktiviert. Nach dem Bearbeiten bleibt der
aktuelle Sichtbarkeitszustand erhalten.
Die zentrale Komponente stellt dafür `setVisibility(card, visible)`,
`isVisible(card)` und `setToggleEnabled(card, enabled)` bereit. Fachmodule
verwenden diese Methoden nur für fachliche Folgeaktionen wie das erneute
Rendern nach dem Speichern.
## Nutzung im Lager
Im Lager-Drawer verwenden die aktive Charge und offene Chargen das Pattern.
+5 -6
View File
@@ -965,7 +965,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo '</div>';
echo '</section>';
echo '<script src="/scripts/help-icon-overlays.js"></script>';
echo '<script src="/scripts/shared-editable-action-card.js?v=20260930-1"></script>';
echo '<script src="/scripts/shared-editable-action-card.js?v=20260930-2"></script>';
echo '<script src="/scripts/shared-mobile-detail.js?v=20260925-1"></script>';
echo '<script src="/scripts/large-table-column-resize.js?v=' . filemtime(__DIR__ . '/../../../../public/scripts/large-table-column-resize.js') . '"></script>';
echo '<script>';
@@ -1206,9 +1206,8 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo " const marketChecklistChapters = (checklist) => Array.isArray(checklist.chapters) ? checklist.chapters : [];";
echo " const marketChecklistReadBody = (checklist) => { const items = Array.isArray(checklist.items) ? checklist.items : []; const chapterSections = marketChecklistChapters(checklist).map((chapter) => { const chapterItems = items.filter((item) => item.chapter_key === chapter.key); const rows = chapterItems.map((item) => { const index = items.indexOf(item); return '<div class=\"market-checklist-item\"><button class=\"sg-checkbox-field sg-checkbox-field--on-grey' + (item.is_completed ? ' sg-form-active' : '') + '\" type=\"button\" role=\"checkbox\" aria-checked=\"' + (item.is_completed ? 'true' : 'false') + '\" aria-label=\"Checklistenpunkt ' + (index + 1) + '\" data-checklist-read-checkbox=\"' + index + '\"><span class=\"sg-checkbox-field__mark\" aria-hidden=\"true\">✓</span></button><span class=\"sg-body\">' + marketEscape(item.label || '') + '</span></div>'; }).join(''); return '<section class=\"sg-form-sections-card__chapter market-checklist-chapter\" data-checklist-chapter=\"' + marketEscape(chapter.key) + '\"><h3 class=\"sg-form-sections-card__chapter-title\">' + marketEscape(chapter.label) + '</h3><div class=\"market-checklist-list\">' + rows + '</div></section>'; }).join(''); return '<div class=\"market-checklist-chapters\">' + chapterSections + '</div><div class=\"market-checklist-comment\"><div class=\"sg-strong\">Kommentar</div><p class=\"sg-body\">' + marketDisplay(checklist.comment) + '</p></div><p class=\"sg-body\" data-checklist-read-status aria-live=\"polite\"></p>'; };";
echo " const marketChecklistEditBody = (checklist) => { const items = Array.isArray(checklist.items) ? checklist.items : []; const sections = marketChecklistChapters(checklist).map((chapter) => { const chapterItems = items.filter((item) => item.chapter_key === chapter.key); const rows = chapterItems.map((item) => '<label class=\"sg-labeled-input-row market-checklist-edit-row\" data-checklist-edit-item data-checklist-chapter-key=\"' + marketEscape(chapter.key) + '\"><span class=\"sg-label\"><button class=\"sg-checkbox-field sg-checkbox-field--on-grey' + (item.is_completed ? ' sg-form-active' : '') + '\" type=\"button\" role=\"checkbox\" aria-checked=\"' + (item.is_completed ? 'true' : 'false') + '\" aria-label=\"Checklistenpunkt abhaken\" data-checklist-edit-checkbox><span class=\"sg-checkbox-field__mark\" aria-hidden=\"true\">✓</span></button></span><span class=\"market-checklist-edit-control\"><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" value=\"' + marketEscape(item.label || '') + '\" data-checklist-label><button class=\"sg-interaction-element sg-button sg-button--warning market-checklist-delete\" type=\"button\" aria-label=\"Checklistenpunkt löschen\" data-checklist-delete-item>−</button></span></label>').join(''); const trailing = '<label class=\"sg-labeled-input-row market-checklist-edit-row market-checklist-edit-row--trailing\" data-checklist-edit-item data-checklist-trailing-item data-checklist-chapter-key=\"' + marketEscape(chapter.key) + '\"><span class=\"sg-label\"><button class=\"sg-checkbox-field sg-checkbox-field--on-grey\" type=\"button\" role=\"checkbox\" aria-checked=\"false\" aria-label=\"Neuen Checklistenpunkt abhaken\" data-checklist-edit-checkbox><span class=\"sg-checkbox-field__mark\" aria-hidden=\"true\">✓</span></button></span><span class=\"market-checklist-edit-control\"><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" value=\"\" aria-label=\"Neuer Checklistenpunkt\" data-checklist-label></span></label>'; return '<section class=\"sg-form-sections-card__chapter market-checklist-chapter\" data-checklist-chapter=\"' + marketEscape(chapter.key) + '\"><h3 class=\"sg-form-sections-card__chapter-title\">' + marketEscape(chapter.label) + '</h3><div class=\"market-checklist-edit-list\" data-checklist-edit-list data-checklist-chapter-key=\"' + marketEscape(chapter.key) + '\">' + rows + trailing + '</div></section>'; }).join(''); return sections + '<label class=\"sg-labeled-input-row market-checklist-comment-field\"><span class=\"sg-label\">Kommentar</span><textarea class=\"sg-interaction-element sg-input-multi-line\" name=\"comment\" rows=\"4\" data-checklist-comment>' + marketEscape(checklist.comment || '') + '</textarea></label>'; };";
echo " const marketChecklistCard = (id, checklist) => { const headerClass = marketChecklistSignalSegment(checklist.items); const actions = '<button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-checklist-open aria-expanded=\"false\">Anzeigen</button>' + marketEditButton('checklist', id); return '<article class=\"sg-card sg-card--content-card market-detail-card market-checklist-card--collapsed\" data-component=\"content-card\" data-shared-editable-action-card data-checklist-card><header class=\"sg-card-segment sg-card-segment--header ' + headerClass + ' sg-object-card__header\"><div class=\"sg-strong\">Checkliste</div><div class=\"sg-component-row\">' + actions + '</div></header><div class=\"sg-card-segment sg-card-segment--body sg-card-segment--gray\" data-shared-editable-card-read hidden>' + marketChecklistReadBody(checklist) + '</div><div class=\"sg-card-segment sg-card-segment--body sg-card-segment--gray\" data-shared-editable-card-form hidden><form class=\"sg-form-sections-card market-checklist-form\" data-market-checklist-form data-market-checklist-id=\"' + marketEscape(id) + '\"><div class=\"sg-form-sections-card__body\" data-pattern-part=\"form-body\">' + marketChecklistEditBody(checklist) + '</div><footer class=\"sg-form-sections-card__actions-segment\" data-pattern-part=\"form-actions-segment\"><div class=\"sg-form-sections-card__actions\" data-pattern-part=\"form-actions\"><button class=\"sg-interaction-element sg-button sg-button--active sg-form-sections-card__action\" type=\"button\" data-shared-editable-card-cancel>Abbrechen</button><button class=\"sg-interaction-element sg-button sg-button--process sg-form-sections-card__action\" type=\"submit\">Speichern</button></div></footer><p class=\"sg-body\" data-market-checklist-status aria-live=\"polite\"></p></form></div></article>'; };";
echo " const setChecklistVisibility = (card, visible, showRead = visible) => { if (!card) { return; } card.classList.toggle('market-checklist-card--collapsed', !visible); const readView = card.querySelector('[data-shared-editable-card-read]'); const toggle = card.querySelector('[data-checklist-open]'); if (readView) { readView.hidden = !showRead; } if (toggle) { toggle.textContent = visible ? 'Verbergen' : 'Anzeigen'; toggle.setAttribute('aria-expanded', String(visible)); } };";
echo " const setChecklistOpenButtonEnabled = (card, enabled) => { const toggle = card?.querySelector('[data-checklist-open]'); if (!toggle) { return; } toggle.disabled = !enabled; toggle.setAttribute('aria-disabled', String(!enabled)); toggle.classList.toggle('sg-button--inactive', !enabled); };";
echo " const marketChecklistCard = (id, checklist) => { const headerClass = marketChecklistSignalSegment(checklist.items); const actions = '<button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-shared-editable-card-toggle aria-expanded=\"false\">Anzeigen</button>' + marketEditButton('checklist', id); return '<article class=\"sg-card sg-card--content-card market-detail-card\" data-component=\"content-card\" data-shared-editable-action-card data-shared-editable-card-collapsible data-shared-editable-card-collapsed data-checklist-card><header class=\"sg-card-segment sg-card-segment--header ' + headerClass + ' sg-object-card__header\"><div class=\"sg-strong\">Checkliste</div><div class=\"sg-component-row\">' + actions + '</div></header><div class=\"sg-card-segment sg-card-segment--body sg-card-segment--gray\" data-shared-editable-card-read hidden>' + marketChecklistReadBody(checklist) + '</div><div class=\"sg-card-segment sg-card-segment--body sg-card-segment--gray\" data-shared-editable-card-form hidden><form class=\"sg-form-sections-card market-checklist-form\" data-market-checklist-form data-market-checklist-id=\"' + marketEscape(id) + '\"><div class=\"sg-form-sections-card__body\" data-pattern-part=\"form-body\">' + marketChecklistEditBody(checklist) + '</div><footer class=\"sg-form-sections-card__actions-segment\" data-pattern-part=\"form-actions-segment\"><div class=\"sg-form-sections-card__actions\" data-pattern-part=\"form-actions\"><button class=\"sg-interaction-element sg-button sg-button--active sg-form-sections-card__action\" type=\"button\" data-shared-editable-card-cancel>Abbrechen</button><button class=\"sg-interaction-element sg-button sg-button--process sg-form-sections-card__action\" type=\"submit\">Speichern</button></div></footer><p class=\"sg-body\" data-market-checklist-status aria-live=\"polite\"></p></form></div></article>'; };";
echo " const setChecklistVisibility = (card, visible, showRead = visible) => { if (!window.SharedEditableActionCard) { return; } window.SharedEditableActionCard.setVisibility(card, visible); if (!showRead) { const readView = card?.querySelector('[data-shared-editable-card-read]'); if (readView) { readView.hidden = true; } } };";
echo " const setChecklistSignal = (card, items) => { const header = card?.querySelector('header'); if (!header) { return; } header.classList.remove('sg-card-segment--signal-red', 'sg-card-segment--signal-yellow', 'sg-card-segment--signal-green'); header.classList.add(marketChecklistSignalSegment(items)); };";
echo " const marketStatusCard = (id, values) => { const headerClass = marketStatusSegment(values.status); const textClass = headerClass === 'sg-card-segment--gray' ? '' : 'sg-text-on-dark'; const actions = marketEditButton('status', id); const header = '<header class=\"sg-card-segment sg-card-segment--header ' + headerClass + ' sg-object-card__header\" data-component-part=\"card-header\"><div class=\"sg-strong ' + textClass + '\">Status: ' + marketEscape(marketStatusLabel(values.status)) + '</div><div class=\"sg-component-row\">' + actions + '</div></header>'; return '<article class=\"sg-card sg-card--content-card market-detail-card\" data-component=\"content-card\" data-shared-editable-action-card>' + header + '<div class=\"sg-card-segment sg-card-segment--body sg-card-segment--gray\" data-shared-editable-card-form hidden>' + marketEditForm('status', id, values) + '</div></article>'; };";
echo " const marketEditButton = () => '<button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-shared-editable-card-edit>Bearbeiten</button>';";
@@ -1246,9 +1245,9 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo " const ensureChecklistTrailingItem = (form, list) => { const targetList = list || form?.querySelector('[data-checklist-edit-list]'); if (!targetList) { return; } const trailing = targetList.querySelector('[data-checklist-trailing-item]'); if (!trailing || (trailing.querySelector('[data-checklist-label]')?.value || '').trim() !== '') { const item = document.createElement('label'); item.className = 'sg-labeled-input-row market-checklist-edit-row market-checklist-edit-row--trailing'; item.dataset.checklistEditItem = ''; item.dataset.checklistTrailingItem = ''; item.dataset.checklistChapterKey = targetList.dataset.checklistChapterKey || ''; item.innerHTML = '<span class=\"sg-label\"><button class=\"sg-checkbox-field sg-checkbox-field--on-grey\" type=\"button\" role=\"checkbox\" aria-checked=\"false\" aria-label=\"Neuen Checklistenpunkt abhaken\" data-checklist-edit-checkbox><span class=\"sg-checkbox-field__mark\" aria-hidden=\"true\">✓</span></button></span><span class=\"market-checklist-edit-control\"><input class=\"sg-interaction-element sg-input-single-line\" type=\"text\" value=\"\" aria-label=\"Neuer Checklistenpunkt\" data-checklist-label></span>'; targetList.append(item); } };";
echo " const setChecklistCheckbox = (button, checked) => { if (!button) { return; } button.setAttribute('aria-checked', String(checked)); button.classList.toggle('sg-form-active', checked); };";
echo " const persistChecklistReadToggle = async (button) => { const drawer = getMarketDrawer(); const card = button.closest('[data-checklist-card]'); const status = card?.querySelector('[data-checklist-read-status]'); const index = Number(button.dataset.checklistReadCheckbox); let payload = {}; try { payload = drawer?.dataset.marketPayload ? JSON.parse(drawer.dataset.marketPayload) : {}; } catch (error) { return; } const sourceItems = Array.isArray(payload.occurrence?.checklist?.items) ? payload.occurrence.checklist.items : []; if (!sourceItems[index]) { return; } const previous = button.getAttribute('aria-checked') === 'true'; const next = !previous; setChecklistCheckbox(button, next); setChecklistCheckbox(card.querySelectorAll('[data-checklist-edit-checkbox]')[index], next); const items = Array.from(card.querySelectorAll('[data-checklist-read-checkbox]')).map((checkbox, itemIndex) => ({ chapter_key: sourceItems[itemIndex]?.chapter_key || '', label: sourceItems[itemIndex]?.label || '', is_completed: checkbox.getAttribute('aria-checked') === 'true' })); setChecklistSignal(card, items); button.disabled = true; 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: 'update_checklist', id: payload.occurrence.id, items: items.map((item) => ({ chapter_key: item.chapter_key, label: item.label || '', is_completed: Boolean(item.is_completed) })), comment: payload.occurrence.checklist.comment || '' }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } payload.occurrence.checklist = result.checklist; if (drawer) { drawer.dataset.marketPayload = JSON.stringify(payload); } if (status) { status.textContent = ''; } } catch (error) { setChecklistCheckbox(button, previous); setChecklistCheckbox(card.querySelectorAll('[data-checklist-edit-checkbox]')[index], previous); const rollbackItems = Array.from(card.querySelectorAll('[data-checklist-read-checkbox]')).map((checkbox, itemIndex) => ({ chapter_key: sourceItems[itemIndex]?.chapter_key || '', label: sourceItems[itemIndex]?.label || '', is_completed: checkbox.getAttribute('aria-checked') === 'true' })); setChecklistSignal(card, rollbackItems); if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } finally { button.disabled = false; } };";
echo " document.addEventListener('click', (event) => { const checklistOpen = event.target.closest('[data-checklist-open]'); if (checklistOpen) { const card = checklistOpen.closest('[data-checklist-card]'); const visible = !card?.classList.contains('market-checklist-card--collapsed'); setChecklistVisibility(card, !visible); if (!visible) { card?.querySelector('[data-checklist-read-checkbox]')?.focus(); } return; } const checklistEdit = event.target.closest('[data-checklist-card] [data-shared-editable-card-edit]'); if (checklistEdit) { const checklistCard = checklistEdit.closest('[data-checklist-card]'); setChecklistVisibility(checklistCard, true, false); setChecklistOpenButtonEnabled(checklistCard, false); return; } const checklistCancel = event.target.closest('[data-checklist-card] [data-shared-editable-card-cancel]'); if (checklistCancel) { const checklistCard = checklistCancel.closest('[data-checklist-card]'); setChecklistVisibility(checklistCard, true); setChecklistOpenButtonEnabled(checklistCard, true); return; } const checkbox = event.target.closest('[data-checklist-edit-checkbox]'); if (checkbox) { event.preventDefault(); setChecklistCheckbox(checkbox, checkbox.getAttribute('aria-checked') !== 'true'); const editCard = checkbox.closest('[data-checklist-card]'); setChecklistSignal(editCard, Array.from(editCard?.querySelectorAll('[data-checklist-edit-item]') || []).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' }))); return; } const readCheckbox = event.target.closest('[data-checklist-read-checkbox]'); if (readCheckbox) { event.preventDefault(); void persistChecklistReadToggle(readCheckbox); return; } const deleteItem = event.target.closest('[data-checklist-delete-item]'); if (deleteItem) { const list = deleteItem.closest('[data-checklist-edit-list]'); deleteItem.closest('[data-checklist-edit-item]')?.remove(); ensureChecklistTrailingItem(deleteItem.closest('[data-market-checklist-form]'), list); const deleteCard = deleteItem.closest('[data-checklist-card]'); setChecklistSignal(deleteCard, Array.from(deleteCard?.querySelectorAll('[data-checklist-edit-item]') || []).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' }))); } });";
echo " document.addEventListener('click', (event) => { const checkbox = event.target.closest('[data-checklist-edit-checkbox]'); if (checkbox) { event.preventDefault(); setChecklistCheckbox(checkbox, checkbox.getAttribute('aria-checked') !== 'true'); const editCard = checkbox.closest('[data-checklist-card]'); setChecklistSignal(editCard, Array.from(editCard?.querySelectorAll('[data-checklist-edit-item]') || []).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' }))); return; } const readCheckbox = event.target.closest('[data-checklist-read-checkbox]'); if (readCheckbox) { event.preventDefault(); void persistChecklistReadToggle(readCheckbox); return; } const deleteItem = event.target.closest('[data-checklist-delete-item]'); if (deleteItem) { const list = deleteItem.closest('[data-checklist-edit-list]'); deleteItem.closest('[data-checklist-edit-item]')?.remove(); ensureChecklistTrailingItem(deleteItem.closest('[data-market-checklist-form]'), list); const deleteCard = deleteItem.closest('[data-checklist-card]'); setChecklistSignal(deleteCard, Array.from(deleteCard?.querySelectorAll('[data-checklist-edit-item]') || []).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' }))); } });";
echo " document.addEventListener('input', (event) => { const input = event.target.closest('[data-market-checklist-form] [data-checklist-label]'); if (!input) { return; } const form = input.closest('[data-market-checklist-form]'); const item = input.closest('[data-checklist-edit-item]'); const list = input.closest('[data-checklist-edit-list]'); if (item?.hasAttribute('data-checklist-trailing-item') && input.value.trim() !== '') { item.removeAttribute('data-checklist-trailing-item'); } ensureChecklistTrailingItem(form, list); const card = input.closest('[data-checklist-card]'); setChecklistSignal(card, Array.from(card?.querySelectorAll('[data-checklist-edit-item]') || []).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' }))); });";
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-checklist-form]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-checklist-status]'); const checklistWasVisible = form.closest('[data-checklist-card]')?.classList.contains('market-checklist-card--collapsed') === false; const items = Array.from(form.querySelectorAll('[data-checklist-edit-item]')).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ chapter_key: item.dataset.checklistChapterKey || item.closest('[data-checklist-edit-list]')?.dataset.checklistChapterKey || '', label: item.querySelector('[data-checklist-label]')?.value || '', is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' })); 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: 'update_checklist', id: form.dataset.marketChecklistId, items, comment: form.querySelector('[data-checklist-comment]')?.value || '' }) }); 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); if (checklistWasVisible) { setChecklistVisibility(getMarketDrawer()?.querySelector('[data-checklist-card]'), true); } } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } });";
echo " document.addEventListener('submit', async (event) => { const form = event.target.closest('[data-market-checklist-form]'); if (!form) { return; } event.preventDefault(); event.stopImmediatePropagation(); const status = form.querySelector('[data-market-checklist-status]'); const checklistWasVisible = window.SharedEditableActionCard?.isVisible(form.closest('[data-checklist-card]')) === true; const items = Array.from(form.querySelectorAll('[data-checklist-edit-item]')).filter((item) => !item.hasAttribute('data-checklist-trailing-item')).map((item) => ({ chapter_key: item.dataset.checklistChapterKey || item.closest('[data-checklist-edit-list]')?.dataset.checklistChapterKey || '', label: item.querySelector('[data-checklist-label]')?.value || '', is_completed: item.querySelector('[data-checklist-edit-checkbox]')?.getAttribute('aria-checked') === 'true' })); 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: 'update_checklist', id: form.dataset.marketChecklistId, items, comment: form.querySelector('[data-checklist-comment]')?.value || '' }) }); 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); if (checklistWasVisible) { setChecklistVisibility(getMarketDrawer()?.querySelector('[data-checklist-card]'), true); } } } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } });";
echo " const marketDrawerTitle = (payload) => { const marketName = payload.market?.name || 'Marktdurchführung'; const dates = (Array.isArray(payload.occurrence?.days) ? payload.occurrence.days : []).map((day) => marketDate(day.date)).filter(Boolean); return dates.length ? marketName + ', Marktdurchführung ' + dates.join(', ') : marketName; };";
echo " const openMarketDrawer = async (trigger) => { const drawer = getMarketDrawer(); if (!drawer) { return; } const title = drawer.querySelector('[data-market-drawer-title]'); const content = drawer.querySelector('[data-market-drawer-content]'); setMarketDrawerState(true); if (title) { title.textContent = 'Marktdurchführung'; } if (content) { content.innerHTML = '<p class=\"sg-body\">Marktdetails werden geladen …</p>'; } try { const response = await fetch('/api/market-occurrence.php?id=' + encodeURIComponent(trigger.dataset.marketOccurrenceId || ''), { credentials: 'same-origin' }); const payload = await response.json(); if (!response.ok || !payload.ok) { throw new Error(payload.error || 'Marktdetails konnten nicht geladen werden.'); } 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 (content) { content.innerHTML = '<p class=\"sg-body\">' + marketEscape(error.message) + '</p>'; } } contentRoot.querySelectorAll('[data-market-drawer-open]').forEach((button) => button.setAttribute('aria-expanded', String(button === trigger))); };";
echo " const openNewMarketDrawer = async () => { const drawer = getMarketDrawer(); if (!drawer) { return; } const title = drawer.querySelector('[data-market-drawer-title]'); const content = drawer.querySelector('[data-market-drawer-content]'); setMarketDrawerState(true); if (title) { title.textContent = 'Neue Marktdurchführung'; } if (content) { content.innerHTML = '<p class=\"sg-body\">Pflichtangaben werden geladen …</p>'; } try { const response = await fetch('/api/market-occurrence.php?action=create_options', { credentials: 'same-origin' }); const payload = await response.json(); if (!response.ok || !payload.ok) { throw new Error(payload.error || 'Pflichtangaben konnten nicht geladen werden.'); } if (content) { content.innerHTML = marketCreateForm(payload.market_definitions || []); } } catch (error) { if (content) { content.innerHTML = '<p class=\"sg-body\">' + marketEscape(error.message) + '</p>'; } } };";
-4
View File
@@ -438,10 +438,6 @@ body.sg-lager-mobile-detail-open > :not([data-lager-mobile-detail-host]) {
gap: var(--spacing-small);
}
.market-checklist-card--collapsed [data-shared-editable-card-read] {
display: none;
}
.market-checklist-item,
.market-checklist-edit-item {
display: flex;
+53 -1
View File
@@ -6,6 +6,35 @@
const getCard = (element) => element.closest(cardSelector);
const setToggleEnabled = (card, enabled) => {
const toggle = card?.querySelector('[data-shared-editable-card-toggle]');
if (!toggle) {
return;
}
toggle.disabled = !enabled;
toggle.setAttribute('aria-disabled', String(!enabled));
toggle.classList.toggle('sg-button--inactive', !enabled);
};
const setVisibility = (card, visible) => {
if (!card || !card.hasAttribute('data-shared-editable-card-collapsible')) {
return;
}
const readView = card.querySelector('[data-shared-editable-card-read]');
const toggle = card.querySelector('[data-shared-editable-card-toggle]');
card.dataset.sharedEditableCardVisible = String(visible);
card.classList.toggle('shared-editable-action-card--collapsed', !visible);
if (readView && card.querySelector('[data-shared-editable-card-form]')?.hidden !== false) {
readView.hidden = !visible;
}
if (toggle) {
toggle.textContent = visible ? 'Verbergen' : 'Anzeigen';
toggle.setAttribute('aria-expanded', String(visible));
}
};
const isVisible = (card) => card?.dataset.sharedEditableCardVisible === 'true';
const getScrollContainer = (element) => {
let candidate = element?.parentElement || null;
@@ -66,6 +95,15 @@
const editView = card.querySelector('[data-shared-editable-card-form]');
const editButtons = card.querySelectorAll('[data-shared-editable-card-edit]');
if (card.hasAttribute('data-shared-editable-card-collapsible')) {
if (enabled) {
setVisibility(card, true);
} else if (!card.dataset.sharedEditableCardVisible) {
setVisibility(card, !card.hasAttribute('data-shared-editable-card-collapsed'));
}
setToggleEnabled(card, !enabled);
}
if (readView) {
readView.hidden = enabled;
}
@@ -77,6 +115,10 @@
editButton.setAttribute('aria-disabled', String(enabled));
editButton.classList.toggle('sg-button--inactive', enabled);
});
if (card.hasAttribute('data-shared-editable-card-collapsible') && enabled && readView) {
readView.hidden = true;
}
};
const resetDeleteConfirmation = (card) => {
@@ -140,6 +182,16 @@
root.dataset.sharedEditableActionCardsInitialized = 'true';
root.querySelectorAll(cardSelector).forEach((card) => setEditMode(card, false));
root.addEventListener('click', (event) => {
const toggle = event.target.closest('[data-shared-editable-card-toggle]');
if (toggle && root.contains(toggle)) {
const card = getCard(toggle);
if (card?.querySelector('[data-shared-editable-card-form]')?.hidden === false) {
return;
}
setVisibility(card, !isVisible(card));
return;
}
const edit = event.target.closest('[data-shared-editable-card-edit]');
if (edit && root.contains(edit)) {
const card = getCard(edit);
@@ -185,5 +237,5 @@
});
};
window.SharedEditableActionCard = { init, resetDeleteConfirmation, setEditMode };
window.SharedEditableActionCard = { init, resetDeleteConfirmation, setEditMode, setToggleEnabled, setVisibility, isVisible };
}());