From f72078273694301e18797a1ae8936d948f5ac671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Gla=CC=88ser?= Date: Sun, 20 Sep 2026 08:57:54 +0200 Subject: [PATCH] =?UTF-8?q?Markt-Neuanlage=20im=20M=C3=A4rkte-Drawer=20imp?= =?UTF-8?q?lementieren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/shared/auth/ui/home.php | 29 ++++++++++++++++-- public/api/market-definition.php | 52 ++++++++++++++++++++++++++++++++ public/index.php | 1 + 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 public/api/market-definition.php diff --git a/modules/shared/auth/ui/home.php b/modules/shared/auth/ui/home.php index 99e6cc3..7cbb8ad 100644 --- a/modules/shared/auth/ui/home.php +++ b/modules/shared/auth/ui/home.php @@ -163,10 +163,11 @@ function auth_render_markets_content(array $marketTable): string function auth_render_market_definitions_content(array $marketTable): string { $rows = $marketTable['rows'] ?? $marketTable; + $categories = $marketTable['categories'] ?? []; $html = []; - $html[] = '
'; + $html[] = '
'; $html[] = '
'; - $html[] = '
'; + $html[] = '
'; $html[] = '
Märkte
'; $html[] = '
'; foreach (['Markt', 'Kategorie', 'Website', 'Durchführungen', 'Status'] as $label) { @@ -190,6 +191,12 @@ function auth_render_market_definitions_content(array $marketTable): string $html[] = '
'; } $html[] = '
'; + $html[] = ''; + $html[] = '
'; return implode('', $html); } @@ -928,6 +935,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM echo " if (contentBody) { contentBody.innerHTML = portalModuleContentCards[activeEntry] || ''; }"; echo " if (activeEntry === 'Bestellungen') { initBestellungenBindings(); }"; echo " if (activeEntry === 'Marktdurchführungen') { initMarketsBindings(); }"; + echo " if (activeEntry === 'Märkte') { initMarketDefinitionsBindings(); }"; echo " if (activeEntry === 'Lager' || activeEntry === 'Artikel') { initLagerBindings(); }"; echo " menu.innerHTML = '';"; echo " entries.forEach((entry, index) => {"; @@ -950,6 +958,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM echo " if (contentBody) { contentBody.innerHTML = portalModuleContentCards[entryName] || ''; }"; echo " if (entryName === 'Bestellungen') { initBestellungenBindings(); }"; echo " if (entryName === 'Marktdurchführungen') { initMarketsBindings(); }"; + echo " if (entryName === 'Märkte') { initMarketDefinitionsBindings(); }"; echo " if (entryName === 'Lager' || entryName === 'Artikel') { initLagerBindings(); }"; echo " });"; echo " });"; @@ -1180,6 +1189,22 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM echo " }"; echo " bindTable();"; echo "}"; + echo "function initMarketDefinitionsBindings() {"; + echo " const contentRoot = document.querySelector('[data-left-navigation-content-body]');"; + echo " const definitionContent = contentRoot?.querySelector('[data-market-definitions-content]');"; + echo " const drawer = definitionContent?.querySelector('[data-market-definition-drawer]');"; + echo " const card = drawer?.querySelector('[data-market-definition-create-card]');"; + echo " const form = drawer?.querySelector('[data-market-definition-form]');"; + echo " if (!contentRoot || !definitionContent || !drawer || !card || !form || definitionContent.dataset.marketDefinitionsBound === 'true') { return; }"; + echo " definitionContent.dataset.marketDefinitionsBound = 'true';"; + echo " const setDrawerState = (open) => { drawer.dataset.open = String(open); drawer.setAttribute('aria-hidden', String(!open)); const trigger = definitionContent.querySelector('[data-market-definition-create]'); if (trigger) { trigger.setAttribute('aria-expanded', String(open)); } const backdrop = definitionContent.querySelector('[data-market-definition-drawer-backdrop]'); if (backdrop) { backdrop.dataset.open = String(open); backdrop.setAttribute('aria-hidden', String(!open)); } document.body.classList.toggle('sg-card-list-page-drawer-open', open); };"; + echo " const resetForm = () => { form.reset(); const status = form.querySelector('[data-market-definition-status]'); if (status) { status.textContent = ''; } };"; + echo " const closeDrawer = () => { resetForm(); card.hidden = true; setDrawerState(false); };"; + echo " const appendMarketRow = (market) => { const table = contentRoot.querySelector('[data-market-definitions-table]'); if (!table) { return; } const row = document.createElement('div'); row.className = 'sg-card-segment sg-card-segment--body sg-card-segment--white sg-large-table__row'; row.setAttribute('role', 'row'); row.dataset.componentPart = 'large-table-row'; const category = form.querySelector('[data-market-definition-category]')?.selectedOptions[0]?.textContent || ''; const website = market.website_url || 'Nicht hinterlegt'; [market.name, category, website, '0', 'Aktiv'].forEach((value) => { const cell = document.createElement('div'); cell.className = 'sg-large-table__cell'; cell.setAttribute('role', 'cell'); cell.textContent = value; row.appendChild(cell); }); table.appendChild(row); };"; + echo " document.addEventListener('click', (event) => { if (event.target.closest('[data-market-definition-create]')) { event.preventDefault(); resetForm(); card.hidden = false; setDrawerState(true); form.querySelector('[data-market-definition-name]')?.focus(); return; } if (event.target.closest('[data-market-definition-cancel]') || event.target.closest('[data-market-definition-drawer-backdrop]')) { event.preventDefault(); closeDrawer(); } });"; + echo " document.addEventListener('keydown', (event) => { if (event.key === 'Escape' && drawer.dataset.open === 'true') { closeDrawer(); } });"; + echo " form.addEventListener('submit', async (event) => { event.preventDefault(); const status = form.querySelector('[data-market-definition-status]'); const submit = form.querySelector('[type=submit]'); const fields = Object.fromEntries(new FormData(form).entries()); if (status) { status.textContent = 'Speichern läuft …'; } if (submit) { submit.disabled = true; } try { const response = await fetch('/api/market-definition.php', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': definitionContent.dataset.marketDefinitionsCsrfToken || '' }, body: JSON.stringify({ fields }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Speichern fehlgeschlagen.'); } appendMarketRow(result.market); closeDrawer(); } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Speichern fehlgeschlagen.'; } } finally { if (submit) { submit.disabled = false; } } });"; + echo "}"; echo "function initBestellungenBindings() {"; echo " const contentRoot = document.querySelector('[data-left-navigation-content-body]');"; echo " if (!contentRoot) { return; }"; diff --git a/public/api/market-definition.php b/public/api/market-definition.php new file mode 100644 index 0000000..bc7f818 --- /dev/null +++ b/public/api/market-definition.php @@ -0,0 +1,52 @@ + false, 'error' => 'Nicht autorisiert.']); + exit; +} + +if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'POST') { + http_response_code(405); + echo json_encode(['ok' => false, 'error' => 'Methode nicht erlaubt.']); + exit; +} + +$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; +} + +try { + $fields = is_array($payload['fields'] ?? null) ? $payload['fields'] : []; + $market = market_management_create_market($pdo, [ + 'name' => $fields['name'] ?? null, + 'category_id' => $fields['category_id'] ?? null, + 'website_url' => $fields['website_url'] ?? null, + ]); + echo json_encode(['ok' => true, 'market' => $market], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +} catch (Throwable $error) { + http_response_code(422); + echo json_encode(['ok' => false, 'error' => $error->getMessage()], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +} diff --git a/public/index.php b/public/index.php index 51df629..04d04ff 100644 --- a/public/index.php +++ b/public/index.php @@ -87,6 +87,7 @@ if ($currentUser !== null) { 'limit' => 100, 'offset' => 0, ]), + 'categories' => market_management_list_categories($pdo), ]; if ((string) ($_GET['bestellungen_fragment'] ?? '') === '1') { echo auth_render_bestellungen_content($bestellungenTable);