diff --git a/modules/market_management/service.php b/modules/market_management/service.php index c066179..ddf14a9 100644 --- a/modules/market_management/service.php +++ b/modules/market_management/service.php @@ -502,6 +502,11 @@ function market_management_search_markets(PDO $pdo, array $input): array $sortColumn = match ($sort) { 'earliest_date' => 'MIN(d.date)', 'updated_at' => 'm.updated_at', + 'category_name' => 'c.name', + 'organizer_name' => 'p.name', + 'website_url' => 'm.website_url', + 'occurrence_count' => 'COUNT(DISTINCT o.id)', + 'status' => 'CASE WHEN m.archived_at IS NULL THEN 0 ELSE 1 END', default => 'm.name', }; diff --git a/modules/shared/auth/ui/home.php b/modules/shared/auth/ui/home.php index c2e545f..35a2bf1 100644 --- a/modules/shared/auth/ui/home.php +++ b/modules/shared/auth/ui/home.php @@ -195,6 +195,8 @@ function auth_render_market_definitions_content(array $marketTable): string $rows = $marketTable['rows'] ?? $marketTable; $categories = $marketTable['categories'] ?? []; $organizers = $marketTable['organizers'] ?? []; + $sortColumn = (string) ($marketTable['sort_column'] ?? 'name'); + $sortDirection = strtoupper((string) ($marketTable['sort_direction'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC'; $html = []; $html[] = '
'; $html[] = '
'; @@ -202,9 +204,26 @@ function auth_render_market_definitions_content(array $marketTable): string $html[] = '
'; $html[] = '
Märkte
'; $html[] = '
'; - $definitionColumns = ['Markt', 'Kategorie', 'Veranstalter', 'Website', 'Durchführungen', 'Status']; - foreach ($definitionColumns as $index => $label) { - $html[] = '
' . auth_escape_html($label) . '' . auth_large_table_resize_handle($label, $index, count($definitionColumns)) . '
'; + $definitionColumns = [ + 'name' => 'Markt', + 'category_name' => 'Kategorie', + 'organizer_name' => 'Veranstalter', + 'website_url' => 'Website', + 'occurrence_count' => 'Durchführungen', + 'status' => 'Status', + ]; + $definitionColumnIndex = 0; + foreach ($definitionColumns as $column => $label) { + $isActive = $column === $sortColumn; + $headerDirection = $isActive ? strtolower($sortDirection) : 'none'; + $iconDirection = $isActive && $sortDirection === 'DESC' ? 'descending' : 'ascending'; + $targetDirection = $isActive && $sortDirection === 'ASC' ? 'DESC' : 'ASC'; + $ariaLabel = $isActive + ? $label . ' ' . ($sortDirection === 'ASC' ? 'aufsteigend sortiert' : 'absteigend sortiert') + : $label . ' sortieren'; + $sortButton = ''; + $html[] = '
' . $sortButton . auth_large_table_resize_handle($label, $definitionColumnIndex, count($definitionColumns)) . '
'; + $definitionColumnIndex++; } $html[] = '
'; foreach ($rows as $index => $row) { @@ -1333,6 +1352,7 @@ JS; echo " const deleteMarketDefinition = async (submit) => { if (submit.disabled || !activeMarket?.id) { return; } const card = submit.closest('[data-shared-editable-action-card]'); const input = card?.querySelector('[data-shared-editable-card-delete-input]'); const status = card?.querySelector('[data-market-definition-delete-status]'); if (!card || !input || input.value !== 'DELETE') { return; } if (status) { status.textContent = 'Löschen läuft …'; } submit.disabled = true; submit.setAttribute('aria-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({ action: 'delete', id: activeMarket.id, confirmation: input.value }) }); const result = await response.json(); if (!response.ok || !result.ok) { throw new Error(result.error || 'Löschen fehlgeschlagen.'); } const row = activeMarket.trigger.closest('[data-market-definition-row]'); row?.remove(); closeDrawer(); } catch (error) { if (status) { status.textContent = error instanceof Error ? error.message : 'Löschen fehlgeschlagen.'; } submit.disabled = false; submit.setAttribute('aria-disabled', 'false'); } };"; echo " const showReadCard = (trigger) => { activeMarket = { id: trigger.dataset.marketDefinitionId || '', trigger }; drawer.querySelector('[data-market-definition-read-title]').textContent = trigger.dataset.marketDefinitionName || 'Markt'; readCard.querySelector('[data-market-definition-read-name]').textContent = trigger.dataset.marketDefinitionName || 'Nicht hinterlegt'; readCard.querySelector('[data-market-definition-read-category]').textContent = trigger.dataset.marketDefinitionCategoryName || 'Nicht hinterlegt'; readCard.querySelector('[data-market-definition-read-organizer]').textContent = trigger.dataset.marketDefinitionOrganizerName || 'Nicht hinterlegt'; readCard.querySelector('[data-market-definition-read-website]').textContent = trigger.dataset.marketDefinitionWebsite || 'Nicht hinterlegt'; const title = form.querySelector('[data-market-definition-form-title]'); if (title) { title.textContent = 'Markt bearbeiten'; } const name = form.querySelector('[data-market-definition-name]'); const category = form.querySelector('[data-market-definition-category]'); const organizer = form.querySelector('[data-market-definition-organizer]'); const website = form.querySelector('[data-market-definition-website]'); if (name) { name.value = trigger.dataset.marketDefinitionName || ''; } if (category) { category.value = trigger.dataset.marketDefinitionCategoryId || ''; } if (organizer) { organizer.value = trigger.dataset.marketDefinitionOrganizerId || ''; } if (website) { website.value = trigger.dataset.marketDefinitionWebsite === 'Nicht hinterlegt' ? '' : (trigger.dataset.marketDefinitionWebsite || ''); } syncNewOrganizerFields(); if (sharedCard && window.SharedEditableActionCard) { window.SharedEditableActionCard.setEditMode(sharedCard, false); } setDrawerState(true); };"; echo " definitionContent.addEventListener('click', (event) => { const trigger = event.target.closest('[data-market-definition-open]'); if (!trigger || !definitionContent.contains(trigger)) { return; } event.preventDefault(); event.stopPropagation(); if (window.matchMedia('(max-width: 767px)').matches) { openMarketDefinitionMobileDetail(trigger); } else { showReadCard(trigger); } }, true);"; + echo " definitionContent.addEventListener('click', (event) => { const sortButton = event.target.closest('[data-market-definition-sort]'); if (!sortButton || !definitionContent.contains(sortButton)) { return; } event.preventDefault(); const url = new URL(window.location.href); url.searchParams.set('portal_module', 'Märkte'); url.searchParams.set('portal_section', 'Märkte'); url.searchParams.set('market_definition_sort', sortButton.dataset.marketDefinitionSortColumn || 'name'); url.searchParams.set('market_definition_dir', sortButton.dataset.marketDefinitionSortDirection || 'ASC'); window.location.assign(url.toString()); });"; echo " definitionContent.addEventListener('click', (event) => { const deleteSubmit = event.target.closest('[data-market-definition-delete-submit]'); if (deleteSubmit) { void deleteMarketDefinition(deleteSubmit); return; } const trigger = event.target.closest('[data-market-definition-open]'); if (trigger) { const deleteButton = drawer.querySelector('[data-market-definition-delete]'); if (deleteButton) { deleteButton.hidden = false; } } if (event.target.closest('[data-market-definition-create]')) { const deleteButton = drawer.querySelector('[data-market-definition-delete]'); if (deleteButton) { deleteButton.hidden = true; } } });"; echo " const openEditCard = () => { if (!activeMarket) { return; } const title = form.querySelector('[data-market-definition-form-title]'); if (title) { title.textContent = 'Markt bearbeiten'; } const name = form.querySelector('[data-market-definition-name]'); const category = form.querySelector('[data-market-definition-category]'); const organizer = form.querySelector('[data-market-definition-organizer]'); const website = form.querySelector('[data-market-definition-website]'); if (name) { name.value = activeMarket.trigger.dataset.marketDefinitionName || ''; } if (category) { category.value = activeMarket.trigger.dataset.marketDefinitionCategoryId || ''; } if (organizer) { organizer.value = activeMarket.trigger.dataset.marketDefinitionOrganizerId || ''; } if (website) { website.value = activeMarket.trigger.dataset.marketDefinitionWebsite === 'Nicht hinterlegt' ? '' : (activeMarket.trigger.dataset.marketDefinitionWebsite || ''); } syncNewOrganizerFields(); };"; echo " editButton.addEventListener('click', (event) => { event.preventDefault(); openEditCard(); });"; diff --git a/public/index.php b/public/index.php index ee4c3cf..db30c74 100644 --- a/public/index.php +++ b/public/index.php @@ -79,16 +79,20 @@ if ($currentUser !== null) { 'page_size' => 20, ]; $marketOccurrencesTable['has_more'] = count($marketOccurrencesTable['rows']) >= $marketLimit; + $marketDefinitionSort = (string) ($_GET['market_definition_sort'] ?? 'name'); + $marketDefinitionDirection = strtoupper((string) ($_GET['market_definition_dir'] ?? 'ASC')); $marketDefinitionsTable = [ 'rows' => market_management_search_markets($pdo, [ 'include_archived' => false, - 'sort' => 'name', - 'sort_direction' => 'ASC', + 'sort' => $marketDefinitionSort, + 'sort_direction' => $marketDefinitionDirection, 'limit' => 100, 'offset' => 0, ]), 'categories' => market_management_list_categories($pdo), 'organizers' => market_management_list_organizers($pdo), + 'sort_column' => $marketDefinitionSort, + 'sort_direction' => $marketDefinitionDirection, ]; if ((string) ($_GET['bestellungen_fragment'] ?? '') === '1') { echo auth_render_bestellungen_content($bestellungenTable);