yasdasd
All checks were successful
Deploy / deploy (push) Successful in 56s

This commit is contained in:
2026-08-10 22:57:21 +02:00
parent 018496f720
commit 5d979e60bf
11 changed files with 314 additions and 33 deletions

View File

@@ -54,6 +54,31 @@ foreach (($listingCategories ?? []) as $categoryRow) {
$categoryTitleMap[(string)($categoryRow['slug'] ?? '')] = (string)($categoryRow['title'] ?? '');
}
$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 = '';
foreach (($eventLocationOptions ?? []) as $locationOption) {
if ((int)($locationOption['id'] ?? 0) !== $eventLocationSourceIdValue) {
continue;
}
$optionTypeLabel = (string)($locationOption['listing_type'] ?? '') === 'place' ? 'Ort' : 'Veranstaltung';
$optionAddressLabel = trim(implode(', ', array_filter([
(string)($locationOption['street'] ?? ''),
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);
}
break;
}
$priceRows = [];
if ($editingListing && !empty($editListing['prices']) && is_array($editListing['prices'])) {
foreach ($editListing['prices'] as $priceRow) {
@@ -1100,6 +1125,7 @@ if (!empty($canManageSystemSettings)) {
<?php if ($editingListing && $editListing): ?>
<input type="hidden" name="listing_id" value="<?= (int)$editListing['id'] ?>">
<?php endif; ?>
<input type="hidden" name="event_location_source_id" id="eventLocationSourceId" value="<?= $eventLocationSourceIdValue > 0 ? (int)$eventLocationSourceIdValue : '' ?>">
<datalist id="existingPlacesList">
<?php foreach ($placeSuggestions as $placeSuggestion): ?>
<option value="<?= htmlspecialchars((string)$placeSuggestion['title'], ENT_QUOTES) ?>" data-place='<?= htmlspecialchars(json_encode($placeSuggestion, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES) ?>'>
@@ -1107,6 +1133,25 @@ if (!empty($canManageSystemSettings)) {
</option>
<?php endforeach; ?>
</datalist>
<datalist id="eventLocationOptionList">
<?php foreach (($eventLocationOptions ?? []) as $locationOption): ?>
<?php
$optionTypeLabel = (string)($locationOption['listing_type'] ?? '') === 'place' ? 'Ort' : 'Veranstaltung';
$optionAddressLabel = trim(implode(', ', array_filter([
(string)($locationOption['street'] ?? ''),
trim((string)($locationOption['zip'] ?? '') . ' ' . (string)($locationOption['city'] ?? '')),
])));
$optionDateLabel = !empty($locationOption['starts_at']) ? date('d.m.Y', strtotime((string)$locationOption['starts_at'])) : '';
$optionLabel = trim(implode(' · ', array_filter([
$optionTypeLabel,
(string)($locationOption['title'] ?? ''),
$optionDateLabel,
$optionAddressLabel,
])));
?>
<option value="<?= htmlspecialchars($optionLabel, ENT_QUOTES) ?>" data-location='<?= htmlspecialchars(json_encode($locationOption, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES) ?>'></option>
<?php endforeach; ?>
</datalist>
<?php if (!$editing && $showEventTypeChoice): ?>
<div class="stack gap-8">
<label class="label">Was möchtest du anlegen?</label>
@@ -1130,8 +1175,19 @@ if (!empty($canManageSystemSettings)) {
</div>
<div class="stack gap-6">
<label class="label" for="evDesc">Beschreibung</label>
<textarea id="evDesc" name="description" class="textarea" rows="4" placeholder="Was erwartet Familien vor Ort?" required><?= htmlspecialchars($descriptionValue, ENT_QUOTES) ?></textarea>
<p class="muted small" style="margin:0;">Die Kurzbeschreibung wird automatisch aus dem Text erzeugt.</p>
<textarea id="evDesc" name="description" class="textarea" rows="4" placeholder="Was erwartet Familien vor Ort?"><?= htmlspecialchars($descriptionValue, ENT_QUOTES) ?></textarea>
</div>
<div class="stack gap-6" id="eventLocationModeWrap">
<label class="label" for="eventLocationMode">Location</label>
<select id="eventLocationMode" name="event_location_mode" class="select">
<option value="place" <?= $eventLocationModeValue === 'place' ? 'selected' : '' ?>>Ort aus Datenbank</option>
<option value="editorial_event" <?= $eventLocationModeValue === 'editorial_event' ? 'selected' : '' ?>>Veranstaltung aus Datenbank</option>
<option value="custom" <?= $eventLocationModeValue === 'custom' ? 'selected' : '' ?>>Benutzerdefiniert</option>
</select>
</div>
<div class="stack gap-6" id="eventLocationSourceWrap">
<label class="label" for="eventLocationSourceInput" id="eventLocationSourceLabel">Ort aus Datenbank</label>
<input id="eventLocationSourceInput" class="input" list="eventLocationOptionList" placeholder="Eintrag aus der Datenbank auswählen" value="<?= htmlspecialchars($eventLocationSourceInputValue, ENT_QUOTES) ?>">
</div>
<div class="stack gap-6">
<label class="label" for="categoryInput">Kategorie</label>
@@ -1184,7 +1240,7 @@ if (!empty($canManageSystemSettings)) {
<?php elseif (($section ?? '') === 'places'): ?>
<p class="muted small" style="margin:0;">Neue Orte und Veranstaltungen werden erst nach Prüfung durch einen Admin freigegeben.</p>
<?php endif; ?>
<div class="form-grid">
<div class="form-grid" id="eventAddressFieldsWrap">
<div class="stack gap-6">
<label class="label" for="evZip">PLZ</label>
<input id="evZip" name="zip" class="input" maxlength="5" value="<?= htmlspecialchars($zipValue, ENT_QUOTES) ?>">
@@ -1194,18 +1250,17 @@ if (!empty($canManageSystemSettings)) {
<input id="evCity" name="city" class="input" value="<?= htmlspecialchars($cityValue, ENT_QUOTES) ?>">
</div>
</div>
<div class="form-grid">
<div class="form-grid" id="eventAddressStreetWrap">
<div class="stack gap-6">
<label class="label" for="evStreet">Straße / Adresse</label>
<input id="evStreet" name="street" class="input" placeholder="z. B. Musterstraße 12" value="<?= htmlspecialchars($streetValue, ENT_QUOTES) ?>">
<p class="muted small">Wird zur Karten-/Umkreissuche genutzt.</p>
</div>
<div class="stack gap-6">
<label class="label" for="evRegion">Region/Bezirk</label>
<input id="evRegion" name="region" class="input" value="<?= htmlspecialchars($regionValue, ENT_QUOTES) ?>">
</div>
</div>
<div class="flex gap-8" style="flex-wrap:wrap; align-items:center;">
<div class="flex gap-8" style="flex-wrap:wrap; align-items:center;" id="eventAddressActionsWrap">
<button class="btn ghost" type="button" id="btnEventAddressSearch">Adresse suchen</button>
<button class="btn ghost" type="button" id="btnEventBrowserLocation">📍 Aktuellen Standort</button>
</div>
@@ -1355,6 +1410,7 @@ if (!empty($canManageSystemSettings)) {
const btnEventBrowserLocation = document.getElementById('btnEventBrowserLocation');
const eventAddressStatus = document.getElementById('eventAddressStatus');
const mapSearch = document.getElementById('mapSearch');
const mapDetails = document.getElementById('mapDetails');
const latInput = document.getElementById('evLat');
const lngInput = document.getElementById('evLng');
const entryKindInput = document.getElementById('entryKind');
@@ -1364,6 +1420,15 @@ if (!empty($canManageSystemSettings)) {
const eventDateLabel = document.getElementById('eventDateLabel');
const eventChildrenWrap = document.getElementById('eventChildrenWrap');
const eventCapacityWrap = document.getElementById('eventCapacityWrap');
const eventLocationModeWrap = document.getElementById('eventLocationModeWrap');
const eventLocationModeSelect = document.getElementById('eventLocationMode');
const eventLocationSourceWrap = document.getElementById('eventLocationSourceWrap');
const eventLocationSourceInput = document.getElementById('eventLocationSourceInput');
const eventLocationSourceIdInput = document.getElementById('eventLocationSourceId');
const eventLocationSourceLabel = document.getElementById('eventLocationSourceLabel');
const eventAddressFieldsWrap = document.getElementById('eventAddressFieldsWrap');
const eventAddressStreetWrap = document.getElementById('eventAddressStreetWrap');
const eventAddressActionsWrap = document.getElementById('eventAddressActionsWrap');
const eventValidUntilWrap = document.getElementById('eventValidUntilWrap');
const eventPricingWrap = document.getElementById('eventPricingWrap');
const visibilityDetails = document.getElementById('visibilityDetails');
@@ -1371,6 +1436,7 @@ if (!empty($canManageSystemSettings)) {
const placeOpeningHoursWrap = document.getElementById('placeOpeningHoursWrap');
const eventRecurrenceWrap = document.getElementById('eventRecurrenceWrap');
const eventDateInput = document.getElementById('evDate');
const eventDescriptionInput = document.getElementById('evDesc');
const categoryInput = document.getElementById('categoryInput');
const categorySlugInput = document.getElementById('listingCategorySlug');
const categoryInputStatus = document.getElementById('categoryInputStatus');
@@ -1429,6 +1495,77 @@ if (!empty($canManageSystemSettings)) {
});
}
function findEventLocationOptionByLabel(label) {
const normalized = (label || '').trim();
if (!normalized) return null;
const option = document.querySelector(`#eventLocationOptionList option[value="${CSS.escape(normalized)}"]`);
if (!option) return null;
try {
return JSON.parse(option.getAttribute('data-location') || 'null');
} catch (error) {
return null;
}
}
function applyEventLocationOption(option) {
if (!option) return;
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = String(option.id || '');
if (streetInput) streetInput.value = String(option.street || '');
if (zipInput) zipInput.value = String(option.zip || '');
if (cityInput) cityInput.value = String(option.city || '');
if (regionInput) regionInput.value = String(option.region || '');
if (latInput) latInput.value = option.lat !== null && option.lat !== '' ? Number(option.lat).toFixed(7) : '';
if (lngInput) lngInput.value = option.lng !== null && option.lng !== '' ? Number(option.lng).toFixed(7) : '';
if (categoryInput) categoryInput.value = String(option.category_title || '');
if (categorySlugInput) categorySlugInput.value = String(option.category_slug || '');
if (categoryInputStatus) categoryInputStatus.textContent = '';
if (eventAddressStatus) eventAddressStatus.textContent = '';
}
function clearEventLocationSelection() {
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = '';
if (eventLocationSourceInput) eventLocationSourceInput.value = '';
if (categoryInputStatus) categoryInputStatus.textContent = '';
}
function syncEventLocationMode() {
const kind = entryKindInput?.value || 'event';
const mode = eventLocationModeSelect?.value || 'custom';
const isOwnEvent = kind === 'event';
const usesDatabaseLocation = isOwnEvent && mode !== 'custom';
setSectionState(eventLocationModeWrap, isOwnEvent);
setSectionState(eventLocationSourceWrap, usesDatabaseLocation);
setSectionState(eventAddressFieldsWrap, !usesDatabaseLocation);
setSectionState(eventAddressStreetWrap, !usesDatabaseLocation);
setSectionState(eventAddressActionsWrap, !usesDatabaseLocation);
setSectionState(mapDetails, !usesDatabaseLocation);
if (eventLocationSourceLabel) {
eventLocationSourceLabel.textContent = mode === 'editorial_event' ? 'Veranstaltung aus Datenbank' : 'Ort aus Datenbank';
}
if (categoryInput) {
categoryInput.readOnly = usesDatabaseLocation;
if (usesDatabaseLocation) {
categoryInput.removeAttribute('list');
} else {
categoryInput.setAttribute('list', 'listingCategoryList');
}
}
if (usesDatabaseLocation) {
const option = findEventLocationOptionByLabel(eventLocationSourceInput?.value || '');
if (option && String(option.listing_type || '') === mode) {
applyEventLocationOption(option);
} else {
clearEventLocationSelection();
}
} else if (isOwnEvent) {
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = '';
}
}
function syncEventEntryKind(nextKind) {
const kind = nextKind || entryKindInput?.value || 'event';
if (entryKindInput) entryKindInput.value = kind;
@@ -1451,7 +1588,8 @@ if (!empty($canManageSystemSettings)) {
setSectionState(eventDateRow, kind !== 'place');
if (eventDateInput) eventDateInput.required = kind !== 'place';
if (categoryInput) categoryInput.required = true;
if (eventDescriptionInput) eventDescriptionInput.required = kind !== 'event';
if (categoryInput) categoryInput.required = kind !== 'event';
setSectionState(eventChildrenWrap, kind === 'event' || kind === 'editorial_event');
setSectionState(eventCapacityWrap, kind === 'event');
setSectionState(eventValidUntilWrap, kind === 'editorial_event');
@@ -1463,6 +1601,7 @@ if (!empty($canManageSystemSettings)) {
if (eventDateLabel) {
eventDateLabel.textContent = kind === 'editorial_event' ? 'Gültig von' : 'Datum';
}
syncEventLocationMode();
}
entryKindButtons.forEach((button) => {
@@ -1470,6 +1609,19 @@ if (!empty($canManageSystemSettings)) {
syncEventEntryKind(button.getAttribute('data-entry-kind-choice') || 'event');
});
});
eventLocationModeSelect?.addEventListener('change', () => {
clearEventLocationSelection();
syncEventLocationMode();
});
eventLocationSourceInput?.addEventListener('change', () => {
const mode = eventLocationModeSelect?.value || 'custom';
const option = findEventLocationOptionByLabel(eventLocationSourceInput.value || '');
if (!option || String(option.listing_type || '') !== mode) {
clearEventLocationSelection();
return;
}
applyEventLocationOption(option);
});
syncEventEntryKind(entryKindInput?.value || 'event');
function createOpeningRow() {
@@ -1503,8 +1655,12 @@ if (!empty($canManageSystemSettings)) {
btnAddPriceRow?.addEventListener('click', createPriceRow);
function syncCategoryInputState() {
const value = (categoryInput?.value || '').trim().toLowerCase();
if (!categorySlugInput || !categoryInputStatus) return;
if ((entryKindInput?.value || 'event') === 'event' && (eventLocationModeSelect?.value || 'custom') !== 'custom') {
categoryInputStatus.textContent = '';
return;
}
const value = (categoryInput?.value || '').trim().toLowerCase();
if (!value) {
categorySlugInput.value = '';
categoryInputStatus.textContent = '';