Implement market occurrence drawer
This commit is contained in:
1 parent
7178c4af53
commit
49fd392f79
3 files changed
+169
-3
No files matched your search
@@ -133,7 +133,7 @@ function auth_render_markets_large_table(array $marketTable): string
|
||||
}
|
||||
$html[] = '<div class="' . $rowClass . '" role="row" data-component-part="large-table-row">';
|
||||
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html((string) ($row['market_name'] ?? '')) . '</div>';
|
||||
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html(auth_format_market_date($row['earliest_date'] ?? null, $row['latest_date'] ?? null)) . '</div>';
|
||||
$html[] = '<div class="sg-large-table__cell" role="cell"><a class="sg-hyperlink" href="#" aria-haspopup="dialog" aria-expanded="false" data-market-drawer-open="true" data-market-occurrence-id="' . (int) ($row['id'] ?? 0) . '">' . auth_escape_html(auth_format_market_date($row['earliest_date'] ?? null, $row['latest_date'] ?? null)) . '</a></div>';
|
||||
$html[] = '<div class="sg-large-table__cell" role="cell">' . auth_escape_html((string) ($row['place'] ?? '')) . '</div>';
|
||||
$status = (string) ($row['status'] ?? '');
|
||||
$statusSignalClass = trim(auth_market_status_signal_class($status));
|
||||
@@ -151,7 +151,12 @@ function auth_render_markets_large_table(array $marketTable): string
|
||||
|
||||
function auth_render_markets_content(array $marketTable): string
|
||||
{
|
||||
return '<div data-markets-content="true">' . auth_render_markets_large_table($marketTable) . '</div>';
|
||||
return '<div data-markets-content="true" data-market-csrf-token="' . auth_escape_html(auth_csrf_token()) . '">' . auth_render_markets_large_table($marketTable)
|
||||
. '<div class="sg-card-list-page-drawer-backdrop" aria-hidden="true" data-market-drawer-backdrop="true" data-open="false"></div>'
|
||||
. '<aside class="sg-card-list-page-drawer" aria-label="Marktdurchführung-Details" aria-hidden="true" data-market-drawer="true" data-open="false">'
|
||||
. '<div class="sg-card-list-page-drawer__header"><h2 class="sg-heading-h2 sg-card-list-page-drawer__title" data-market-drawer-title>Marktdurchführung</h2><button class="sg-interaction-element sg-hyperlink" type="button" data-market-drawer-close>schliessen</button></div>'
|
||||
. '<div class="sg-card-list-page-drawer__content" data-market-drawer-content><p class="sg-body">Marktdetails werden geladen …</p></div>'
|
||||
. '</aside></div>';
|
||||
}
|
||||
|
||||
function auth_render_bestellungen_large_table(array $bestellungenTable): string
|
||||
@@ -1027,14 +1032,38 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $best
|
||||
echo " const getSortDirection = () => (getTable()?.dataset.marketsSortDirection || 'ASC').toUpperCase();";
|
||||
echo " const getCurrentLimit = () => parseInt(getTable()?.dataset.marketsLimit || '20', 10);";
|
||||
echo " const getPageSize = () => parseInt(getTable()?.dataset.marketsPageSize || '20', 10);";
|
||||
echo " const marketEscape = (value) => String(value ?? '').replace(/[&<>\"']/g, (character) => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' }[character] || character));";
|
||||
echo " const marketDisplay = (value) => value === null || value === undefined || value === '' ? 'Nicht hinterlegt' : marketEscape(value);";
|
||||
echo " const marketDate = (value) => { if (!value) { return 'Nicht hinterlegt'; } const parts = String(value).split('-'); return parts.length === 3 ? parts[2] + '.' + parts[1] + '.' + parts[0] : marketEscape(value); };";
|
||||
echo " const marketStatusLabel = (value) => ({ open: 'Offen', waitlist: 'Warteliste', registered: 'Angemeldet', confirmation_pending: 'Bestätigung ausstehend', confirmed: 'Bestätigt', cancelled: 'Abgesagt', completed: 'Abgeschlossen' }[value] || value || 'Nicht hinterlegt');";
|
||||
echo " const marketStatusSegment = (value) => ({ waitlist: 'sg-card-segment--signal-red', registered: 'sg-card-segment--signal-yellow', confirmation_pending: 'sg-card-segment--signal-yellow', confirmed: 'sg-card-segment--signal-green' }[value] || 'sg-card-segment--darkblue');";
|
||||
echo " const marketRow = (label, value, specific = false) => '<div class=\"market-detail-row\"><dt>' + marketEscape(label) + (specific ? ' <span class=\"sg-table-label market-day-specific\">(Tagesspezifisch)</span>' : '') + '</dt><dd>' + marketDisplay(value) + '</dd></div>';";
|
||||
echo " const marketCard = (title, body, headerClass = 'sg-card-segment--darkgreen', actions = '') => '<article class=\"sg-card sg-card--content-card market-detail-card\" data-component=\"content-card\"><header class=\"sg-card-segment sg-card-segment--header ' + headerClass + '\" data-component-part=\"card-header\"><div class=\"sg-strong sg-text-on-dark\">' + title + '</div>' + actions + '</header><div class=\"sg-card-segment sg-card-segment--body\" data-component-part=\"card-body\">' + body + '</div></article>';";
|
||||
echo " const marketEditButton = (scope, id) => '<button class=\"sg-interaction-element sg-hyperlink\" type=\"button\" data-market-edit=\"true\" data-market-edit-scope=\"' + scope + '\" data-market-edit-id=\"' + marketEscape(id) + '\">bearbeiten</button>';";
|
||||
echo " const renderMarketDetail = (payload) => { const market = payload.market || {}; const occurrence = payload.occurrence || {}; const days = Array.isArray(occurrence.days) ? occurrence.days : []; const website = market.website_url ? '<a class=\"sg-hyperlink\" href=\"' + marketEscape(market.website_url) + '\" target=\"_blank\" rel=\"noopener\">' + marketEscape(market.website_url) + '</a>' : 'Nicht hinterlegt'; const overview = marketRow('Veranstaltung', market.name) + marketRow('Kategorie', market.category_name || market.category_code) + '<div class=\"market-detail-row\"><dt>Website</dt><dd>' + website + '</dd></div>' + marketRow('Ort', occurrence.place) + marketRow('Adresse', occurrence.address) + marketRow('Gemeinde', occurrence.municipality_reference_id); const dates = days.map((day) => marketDate(day.date)).join(' – '); const participation = marketRow('Anmeldung / Anfrage', occurrence.request_or_registration_date) + marketRow('Anmeldefrist', occurrence.registration_deadline) + marketRow('Bestätigung', occurrence.confirmation_date) + marketRow('Zahlungsfrist', occurrence.payment_deadline) + marketRow('Zahlungsstatus', occurrence.payment_status); const infrastructure = marketRow('Standgrösse', occurrence.stand_size) + marketRow('Tisch vorhanden', occurrence.table_available) + marketRow('Tisch mietbar', occurrence.table_rentable) + marketRow('Eigener Tisch erforderlich', occurrence.own_table_required) + marketRow('Überdachung', occurrence.shelter_available) + marketRow('Strom', occurrence.power_available) + marketRow('Wasser', occurrence.water_available) + marketRow('Beleuchtung', occurrence.lighting_available); const travel = marketRow('Reiseadresse', occurrence.travel_address) + marketRow('Distanz (km)', occurrence.travel_distance_km) + marketRow('Reisedauer (Minuten)', occurrence.travel_duration_minutes) + marketRow('Parkmöglichkeiten', occurrence.parking_options) + marketRow('Anlieferung', occurrence.delivery_access) + marketRow('Direktes Entladen', occurrence.direct_unload_possible) + marketRow('Lagerung', occurrence.storage_options); const notes = marketRow('Teilnahmebedingungen', occurrence.other_participation_conditions) + marketRow('Rücktrittsbedingungen', occurrence.cancellation_terms) + marketRow('Umsatzbeteiligung', occurrence.revenue_share_terms) + marketRow('Notizen', occurrence.notes); const dayTabs = days.map((day, index) => '<button class=\"sg-interaction-element sg-button sg-tab-button\" type=\"button\" role=\"tab\" aria-selected=\"' + (index === 0 ? 'true' : 'false') + '\" aria-controls=\"market-day-panel-' + day.id + '\" id=\"market-day-tab-' + day.id + '\" data-market-day-tab=\"true\">' + marketDate(day.date) + '</button>').join(''); const dayPanels = days.map((day, index) => '<div class=\"sg-content-block-card-group__panel\" id=\"market-day-panel-' + day.id + '\" role=\"tabpanel\" aria-labelledby=\"market-day-tab-' + day.id + '\"' + (index === 0 ? '' : ' hidden') + '><div class=\"sg-content-cards-group\" data-pattern=\"content-cards-group\" aria-label=\"Durchführungstag ' + marketDate(day.date) + '\"><article class=\"sg-card sg-card--content-card\"><header class=\"sg-card-segment sg-card-segment--header sg-card-segment--darkgreen\"><div class=\"sg-strong sg-text-on-dark\">' + marketDate(day.date) + '</div>' + marketEditButton('day', day.id) + '</header><div class=\"sg-card-segment sg-card-segment--body\"><dl class=\"market-detail-grid\">' + marketRow('Datum', marketDate(day.date), true) + marketRow('Standzeit von', day.stand_start_time, true) + marketRow('Standzeit bis', day.stand_end_time, true) + marketRow('Aufbau von', day.setup_start_time, true) + marketRow('Aufbau bis', day.setup_end_time, true) + marketRow('Anlieferung von', day.delivery_start_time, true) + marketRow('Anlieferung bis', day.delivery_end_time, true) + marketRow('Beobachtungen', day.observations, true) + '</dl></div></article></div></div>').join(''); return '<article class=\"sg-card sg-card--content-card market-status-card\"><header class=\"sg-card-segment sg-card-segment--header ' + marketStatusSegment(occurrence.status) + '\"><div class=\"sg-strong sg-text-on-dark\">Status: ' + marketEscape(marketStatusLabel(occurrence.status)) + '</div>' + marketEditButton('occurrence', occurrence.id) + '</header><div class=\"sg-card-segment sg-card-segment--body\"><dl class=\"market-detail-grid\">' + marketRow('Datumsbereich', dates) + marketRow('Anzahl Tage', days.length) + '</dl></div></article>' + marketCard('Veranstaltung und Durchführung', '<dl class=\"market-detail-grid\">' + overview + '</dl>', 'sg-card-segment--darkgreen') + marketCard('Teilnahme und Zahlung', '<dl class=\"market-detail-grid\">' + participation + '</dl>', 'sg-card-segment--darkgreen') + marketCard('Infrastruktur', '<dl class=\"market-detail-grid\">' + infrastructure + '</dl>', 'sg-card-segment--darkgreen') + marketCard('Anreise und Logistik', '<dl class=\"market-detail-grid\">' + travel + '</dl>', 'sg-card-segment--darkgreen') + marketC
Line truncated
|
||||
echo " const marketEditForm = (scope, id, values) => { const fields = scope === 'day' ? [['date', 'Datum', 'date'], ['stand_start_time', 'Standzeit von', 'time'], ['stand_end_time', 'Standzeit bis', 'time'], ['setup_start_time', 'Aufbau von', 'time'], ['setup_end_time', 'Aufbau bis', 'time'], ['delivery_start_time', 'Anlieferung von', 'time'], ['delivery_end_time', 'Anlieferung bis', 'time'], ['observations', 'Beobachtungen', 'text']] : [['place', 'Ort', 'text'], ['address', 'Adresse', 'text'], ['registration_deadline', 'Anmeldefrist', 'date'], ['request_or_registration_date', 'Anmeldung / Anfrage', 'date'], ['confirmation_date', 'Bestätigung', 'date'], ['payment_deadline', 'Zahlungsfrist', 'date'], ['notes', 'Notizen', 'text']]; return '<form class=\"market-edit-form\" data-market-edit-form=\"true\" data-market-edit-scope=\"' + scope + '\" data-market-edit-id=\"' + id + '\"><dl class=\"market-detail-grid\">' + fields.map(([key, label, type]) => '<div class=\"market-detail-row\"><dt>' + marketEscape(label) + '</dt><dd><input class=\"sg-interaction-element sg-input-single-line\" type=\"' + type + '\" name=\"' + key + '\" value=\"' + marketEscape(values[key] || '') + '\"></dd></div>').join('') + '</dl><div class=\"sg-component-row\"><button class=\"sg-interaction-element sg-button sg-button--process\" type=\"submit\">speichern</button><button class=\"sg-interaction-element sg-button sg-button--active\" type=\"button\" data-market-edit-cancel>abbrechen</button><span class=\"sg-body\" data-market-edit-status></span></div></form>'; };";
|
||||
echo " const buildUrl = (params) => { const url = new URL(window.location.href); Object.entries(params).forEach(([key, value]) => { if (value === null || value === undefined || value === '') { url.searchParams.delete(key); } else { url.searchParams.set(key, String(value)); } }); return url; };";
|
||||
echo " const replaceFragment = (html) => { const doc = new DOMParser().parseFromString(html, 'text/html'); const fragment = doc.querySelector('[data-markets-content]'); if (!fragment) { return false; } contentRoot.innerHTML = fragment.innerHTML; return Boolean(getTable()); };";
|
||||
echo " const loadFragment = async (params) => { if (searchTimer) { window.clearTimeout(searchTimer); searchTimer = null; } const input = getSearchInput(); const wasFocused = input ? document.activeElement === input : false; const response = await fetch(buildUrl({ ...params, market_fragment: '1' }).toString(), { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }); if (!response.ok) { return; } const replaced = replaceFragment(await response.text()); if (!replaced) { return; } window.history.replaceState({}, '', buildUrl(params)); bindTable(); if (wasFocused) { const nextInput = getSearchInput(); nextInput?.focus({ preventScroll: true }); if (nextInput) { nextInput.setSelectionRange(nextInput.value.length, nextInput.value.length); } } };";
|
||||
echo " const syncSearchState = () => { const input = getSearchInput(); const wrap = input?.closest('[data-component=\'single-line-input\']'); if (!input || !wrap) { return; } const hasValue = input.value.trim() !== ''; wrap.setAttribute('data-has-value', String(hasValue)); wrap.dataset.componentState = hasValue ? 'active' : 'inactive-selectable'; };";
|
||||
echo " const bindTable = () => { const table = getTable(); if (!table) { return; } const input = getSearchInput(); const clearButton = table.querySelector('[data-markets-search-clear]'); if (input && input.dataset.marketsBound !== 'true') { input.dataset.marketsBound = 'true'; input.addEventListener('input', () => { syncSearchState(); if (searchTimer) { window.clearTimeout(searchTimer); } searchTimer = window.setTimeout(() => { void loadFragment({ market_search: getSearchValue(), market_sort: getSortColumn(), market_dir: getSortDirection(), market_limit: getPageSize() }); }, 500); }); } if (clearButton && clearButton.dataset.marketsBound !== 'true') { clearButton.dataset.marketsBound = 'true'; clearButton.addEventListener('click', () => { const currentInput = getSearchInput(); if (currentInput) { currentInput.value = ''; } syncSearchState(); void loadFragment({ market_search: '', market_sort: getSortColumn(), market_dir: getSortDirection(), market_limit: getPageSize() }); }); } syncSearchState(); };";
|
||||
echo " let marketMobileDetailHost = null; let marketOpeningMobileDetail = false; let marketDetailOpen = false;";
|
||||
echo " const getMarketDrawer = () => document.querySelector('[data-market-mobile-drawer]') || contentRoot.querySelector('[data-market-drawer]');";
|
||||
echo " const getMarketBackdrop = () => contentRoot.querySelector('[data-market-drawer-backdrop]');";
|
||||
echo " const setMarketDrawerState = (open) => { const drawer = getMarketDrawer(); const backdrop = getMarketBackdrop(); if (!drawer || !backdrop) { return; } drawer.dataset.open = String(open); drawer.setAttribute('aria-hidden', String(!open)); backdrop.dataset.open = String(open); backdrop.setAttribute('aria-hidden', String(!open)); document.body.classList.toggle('sg-card-list-page-drawer-open', open); };";
|
||||
echo " const applyMarketTabs = (root) => { root.querySelectorAll('[data-market-day-tab]').forEach((tab) => { if (tab.dataset.marketTabBound === 'true') { return; } tab.dataset.marketTabBound = 'true'; tab.addEventListener('click', () => { const group = tab.closest('[data-pattern=\"card-gruppe-mit-tastennavigation\"]'); group?.querySelectorAll('[data-market-day-tab]').forEach((candidate) => candidate.setAttribute('aria-selected', String(candidate === tab))); group?.querySelectorAll('[role=\"tabpanel\"]').forEach((panel) => { panel.hidden = panel.id !== tab.getAttribute('aria-controls'); }); tab.focus(); }); }); };";
|
||||
echo " const renderMarketEdit = (scope, id, payload) => { const drawer = getMarketDrawer(); const source = scope === 'day' ? (payload.occurrence.days || []).find((day) => String(day.id) === String(id)) : payload.occurrence; const card = drawer?.querySelector('[data-market-edit=\"true\"][data-market-edit-scope=\"' + scope + '\"][data-market-edit-id=\"' + id + '\"]')?.closest('article'); if (card && source) { card.querySelector('[data-component-part=\"card-body\"]')?.replaceChildren(); card.querySelector('[data-component-part=\"card-body\"]').innerHTML = marketEditForm(scope, id, source); } };";
|
||||
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 = payload.market?.name || 'Marktdurchführung'; } if (content) { content.innerHTML = renderMarketDetail(payload); applyMarketTabs(content); content.querySelectorAll('[data-market-edit]').forEach((button) => button.addEventListener('click', () => { const scope = button.dataset.marketEditScope || ''; const id = button.dataset.marketEditId || ''; const source = scope === 'day' ? (payload.occurrence.days || []).find((day) => String(day.id) === String(id)) : payload.occurrence; const card = button.closest('article'); const body = card?.querySelector('[data-component-part=\"card-body\"]'); if (body && source) { body.innerHTML = marketEditForm(scope, id, source); } })); } } 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 closeMarketDrawer = () => { setMarketDrawerState(false); contentRoot.querySelectorAll('[data-market-drawer-open]').forEach((button) => button.setAttribute('aria-expanded', 'false')); };";
|
||||
echo " const restoreMarketList = () => { if (marketMobileDetailHost?.parentNode) { const drawer = marketMobileDetailHost.querySelector('[data-market-mobile-drawer]'); if (drawer) { contentRoot.appendChild(drawer); drawer.removeAttribute('data-market-mobile-drawer'); drawer.dataset.open = 'false'; drawer.setAttribute('aria-hidden', 'true'); } marketMobileDetailHost.parentNode.removeChild(marketMobileDetailHost); } marketMobileDetailHost = null; marketDetailOpen = false; document.body.classList.remove('sg-market-mobile-detail-open'); bindTable(); };";
|
||||
echo " const openMarketMobileDetail = (trigger) => { restoreMarketList(); const sourceDrawer = contentRoot.querySelector('[data-market-drawer]'); if (!sourceDrawer) { return; } const host = document.createElement('div'); host.className = 'sg-market-mobile-detail-host'; host.dataset.marketMobileDetailHost = 'true'; host.innerHTML = '<div class=\"sg-navigation-card-layout sg-mobile-detail-back-card\"><div class=\"sg-navigation-card-block\"><article class=\"sg-card\" data-component=\"card\" data-pattern=\"navigation-card\"><div class=\"sg-card-segment sg-card-segment--body\"><div class=\"sg-navigation-card-center\"><button class=\"sg-interaction-element sg-hyperlink\" type=\"button\" data-market-detail-back>zurück</button></div></div></article></div></div>'; sourceDrawer.dataset.marketMobileDrawer = 'true'; sourceDrawer.dataset.open = 'true'; sourceDrawer.setAttribute('aria-hidden', 'false'); host.appendChild(sourceDrawer); document.body.appendChild(host); marketMobileDetailHost = host; document.body.classList.add('sg-market-mobile-detail-open'); marketOpeningMobileDetail = true; void openMarketDrawer(trigger).finally(() => { marketOpeningMobileDetail = false; }); marketDetailOpen = true; window.history.pushState({ marketView: 'detail' }, '', window.location.href); };";
|
||||
echo " if (!marketsBindingsInstalled) {";
|
||||
echo " marketsBindingsInstalled = true;";
|
||||
echo " document.addEventListener('click', (event) => { 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() }); } });";
|
||||
echo " document.addEventListener('click', (event) => { 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; } const edit = event.target.closest('[data-market-edit]'); if (edit) { const drawer = getMarketDrawer(); const current = drawer?.dataset.marketPayload ? JSON.parse(drawer.dataset.marketPayload) : null; if (current) { const scope = edit.dataset.marketEditScope || ''; const id = edit.dataset.marketEditId || ''; const source = scope === 'day' ? (current.occurrence.days || []).find((day) => String(day.id) === String(id)) : current.occurrence; const card = edit.closest('article'); const body = card?.querySelector('[data-component-part=\"card-body\"]'); if (body && source) { body.innerHTML = marketEditForm(scope, id, source); } } return; } if (event.target.closest('[data-market-edit-cancel]')) { const drawer = getMarketDrawer(); if (drawer?.dataset.marketPayload) { const payload = JSON.parse(drawer.dataset.marketPayload); const content = drawer.querySelector('[data-market-drawer-content]'); content.innerHTML = renderMarketDetail(payload); applyMarketTabs(content); } } });";
|
||||
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('click', (event) => { if (event.target.closest('[data-market-drawer-backdrop]')) { closeMarketDrawer(); } });";
|
||||
echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeMarketDrawer(); restoreMarketList(); } });";
|
||||
echo " window.addEventListener('popstate', () => { if (marketDetailOpen) { restoreMarketList(); } });";
|
||||
echo " }";
|
||||
echo " bindTable();";
|
||||
echo "}";
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../modules/shared/db.php';
|
||||
require_once __DIR__ . '/../../modules/shared/auth/service.php';
|
||||
require_once __DIR__ . '/../../modules/market_management/service.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Cache-Control: no-store');
|
||||
|
||||
$env = expand_env_values(parse_env_file(__DIR__ . '/../../.env'));
|
||||
$pdo = connect_database($env);
|
||||
auth_bootstrap_session();
|
||||
|
||||
if (auth_current_user($pdo) === null) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['ok' => false, 'error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$requestMethod = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
if ($requestMethod === 'POST') {
|
||||
$csrf = (string) ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
|
||||
if (!auth_validate_csrf_token($csrf)) {
|
||||
http_response_code(419);
|
||||
echo json_encode(['ok' => false, 'error' => 'Ungültiges Sicherheits-Token']);
|
||||
exit;
|
||||
}
|
||||
$payload = json_decode((string) file_get_contents('php://input'), true);
|
||||
if (!is_array($payload)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'Ungültige Anfrage']);
|
||||
exit;
|
||||
}
|
||||
$scope = (string) ($payload['scope'] ?? '');
|
||||
$id = market_management_require_positive_id($payload['id'] ?? null, 'id');
|
||||
$fields = is_array($payload['fields'] ?? null) ? $payload['fields'] : [];
|
||||
if ($scope === 'occurrence') {
|
||||
market_management_update_occurrence($pdo, $id, $fields);
|
||||
} elseif ($scope === 'day') {
|
||||
market_management_update_day($pdo, $id, $fields);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Ungültiger Bearbeitungsbereich.');
|
||||
}
|
||||
echo json_encode(['ok' => true]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$occurrenceId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
|
||||
if (!is_int($occurrenceId) || $occurrenceId <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid occurrence id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$occurrence = market_management_get_occurrence($pdo, $occurrenceId);
|
||||
if ($occurrence === null) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['ok' => false, 'error' => 'Occurrence not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$market = market_management_get_market($pdo, (int) $occurrence['market_definition_id']);
|
||||
if ($market === null) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['ok' => false, 'error' => 'Market not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'market' => $market, 'occurrence' => $occurrence], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
@@ -39,6 +39,73 @@ body.sg-card-list-page-drawer-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sg-market-mobile-detail-host {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1500;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-large);
|
||||
padding: var(--spacing-small);
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
background: var(--color-background-purple);
|
||||
}
|
||||
|
||||
.sg-market-mobile-detail-host .sg-card-list-page-drawer {
|
||||
position: static;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
body.sg-market-mobile-detail-open > :not([data-market-mobile-detail-host]) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.market-detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10rem, 0.8fr) minmax(0, 1.2fr);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.market-detail-row {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.market-detail-row dt,
|
||||
.market-detail-row dd {
|
||||
margin: 0;
|
||||
padding: var(--spacing-small) 0;
|
||||
border-bottom: 1px solid var(--color-light-grey);
|
||||
}
|
||||
|
||||
.market-detail-row dt {
|
||||
color: var(--color-medium-grey);
|
||||
}
|
||||
|
||||
.market-detail-row dd {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.market-day-specific {
|
||||
color: var(--color-medium-grey);
|
||||
}
|
||||
|
||||
.market-edit-form .sg-component-row {
|
||||
margin-top: var(--spacing-medium);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.market-detail-grid {
|
||||
grid-template-columns: minmax(8rem, 0.9fr) minmax(0, 1.1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.sg-card-list-page-drawer-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
Reference in new issue
Block a user