yxcyxc
All checks were successful
Deploy / deploy (push) Successful in 55s

This commit is contained in:
2026-08-04 01:20:59 +02:00
parent fa6e975df4
commit 1a58ec91de
10 changed files with 525 additions and 87 deletions

View File

@@ -6,10 +6,32 @@ $avatarPlaceholder = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/
$avatarStyleKeys = array_keys($avatarBuilder ?? []);
$currentAvatarStyle = (string)($profile['avatar_style'] ?? ($avatarStyleKeys[0] ?? 'lorelei'));
$dashboardUserId = (int)($_SESSION['user_id'] ?? 0);
$editing = isset($editEvent) && $editEvent !== null;
$editingEvent = isset($editEvent) && $editEvent !== null;
$editingListing = isset($editListing) && $editListing !== null;
$editing = $editingEvent || $editingListing;
$currentEntryKind = $editingListing ? (string)($editListing['listing_type'] ?? 'editorial_event') : 'event';
$actionEvent = $editing ? 'event_update' : 'event_add';
$startVal = $editEvent ? date('Y-m-d\TH:i', strtotime((string)$editEvent['starts_at'])) : '';
$allowNoKidsChecked = $editEvent ? ((int)$editEvent['allow_kids'] === 0) : false;
$startVal = '';
if ($editingEvent && !empty($editEvent['starts_at'])) {
$startVal = date('Y-m-d\TH:i', strtotime((string)$editEvent['starts_at']));
} elseif ($editingListing && !empty($editListing['starts_at'])) {
$startVal = date('Y-m-d\TH:i', strtotime((string)$editListing['starts_at']));
}
$allowNoKidsChecked = $editingEvent ? ((int)$editEvent['allow_kids'] === 0) : false;
$eventTitle = $editingEvent ? 'Event bearbeiten' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort bearbeiten' : 'Veranstaltung bearbeiten') : 'Neuen Eintrag anlegen');
$submitLabel = $editingEvent ? 'Event speichern' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort speichern' : 'Veranstaltung speichern') : 'Eintrag anlegen');
$titleValue = $editingEvent ? (string)($editEvent['title'] ?? '') : (string)($editListing['title'] ?? '');
$teaserValue = $editingEvent ? (string)($editEvent['teaser_public'] ?? '') : (string)($editListing['teaser_public'] ?? '');
$descriptionValue = $editingEvent ? (string)($editEvent['description'] ?? '') : (string)($editListing['description'] ?? '');
$locationLabelValue = $editingEvent ? (string)($editEvent['location_label'] ?? '') : '';
$streetValue = $editingEvent ? (string)($editEvent['street'] ?? '') : (string)($editListing['street'] ?? '');
$zipValue = $editingEvent ? (string)($editEvent['zip'] ?? '') : (string)($editListing['zip'] ?? '');
$cityValue = $editingEvent ? (string)($editEvent['city'] ?? '') : (string)($editListing['city'] ?? '');
$regionValue = $editingEvent ? (string)($editEvent['region'] ?? '') : (string)($editListing['region'] ?? '');
$latValue = $editingEvent ? (string)($editEvent['lat'] ?? '') : (string)($editListing['lat'] ?? '');
$lngValue = $editingEvent ? (string)($editEvent['lng'] ?? '') : (string)($editListing['lng'] ?? '');
$visibilityValue = $editingEvent ? (string)($editEvent['visibility'] ?? 'public') : (string)($editListing['visibility'] ?? 'public');
$categorySlugValue = $editingListing ? (string)($editListing['category_slug'] ?? '') : '';
$editingChild = isset($editChild) && $editChild !== null;
$childAction = $editingChild ? 'child_update' : 'child_add';
$childModalTitle = $editingChild ? 'Kind bearbeiten' : 'Kind hinzufügen';
@@ -205,15 +227,19 @@ if (!empty($canManageSystemSettings)) {
<?php if ($section === 'events'): ?>
<section class="account-panel" id="events">
<div class="account-panel__head">
<h2>Deine Events</h2>
<p class="muted">Erstelle, bearbeite und verwalte deine eigenen Events und finde passende Termine für gemeinsame Treffen.</p>
<h2>Deine Einträge</h2>
<p class="muted">Lege eigene Events, dauerhafte Orte und sonstige zeitlich begrenzte Veranstaltungen sauber getrennt an.</p>
</div>
<div class="account-panel__body">
<div class="flex gap-12" style="margin:0 0 16px 0; flex-wrap: wrap;">
<button class="btn" type="button" data-modal-open="modalEvent">Event anlegen</button>
<button class="btn" type="button" data-modal-open="modalEvent">Eintrag anlegen</button>
</div>
<div class="card" style="margin-bottom:18px;">
<strong>Eigene Events</strong>
<p class="muted small" style="margin:8px 0 0;">Von dir organisierte Treffen oder Events mit eigener Teilnahme-Logik.</p>
</div>
<?php if (!$eventsUpcoming): ?>
<p class="muted small">Keine zukünftigen Events angelegt.</p>
<p class="muted small">Keine zukünftigen eigenen Events angelegt.</p>
<?php else: ?>
<ul class="dash-list" style="margin-top:10px;">
<?php foreach ($eventsUpcoming as $e): ?>
@@ -277,6 +303,45 @@ if (!empty($canManageSystemSettings)) {
</ul>
<?php endif; ?>
</details>
<div class="card" style="margin:22px 0 14px;">
<strong>Orte und sonstige Veranstaltungen</strong>
<p class="muted small" style="margin:8px 0 0;">Hier erscheinen dauerhafte Orte wie Spielplätze, Cafés oder Restaurants sowie zeitlich begrenzte Hinweise wie Zirkus oder Aktionsflächen.</p>
</div>
<?php if (!$otherListings): ?>
<p class="muted small">Noch keine Orte oder sonstigen Veranstaltungen angelegt.</p>
<?php else: ?>
<ul class="dash-list" style="margin-top:10px;">
<?php foreach ($otherListings as $entry): ?>
<li>
<div style="display:flex; justify-content:space-between; gap:12px; align-items:center; flex-wrap: wrap;">
<div>
<strong><?= htmlspecialchars((string)$entry['title'], ENT_QUOTES) ?></strong>
<span class="badge"><?= ($entry['listing_type'] ?? '') === 'place' ? 'Ort' : 'Sonstige Veranstaltung' ?></span>
<?php if (!empty($entry['category_title'])): ?>
<span class="badge"><?= htmlspecialchars((string)$entry['category_title'], ENT_QUOTES) ?></span>
<?php endif; ?>
<div class="muted small" style="margin-top:4px;">
<?= htmlspecialchars(trim(implode(' · ', array_filter([
(string)($entry['city'] ?? ''),
!empty($entry['starts_at']) ? (string)$entry['starts_at'] : null,
(string)($entry['visibility'] ?? ''),
]))), ENT_QUOTES) ?>
</div>
</div>
<div class="flex gap-8" style="flex-wrap: wrap;">
<a class="btn ghost" href="/dashboard?section=events&edit_listing=<?= (int)$entry['id'] ?>#events">Bearbeiten</a>
<form method="post" action="/dashboard?section=events#events" onsubmit="return confirm('Eintrag wirklich löschen?');">
<input type="hidden" name="action" value="listing_delete">
<input type="hidden" name="listing_id" value="<?= (int)$entry['id'] ?>">
<button class="btn ghost" type="submit">Löschen</button>
</form>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</section>
<?php endif; ?>
@@ -780,65 +845,99 @@ if (!empty($canManageSystemSettings)) {
<div class="modal" id="modalEvent">
<div class="panel">
<div class="head flex between center-y">
<h3><?= $editing ? 'Event bearbeiten' : 'Neues Event' ?></h3>
<h3><?= htmlspecialchars($eventTitle, ENT_QUOTES) ?></h3>
<button class="btn ghost" type="button" data-modal-close>✕</button>
</div>
<form class="stack gap-12" style="margin-top: 10px;" method="post" action="/dashboard?section=events#events">
<input type="hidden" name="action" value="<?= htmlspecialchars($actionEvent, ENT_QUOTES) ?>">
<?php if ($editing && $editEvent): ?>
<input type="hidden" name="entry_kind" id="entryKind" value="<?= htmlspecialchars($currentEntryKind, ENT_QUOTES) ?>">
<?php if ($editingEvent && $editEvent): ?>
<input type="hidden" name="event_id" value="<?= (int)$editEvent['id'] ?>">
<?php endif; ?>
<?php if ($editingListing && $editListing): ?>
<input type="hidden" name="listing_id" value="<?= (int)$editListing['id'] ?>">
<?php endif; ?>
<?php if (!$editing): ?>
<div class="stack gap-8">
<label class="label">Was möchtest du anlegen?</label>
<div class="chips" style="flex-wrap:wrap;">
<button class="btn ghost" type="button" data-entry-kind-choice="event">Eigenes Event</button>
<button class="btn ghost" type="button" data-entry-kind-choice="place">Ort</button>
<button class="btn ghost" type="button" data-entry-kind-choice="editorial_event">Sonstige Veranstaltung</button>
</div>
<p class="muted small" id="entryKindHint" style="margin:0;">Eigenes Event: von dir organisiert und mit konkretem Termin.</p>
</div>
<?php else: ?>
<div class="card">
<strong>Eintragstyp</strong>
<p class="muted small" style="margin:8px 0 0;">
<?= $currentEntryKind === 'place' ? 'Ort: dauerhaft verfügbar.' : ($currentEntryKind === 'editorial_event' ? 'Sonstige Veranstaltung: zeitlich begrenzter Hinweis.' : 'Eigenes Event: von dir organisiert.') ?>
</p>
</div>
<?php endif; ?>
<div class="form-grid">
<div class="stack gap-6">
<label class="label" for="evTitle">Titel</label>
<input id="evTitle" name="title" class="input" placeholder="z. B. Väter-Kaffee im Park" required value="<?= htmlspecialchars($editEvent['title'] ?? '', ENT_QUOTES) ?>">
<input id="evTitle" name="title" class="input" placeholder="z. B. Väter-Kaffee im Park" required value="<?= htmlspecialchars($titleValue, ENT_QUOTES) ?>">
</div>
<div class="stack gap-6">
<label class="label" for="evTeaser">Kurzbeschreibung</label>
<input id="evTeaser" name="teaser" class="input" placeholder="Kurztext für Gäste" required value="<?= htmlspecialchars($editEvent['teaser_public'] ?? '', ENT_QUOTES) ?>">
<input id="evTeaser" name="teaser" class="input" placeholder="Kurztext für Gäste" required value="<?= htmlspecialchars($teaserValue, ENT_QUOTES) ?>">
</div>
</div>
<div class="stack gap-6">
<label class="label" for="evDesc">Beschreibung (voll)</label>
<textarea id="evDesc" name="description" class="textarea" rows="3" placeholder="Was soll passieren, was mitbringen?" required><?= htmlspecialchars($editEvent['description'] ?? '', ENT_QUOTES) ?></textarea>
<textarea id="evDesc" name="description" class="textarea" rows="3" placeholder="Was soll passieren, was mitbringen?" required><?= htmlspecialchars($descriptionValue, ENT_QUOTES) ?></textarea>
</div>
<div class="form-grid">
<div class="form-grid" id="eventDateRow">
<div class="stack gap-6">
<label class="label" for="evDate">Datum & Uhrzeit</label>
<input id="evDate" name="starts_at" class="input" type="datetime-local" required value="<?= htmlspecialchars($startVal, ENT_QUOTES) ?>">
</div>
<div class="stack gap-6">
<div class="stack gap-6" id="eventLocationLabelWrap">
<label class="label" for "evLoc">Ort/Label</label>
<input id="evLoc" name="location_label" class="input" placeholder="Park / Café" value="<?= htmlspecialchars($editEvent['location_label'] ?? '', ENT_QUOTES) ?>">
<input id="evLoc" name="location_label" class="input" placeholder="Park / Café" value="<?= htmlspecialchars($locationLabelValue, ENT_QUOTES) ?>">
</div>
</div>
<div class="stack gap-6" id="eventCategoryWrap">
<label class="label" for="listingCategorySlug">Kategorie</label>
<select id="listingCategorySlug" name="category_slug" class="select">
<option value="">Bitte auswählen</option>
<?php foreach ($listingCategories as $category): ?>
<option value="<?= htmlspecialchars((string)$category['slug'], ENT_QUOTES) ?>" <?= $categorySlugValue === (string)$category['slug'] ? 'selected' : '' ?>>
<?= htmlspecialchars((string)$category['title'], ENT_QUOTES) ?>
</option>
<?php endforeach; ?>
</select>
<p class="muted small" style="margin:0;">Für Orte und sonstige Veranstaltungen, damit Spielplatz, Café, Restaurant, Zoo oder Zirkus sauber unterscheidbar bleiben.</p>
</div>
<div class="form-grid">
<div class="stack gap-6">
<label class="label" for="evZip">PLZ</label>
<input id="evZip" name="zip" class="input" maxlength="5" value="<?= htmlspecialchars($editEvent['zip'] ?? '', ENT_QUOTES) ?>">
<input id="evZip" name="zip" class="input" maxlength="5" value="<?= htmlspecialchars($zipValue, ENT_QUOTES) ?>">
</div>
<div class="stack gap-6">
<label class="label" for="evCity">Stadt</label>
<input id="evCity" name="city" class="input" value="<?= htmlspecialchars($editEvent['city'] ?? '', ENT_QUOTES) ?>">
<input id="evCity" name="city" class="input" value="<?= htmlspecialchars($cityValue, ENT_QUOTES) ?>">
</div>
</div>
<div class="form-grid">
<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($editEvent['street'] ?? '', ENT_QUOTES) ?>">
<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($editEvent['region'] ?? '', ENT_QUOTES) ?>">
<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;">
<button class="btn ghost" type="button" id="btnAddrToMap">Adresse auf Karte setzen</button>
<span class="muted small">Hält Karte und Adresse synchron.</span>
</div>
<input type="hidden" id="evLat" name="lat" value="<?= htmlspecialchars($editEvent['lat'] ?? '', ENT_QUOTES) ?>">
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($editEvent['lng'] ?? '', ENT_QUOTES) ?>">
<input type="hidden" id="evLat" name="lat" value="<?= htmlspecialchars($latValue, ENT_QUOTES) ?>">
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($lngValue, ENT_QUOTES) ?>">
<div class="stack gap-6">
<button class="btn ghost" type="button" id="btnMap">Auf Karte suchen</button>
<div id="mapWrapper" class="map-wrapper" hidden>
@@ -857,17 +956,17 @@ if (!empty($canManageSystemSettings)) {
<div class="stack gap-6">
<label class="label" for="evVis">Sichtbarkeit</label>
<select id="evVis" name="visibility" class="select">
<option value="public" <?= (($editEvent['visibility'] ?? '') === 'public') ? 'selected' : '' ?>>Öffentlich</option>
<option value="members" <?= (($editEvent['visibility'] ?? '') === 'members') ? 'selected' : '' ?>>Nur Mitglieder</option>
<option value="public" <?= $visibilityValue === 'public' ? 'selected' : '' ?>>Öffentlich</option>
<option value="members" <?= $visibilityValue === 'members' ? 'selected' : '' ?>>Nur Mitglieder</option>
</select>
</div>
</div>
<label class="label" style="display:flex; align-items:center; gap:8px;">
<label class="label" id="eventKidsWrap" style="display:flex; align-items:center; gap:8px;">
<input type="checkbox" name="allow_kids" <?= $allowNoKidsChecked ? 'checked' : '' ?>> Event ohne Kinder
</label>
<div class="flex gap-12">
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
<button class="btn" type="submit"><?= $editing ? 'Event speichern' : 'Event anlegen' ?></button>
<button class="btn" type="submit"><?= htmlspecialchars($submitLabel, ENT_QUOTES) ?></button>
</div>
</form>
</div>
@@ -903,6 +1002,15 @@ if (!empty($canManageSystemSettings)) {
const mapSearch = document.getElementById('mapSearch');
const latInput = document.getElementById('evLat');
const lngInput = document.getElementById('evLng');
const entryKindInput = document.getElementById('entryKind');
const entryKindHint = document.getElementById('entryKindHint');
const entryKindButtons = document.querySelectorAll('[data-entry-kind-choice]');
const eventDateRow = document.getElementById('eventDateRow');
const eventLocationLabelWrap = document.getElementById('eventLocationLabelWrap');
const eventCategoryWrap = document.getElementById('eventCategoryWrap');
const eventKidsWrap = document.getElementById('eventKidsWrap');
const eventDateInput = document.getElementById('evDate');
const categorySelect = document.getElementById('listingCategorySlug');
const streetInput = document.getElementById('evStreet');
const zipInput = document.getElementById('evZip');
const cityInput = document.getElementById('evCity');
@@ -923,6 +1031,42 @@ if (!empty($canManageSystemSettings)) {
const profileAddressResultsModal = document.getElementById('modalProfileAddressResults');
let map, marker;
function syncEventEntryKind(nextKind) {
const kind = nextKind || entryKindInput?.value || 'event';
if (entryKindInput) entryKindInput.value = kind;
entryKindButtons.forEach((button) => {
const active = button.getAttribute('data-entry-kind-choice') === kind;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
if (entryKindHint) {
if (kind === 'place') {
entryKindHint.textContent = 'Ort: dauerhaft verfügbar, zum Beispiel Spielplatz, Café, Restaurant oder Zoo.';
} else if (kind === 'editorial_event') {
entryKindHint.textContent = 'Sonstige Veranstaltung: zeitlich begrenzter Hinweis, zum Beispiel Zirkus oder Hüpfburgenstadt.';
} else {
entryKindHint.textContent = 'Eigenes Event: von dir organisiert und mit konkretem Termin.';
}
}
if (eventDateRow) eventDateRow.hidden = kind === 'place';
if (eventDateInput) eventDateInput.required = kind !== 'place';
if (eventLocationLabelWrap) eventLocationLabelWrap.hidden = kind !== 'event';
if (eventCategoryWrap) eventCategoryWrap.hidden = kind === 'event';
if (categorySelect) categorySelect.required = kind !== 'event';
if (eventKidsWrap) eventKidsWrap.hidden = kind !== 'event';
}
entryKindButtons.forEach((button) => {
button.addEventListener('click', () => {
syncEventEntryKind(button.getAttribute('data-entry-kind-choice') || 'event');
});
});
syncEventEntryKind(entryKindInput?.value || 'event');
function ensureLeaflet(callback) {
if (!window.PKTConsent || !window.PKTConsent.has('external_services')) {
alert('Für Karten und Adresssuche bitte zuerst die externen Dienste in den Cookie-Einstellungen erlauben.');