diff --git a/Internal/de/PROJECT_CONTEXT.md b/Internal/de/PROJECT_CONTEXT.md
index 99321d7..12cd89b 100644
--- a/Internal/de/PROJECT_CONTEXT.md
+++ b/Internal/de/PROJECT_CONTEXT.md
@@ -35,6 +35,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
- Eigene Events nutzen jetzt als Pflichtfelder nur noch Titel, Datum und `Mit Kindern`; die Beschreibung ist optional.
- Für die Event-Location gibt es jetzt die Auswahl `Ort aus Datenbank`, `Veranstaltung aus Datenbank` oder `Benutzerdefiniert`.
- Bei `Ort` oder `Veranstaltung` werden Adresse und Kategorie direkt aus dem gewählten Datenbank-Eintrag übernommen.
+- Die Auswahl für Datenbank-Locations läuft im Event-Formular jetzt über ein echtes Auswahlfeld statt über Freitext; `Ort aus Datenbank` zeigt nur Orte, `Veranstaltung aus Datenbank` nur aktuell laufende Veranstaltungen im gültigen Zeitraum.
- Im Formular für eigene Events sind `Gültig bis` und `Wiederholung` ausgeblendet.
- Bei eigener Event-Location aus der Datenbank werden Adresse und Kategorie nur als Info angezeigt und nicht direkt bearbeitet.
- Aus dem Formular für eigene Events kann direkt in das Anlegen eines neuen Orts oder einer neuen Veranstaltung gewechselt werden.
diff --git a/Internal/en/PROJECT_CONTEXT.md b/Internal/en/PROJECT_CONTEXT.md
index c981fbc..400d6d4 100644
--- a/Internal/en/PROJECT_CONTEXT.md
+++ b/Internal/en/PROJECT_CONTEXT.md
@@ -35,6 +35,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
- Own events now only require title, date, and the `with children` choice; the description is optional.
- Event locations now support the modes `place from database`, `event from database`, or `custom`.
- When a database-backed place or event is selected, the address and category are taken directly from that source entry.
+- Database-backed event locations now use a real select field instead of free text; `place from database` only shows places, while `event from database` only shows currently active event-style entries inside their valid date window.
- Inside the own-event form, `valid until` and `recurrence` are hidden.
- When an own event uses a database-backed location, address and category are shown as informational values and are not edited directly.
- The own-event form now offers direct links into creating a new place or a new event-style listing.
diff --git a/partials/landing/account/dashboard.php b/partials/landing/account/dashboard.php
index aa29c5e..cb2c431 100755
--- a/partials/landing/account/dashboard.php
+++ b/partials/landing/account/dashboard.php
@@ -61,14 +61,20 @@ foreach (($listingCategories ?? []) as $categoryRow) {
$categoryInputValue = $categoryTitleMap[$categorySlugValue] ?? $categorySlugValue;
$eventLocationModeValue = $editingEvent ? (string)($editEvent['location_source_type'] ?? 'custom') : 'custom';
$eventLocationSourceIdValue = $editingEvent && !empty($editEvent['location_source_listing_id']) ? (int)$editEvent['location_source_listing_id'] : 0;
-$eventLocationSourceInputValue = '';
$usesDatabaseEventLocation = $currentEntryKind === 'event' && $eventLocationModeValue !== 'custom';
$eventAddressInfoValue = trim(implode(', ', array_filter([
$streetValue,
trim($zipValue . ' ' . $cityValue),
$regionValue,
])));
+$eventLocationPlaceOptions = [];
+$eventLocationEditorialOptions = [];
foreach (($eventLocationOptions ?? []) as $locationOption) {
+ if ((string)($locationOption['listing_type'] ?? '') === 'place') {
+ $eventLocationPlaceOptions[] = $locationOption;
+ } elseif ((string)($locationOption['listing_type'] ?? '') === 'editorial_event') {
+ $eventLocationEditorialOptions[] = $locationOption;
+ }
if ((int)($locationOption['id'] ?? 0) !== $eventLocationSourceIdValue) {
continue;
}
@@ -78,12 +84,6 @@ foreach (($eventLocationOptions ?? []) as $locationOption) {
trim((string)($locationOption['zip'] ?? '') . ' ' . (string)($locationOption['city'] ?? '')),
])));
$optionDateLabel = !empty($locationOption['starts_at']) ? date('d.m.Y', strtotime((string)$locationOption['starts_at'])) : '';
- $eventLocationSourceInputValue = trim(implode(' · ', array_filter([
- $optionTypeLabel,
- (string)($locationOption['title'] ?? ''),
- $optionDateLabel,
- $optionAddressLabel,
- ])));
if ($eventLocationModeValue !== 'custom' && !empty($locationOption['category_title'])) {
$categoryInputValue = (string)$locationOption['category_title'];
$categorySlugValue = (string)($locationOption['category_slug'] ?? $categorySlugValue);
@@ -1144,25 +1144,6 @@ if (!empty($canManageSystemSettings)) {
-
@@ -1197,8 +1178,29 @@ if (!empty($canManageSystemSettings)) {
-
-
+
+
@@ -1450,7 +1452,7 @@ if (!empty($canManageSystemSettings)) {
const eventLocationModeWrap = document.getElementById('eventLocationModeWrap');
const eventLocationModeSelect = document.getElementById('eventLocationMode');
const eventLocationSourceWrap = document.getElementById('eventLocationSourceWrap');
- const eventLocationSourceInput = document.getElementById('eventLocationSourceInput');
+ const eventLocationSourceSelect = document.getElementById('eventLocationSourceSelect');
const eventLocationSourceIdInput = document.getElementById('eventLocationSourceId');
const eventLocationSourceLabel = document.getElementById('eventLocationSourceLabel');
const categoryInputWrap = document.getElementById('categoryInputWrap');
@@ -1532,16 +1534,50 @@ if (!empty($canManageSystemSettings)) {
});
}
- function findEventLocationOptionByLabel(label) {
- const normalized = (label || '').trim();
- if (!normalized) return null;
- const option = document.querySelector(`#eventLocationOptionList option[value="${CSS.escape(normalized)}"]`);
+ const allEventLocationOptions = = json_encode($eventLocationOptions ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
+
+ function buildEventLocationLabel(option) {
+ if (!option) return '';
+ const addressLabel = [String(option.street || ''), `${String(option.zip || '')} ${String(option.city || '')}`.trim()]
+ .filter(Boolean)
+ .join(', ');
+ const dateLabel = option.starts_at ? new Date(String(option.starts_at)).toLocaleDateString('de-DE') : '';
+ return [String(option.title || ''), dateLabel, addressLabel].filter(Boolean).join(' · ');
+ }
+
+ function findEventLocationOptionById(id) {
+ const normalizedId = Number(id || 0);
+ if (!normalizedId) return null;
+ const option = allEventLocationOptions.find((entry) => Number(entry.id || 0) === normalizedId);
if (!option) return null;
- try {
- return JSON.parse(option.getAttribute('data-location') || 'null');
- } catch (error) {
- return null;
- }
+ return option;
+ }
+
+ function rebuildEventLocationOptions() {
+ if (!eventLocationSourceSelect) return;
+ const mode = eventLocationModeSelect?.value || 'custom';
+ const selectedId = Number(eventLocationSourceIdInput?.value || eventLocationSourceSelect.value || 0);
+ const filteredOptions = allEventLocationOptions.filter((option) => String(option.listing_type || '') === mode);
+ const emptyLabel = mode === 'editorial_event'
+ ? 'Keine aktuell verfügbaren Veranstaltungen'
+ : 'Ort aus der Datenbank auswählen';
+
+ eventLocationSourceSelect.innerHTML = '';
+ const placeholderOption = document.createElement('option');
+ placeholderOption.value = '';
+ placeholderOption.textContent = filteredOptions.length === 0 ? emptyLabel : (mode === 'editorial_event' ? 'Veranstaltung aus der Datenbank auswählen' : 'Ort aus der Datenbank auswählen');
+ eventLocationSourceSelect.appendChild(placeholderOption);
+ eventLocationSourceSelect.disabled = filteredOptions.length === 0;
+
+ filteredOptions.forEach((option) => {
+ const element = document.createElement('option');
+ element.value = String(option.id || '');
+ element.textContent = buildEventLocationLabel(option);
+ if (Number(option.id || 0) === selectedId) {
+ element.selected = true;
+ }
+ eventLocationSourceSelect.appendChild(element);
+ });
}
function applyEventLocationOption(option) {
@@ -1585,7 +1621,7 @@ if (!empty($canManageSystemSettings)) {
function clearEventLocationSelection() {
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = '';
- if (eventLocationSourceInput) eventLocationSourceInput.value = '';
+ if (eventLocationSourceSelect) eventLocationSourceSelect.value = '';
if (streetInput) streetInput.value = '';
if (zipInput) zipInput.value = '';
if (cityInput) cityInput.value = '';
@@ -1614,6 +1650,7 @@ if (!empty($canManageSystemSettings)) {
if (eventLocationSourceLabel) {
eventLocationSourceLabel.textContent = mode === 'editorial_event' ? 'Veranstaltung aus Datenbank' : 'Ort aus Datenbank';
}
+ rebuildEventLocationOptions();
if (categoryInput) {
if (usesDatabaseLocation) {
@@ -1624,7 +1661,7 @@ if (!empty($canManageSystemSettings)) {
}
if (usesDatabaseLocation) {
- const option = findEventLocationOptionByLabel(eventLocationSourceInput?.value || '');
+ const option = findEventLocationOptionById(eventLocationSourceSelect?.value || eventLocationSourceIdInput?.value || '');
if (option && String(option.listing_type || '') === mode) {
applyEventLocationOption(option);
} else {
@@ -1683,9 +1720,9 @@ if (!empty($canManageSystemSettings)) {
clearEventLocationSelection();
syncEventLocationMode();
});
- eventLocationSourceInput?.addEventListener('change', () => {
+ eventLocationSourceSelect?.addEventListener('change', () => {
const mode = eventLocationModeSelect?.value || 'custom';
- const option = findEventLocationOptionByLabel(eventLocationSourceInput.value || '');
+ const option = findEventLocationOptionById(eventLocationSourceSelect.value || '');
if (!option || String(option.listing_type || '') !== mode) {
clearEventLocationSelection();
return;
diff --git a/src/App/ListingCatalog.php b/src/App/ListingCatalog.php
index 195f4fc..209ffcd 100644
--- a/src/App/ListingCatalog.php
+++ b/src/App/ListingCatalog.php
@@ -519,6 +519,17 @@ final class ListingCatalog
LEFT JOIN listing_categories lc ON lc.id = lcm.category_id
WHERE l.listing_type IN ("place","editorial_event")
AND l.status IN ("draft","published")
+ AND (
+ l.listing_type = "place"
+ OR (
+ lo.starts_at IS NOT NULL
+ AND DATE(lo.starts_at) <= CURRENT_DATE()
+ AND (
+ lo.recurrence_until IS NULL
+ OR DATE(lo.recurrence_until) >= CURRENT_DATE()
+ )
+ )
+ )
ORDER BY
CASE l.listing_type
WHEN "place" THEN 1