From 49fd392f79da5731e9580978f580f6ded224c893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20Gla=CC=88ser?= Date: Sat, 19 Sep 2026 11:34:45 +0200 Subject: [PATCH] Implement market occurrence drawer --- modules/shared/auth/ui/home.php | 35 ++++++++++++++-- public/api/market-occurrence.php | 70 ++++++++++++++++++++++++++++++++ public/assets/styles.portal.css | 67 ++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 public/api/market-occurrence.php diff --git a/modules/shared/auth/ui/home.php b/modules/shared/auth/ui/home.php index eaef614..8885b44 100644 --- a/modules/shared/auth/ui/home.php +++ b/modules/shared/auth/ui/home.php @@ -133,7 +133,7 @@ function auth_render_markets_large_table(array $marketTable): string } $html[] = '
'; $html[] = '
' . auth_escape_html((string) ($row['market_name'] ?? '')) . '
'; - $html[] = '
' . auth_escape_html(auth_format_market_date($row['earliest_date'] ?? null, $row['latest_date'] ?? null)) . '
'; + $html[] = ''; $html[] = '
' . auth_escape_html((string) ($row['place'] ?? '')) . '
'; $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 '
' . auth_render_markets_large_table($marketTable) . '
'; + return '
' . auth_render_markets_large_table($marketTable) + . '' + . '
'; } 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) => '
' + marketEscape(label) + (specific ? ' (Tagesspezifisch)' : '') + '
' + marketDisplay(value) + '
';"; + echo " const marketCard = (title, body, headerClass = 'sg-card-segment--darkgreen', actions = '') => '
' + title + '
' + actions + '
' + body + '
';"; + echo " const marketEditButton = (scope, id) => '';"; + 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 ? '' + marketEscape(market.website_url) + '' : 'Nicht hinterlegt'; const overview = marketRow('Veranstaltung', market.name) + marketRow('Kategorie', market.category_name || market.category_code) + '
Website
' + website + '
' + 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) => '').join(''); const dayPanels = days.map((day, index) => '
' + marketDate(day.date) + '
' + marketEditButton('day', day.id) + '
' + 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) + '
').join(''); return '
Status: ' + marketEscape(marketStatusLabel(occurrence.status)) + '
' + marketEditButton('occurrence', occurrence.id) + '
' + marketRow('Datumsbereich', dates) + marketRow('Anzahl Tage', days.length) + '
' + marketCard('Veranstaltung und Durchführung', '
' + overview + '
', 'sg-card-segment--darkgreen') + marketCard('Teilnahme und Zahlung', '
' + participation + '
', 'sg-card-segment--darkgreen') + marketCard('Infrastruktur', '
' + infrastructure + '
', 'sg-card-segment--darkgreen') + marketCard('Anreise und Logistik', '
' + travel + '
', 'sg-card-segment--darkgreen') + marketCard('Weitere Angaben', '
' + notes + '
', 'sg-card-segment--darkgreen') + (days.length ? '
' + dayTabs + '
' + dayPanels + '
' : '

Keine Durchführungstage vorhanden.

'); };"; + 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 '
' + fields.map(([key, label, type]) => '
' + marketEscape(label) + '
').join('') + '
'; };"; 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 = '

Marktdetails werden geladen …

'; } 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 = '

' + marketEscape(error.message) + '

'; } } 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 = '
'; 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 "}"; diff --git a/public/api/market-occurrence.php b/public/api/market-occurrence.php new file mode 100644 index 0000000..3eb3ba8 --- /dev/null +++ b/public/api/market-occurrence.php @@ -0,0 +1,70 @@ + 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); diff --git a/public/assets/styles.portal.css b/public/assets/styles.portal.css index 7704e3d..2fca1ae 100644 --- a/public/assets/styles.portal.css +++ b/public/assets/styles.portal.css @@ -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;