XyX
All checks were successful
Deploy / deploy (push) Successful in 53s

This commit is contained in:
2026-08-06 01:17:05 +02:00
parent 80f1ad6b99
commit fa40ca1a60
9 changed files with 165 additions and 81 deletions

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff Projektkontext
Stand: 2026-08-04
Stand: 2026-08-05
## Kurzbeschreibung
Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind lokale Events, Termine und Treffen, ein Community-Forum, Mitgliederprofile mit optionalen Kinderinfos und ein Moderations-/Adminbereich.
@@ -16,17 +16,16 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
- Seiten-Admins erhalten eine eigene System-Sektion für globale Betriebs- und Diensteschalter.
## Konto / Mitgliederbereich
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Community`, `Einstellungen`, `Abmelden`
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Ausflugsziele`, `Community`, `Einstellungen`, `Abmelden`
- Kinder können angelegt, bearbeitet und gelöscht werden.
- Wenn ein Geburtsdatum gesetzt ist, wird das Alter automatisch berechnet und später bei Bedarf aktualisiert.
- Die Konto-E-Mail wird app-seitig verschlüsselt gespeichert und über einen separaten HMAC-Lookup-Hash adressiert.
- Profiladresse kann per Suche oder Browser-Standort übernommen werden.
- Im Bereich `Events` gibt es jetzt drei Eintragstypen: eigenes Event, Ort und sonstige Veranstaltung.
- Die Eingabe im Bereich `Events` soll so einfach wie möglich bleiben und zeigt deshalb zuerst nur Pflichtangaben.
- Kategorien werden im Mitgliederbereich nicht mehr per Select gewählt, sondern per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt.
- Eigene Events unterstützen Kategorie, optionales Bild, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl und Sichtbarkeit.
- `Ort` nutzt die gleiche Adresslogik wie das Profil, blendet den Event-Termin aus und erlaubt stattdessen flexible Öffnungs-/Zeitfenster, optionale Kontaktangaben und flexible Preisregeln.
- `Sonstige Veranstaltung` nutzt ebenfalls die Profil-Adresslogik, unterstützt Wiederholungen, ein Pflichtfeld `Gültig bis`, flexible Preisregeln, Bild-Upload und die Angabe `Mit Kindern`.
- Der Bereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen begrenzt.
- Orte und Veranstaltungen als Ausflugsziele sind vorerst in einen separaten Mitgliederbereichspunkt `Ausflugsziele` verschoben.
- Die Eingabe im Bereich `Events` soll so einfach wie möglich bleiben und zeigt deshalb nur die für echte Termine relevanten Felder.
- Kategorien werden im Mitgliederbereich per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt.
- Eigene Events unterstützen Kategorie, optionales Bild, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl und direkte Karten-/Adress-Synchronisierung.
## Technischer Rahmen
- eigener Front-Controller in `public/index.php`

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff Projektstruktur
Stand: 2026-08-04
Stand: 2026-08-05
## Projektordner
- `config/`: Konfigurationen, inklusive `prod/` und `staging/`
@@ -14,12 +14,12 @@ Stand: 2026-08-04
## Aktuelle Strukturhinweise
- Avatar-Verwaltung: `src/App/Avatar/AvatarManager.php`
- aktive Styles: `src/App/Avatar/Lorelei.php`, `src/App/Avatar/Croodles.php`
- Profil-/Kinder-/Event-Logik im Mitgliederbereich: `partials/landing/account/dashboard.php`
- Profil-/Kinder-/Event-/Ausflugsziel-Logik im Mitgliederbereich: `partials/landing/account/dashboard.php`
- Consent- und rechtliche Texte: `public/page/datenschutz.php`
- System-Einstellungen: `src/App/SystemSettings.php`
- neue Listing-/Ort-Basis: `src/App/ListingCatalog.php`
- UI für Eintragstyp-Auswahl im Mitgliederbereich: `partials/landing/account/dashboard.php`
- Preis-, Wiederholungs-, Bild- und Ortseingabe für neue Listing-Typen: `partials/landing/account/dashboard.php` plus Speicherung in `src/App/ListingCatalog.php`
- UI für getrennte Bereiche `Events` und `Ausflugsziele` im Mitgliederbereich: `partials/landing/account/dashboard.php`
- Preis-, Bild- und Ortseingabe für Ausflugsziele sowie reduzierte Event-Maske: `partials/landing/account/dashboard.php` plus Speicherung in `src/App/ListingCatalog.php`
- Legacy-Eigen-Events wurden erweitert in `src/App/AccountPages.php` und `schema.sql` um Kategorie- und Bildfelder
- Kategorien-Prüfung und Zusammenführung für Seiten-Admins liegen ebenfalls in `partials/landing/account/dashboard.php` mit Logik in `src/App/ListingCatalog.php`

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff
Stand: 2026-08-04
Stand: 2026-08-05
Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale Events, Termine und Treffen, Community-Austausch und einen geschützten Mitgliederbereich.
@@ -16,15 +16,15 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- Community-Admin-Bereich für Bewerbungen, Meldungen, Rollen und Migration
- standortbasierte Sortierung für die neuesten Events
- Mitgliederbereich mit linker Bereichsnavigation
- Profil-Menü mit `Profil`, `Kinder`, `Events`, `Community`, `Einstellungen`
- Profil-Menü mit `Profil`, `Kinder`, `Events`, `Ausflugsziele`, `Community`, `Einstellungen`
- Konto-E-Mails sowie sensible Profilfelder werden app-seitig verschlüsselt gespeichert
- Kinder können angelegt, bearbeitet und gelöscht werden
- Profiladresse mit verschlüsselter Straße/Hausnummer, Adresssuche, Browser-Übernahme und Validierung
- System-Sektion für Seiten-Admins mit Wartungs- und Diensteschaltern
- neue Datenbasis für ein späteres Termin-, Ort- und Veranstaltungssystem angelegt
- Mitgliederbereich `Events` unterstützt jetzt drei Anlagetypen: `Eigenes Event`, `Ort`, `Sonstige Veranstaltung`
- Eigene Events haben jetzt Kategorie, optionales Bild, klare Kinderangabe, optionale Platzzahl und serverseitig validierte Adressdaten
- Orte und sonstige Veranstaltungen haben jetzt eine angepasste Eingabemaske mit Ortssuche, Browser-Adressübernahme, serverseitiger Adressvalidierung, flexiblen Preisregeln, flexiblen Öffnungs-/Zeitfenstern und Bild-Upload
- Mitgliederbereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen fokussiert
- Eigene Events haben jetzt Kategorie, optionales Bild, klare Kinderangabe, optionale Platzzahl und eine direktere Karten-/Adress-Synchronisierung
- Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Ausflugsziele` verschoben
- Die Eingabemasken sind jetzt progressiv aufgebaut: erst Pflichtangaben, optionale Daten in einklappbaren Bereichen
- Kategorien laufen jetzt über eine Sucheingabe mit bestehenden Vorschlägen; neue Kategorien werden automatisch angelegt und können im Systembereich von Seiten-Admins zusammengeführt werden

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff Project Context
Updated: 2026-08-04
Updated: 2026-08-05
## Summary
Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events, appointments, and meetups, a community forum, member profiles with optional child information, and a moderation/admin area.
@@ -16,17 +16,16 @@ Papa-Kind-Treff is a PHP-based platform for fathers. Core areas are local events
- Site admins get a dedicated system section for global operating and service flags.
## Account / Member Area
- Profile menu currently contains `Profile`, `Children`, `Events`, `Community`, `Settings`, `Logout`.
- Profile menu currently contains `Profile`, `Children`, `Events`, `Outings`, `Community`, `Settings`, `Logout`.
- Children can be created, edited, and deleted.
- If a birth date is set, age is calculated automatically and updated later when needed.
- Account email is encrypted application-side and addressed through a separate HMAC lookup hash.
- Profile address can be completed via address search or browser-based location import.
- The `Events` area now distinguishes between own events, places, and other time-limited event entries.
- The `Events` input flow should stay as simple as possible and therefore shows required fields first.
- Categories in the member area are no longer chosen via select only, but via a search input with existing suggestions and automatic creation when needed.
- Own events support category, optional image, a clear `with children` yes/no field, optional capacity, and visibility.
- `Place` uses the same address flow as the profile, hides the event date, and instead supports flexible opening/time slots, optional contact data, and flexible pricing rules.
- `Other Event` also uses the profile-style address flow, supports recurrence, a required `valid until` field, flexible pricing rules, image upload, and a `with children` flag.
- The `Events` area is limited again to real own events and event participations.
- Places and event-like outing targets were moved for now into a separate member-area section `Outings`.
- The `Events` input flow should stay as simple as possible and therefore only shows fields relevant for real scheduled events.
- Categories in the member area are handled via a search input with existing suggestions and automatic creation when needed.
- Own events support category, optional image, a clear `with children` yes/no field, optional capacity, and direct map/address synchronization.
## Technical Frame
- custom front controller in `public/index.php`

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff Project Structure
Updated: 2026-08-04
Updated: 2026-08-05
## Main Directories
- `config/`: configuration, including `prod/` and `staging/`
@@ -14,12 +14,12 @@ Updated: 2026-08-04
## Current Structure Notes
- avatar management: `src/App/Avatar/AvatarManager.php`
- active avatar styles: `src/App/Avatar/Lorelei.php`, `src/App/Avatar/Croodles.php`
- profile/children/event logic inside member area: `partials/landing/account/dashboard.php`
- profile/children/event/outing logic inside member area: `partials/landing/account/dashboard.php`
- consent and legal texts: `public/page/datenschutz.php`
- system settings: `src/App/SystemSettings.php`
- new listing/place base: `src/App/ListingCatalog.php`
- entry type selector UI in member area: `partials/landing/account/dashboard.php`
- pricing, recurrence, image upload, and place/address input for new listing types: `partials/landing/account/dashboard.php` with persistence in `src/App/ListingCatalog.php`
- separated `Events` and `Outings` UI in member area: `partials/landing/account/dashboard.php`
- pricing, image upload, and place/address input for outings plus a reduced own-event form: `partials/landing/account/dashboard.php` with persistence in `src/App/ListingCatalog.php`
- legacy own events were extended in `src/App/AccountPages.php` and `schema.sql` with category and image fields
## Documentation Rule

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff
Updated: 2026-08-04
Updated: 2026-08-05
Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, appointments, and meetups, community exchange, and a protected member area.
@@ -16,15 +16,15 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
- community admin area for applications, reports, roles, and migrations
- location-based ordering for the newest events
- member area with left-side section navigation
- profile menu with `Profile`, `Children`, `Events`, `Community`, `Settings`
- profile menu with `Profile`, `Children`, `Events`, `Outings`, `Community`, `Settings`
- account emails and sensitive profile fields are stored encrypted application-side
- children can be created, edited, and deleted
- profile address supports encrypted street/house number, address search, browser import, and validation
- system section for site admins with maintenance and service toggles
- new data foundation prepared for a broader event, place, and listing system
- member area `Events` now supports three creation types: `Own Event`, `Place`, and `Other Event`
- own events now include category, optional image, explicit child suitability, optional capacity, and server-side validated address data
- places and other events now use adaptive forms with place lookup, browser address pickup, server-side address validation, flexible pricing rules, flexible opening/time slots, and image upload
- member area `Events` is now limited again to real own events and event participations
- own events now include category, optional image, explicit child suitability, optional capacity, and more direct map/address synchronization
- places and other events were moved for now into a separate member-area section `Outings`
- the member-area entry forms now follow a progressive approach: required fields first, optional data inside collapsible sections
- categories now use a search input with existing suggestions; new categories are created automatically and can be merged by site admins in the system area

View File

@@ -1,6 +1,6 @@
# Papa-Kind-Treff Start-Doku
Stand: 2026-08-04
Stand: 2026-08-05
Diese Datei ist die zentrale Start-Doku für neue Chats und den schnellen Projekteinstieg.
@@ -33,12 +33,12 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
- Primärbegriff im Produkt: `Events`
- `Termine` und `Treffen` ergänzend in SEO- und Erklärungstexten
- Hauptnavigation aktuell: `Home`, `Event Suche`, `Community`
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Community`, `Einstellungen`, `Abmelden`
- Profil-Menü aktuell: `Profil`, `Kinder`, `Events`, `Ausflugsziele`, `Community`, `Einstellungen`, `Abmelden`
- neue interne Grundstruktur: `listing_places`, `listings`, `listing_occurrences`, `listing_prices`, `listing_benefits`
- Seiten-Admins haben zusätzlich eine System-Sektion für globale Wartungs- und Diensteschalter
- Im Mitgliederbereich unter `Events` können Nutzer jetzt zwischen `Eigenes Event`, `Ort` und `sonstiger Veranstaltung` unterscheiden
- Für Orte und sonstige Veranstaltungen gibt es jetzt typabhängige Masken mit Ortssuche, Browser-Adressübernahme, Adressprüfung, flexiblen Preisregeln, flexiblen Öffnungs-/Zeitfenstern und Bild-Upload
- Eigene Events unterstützen jetzt zusätzlich Kategorie, Bild-Upload, klare Kinderangabe und optionale Platzzahl
- Im Mitgliederbereich ist `Events` jetzt wieder auf echte eigene Events und Event-Teilnahmen beschränkt
- Orte und sonstige Veranstaltungen laufen jetzt separat unter `Ausflugsziele`
- Eigene Events unterstützen aktuell Kategorie, Bild-Upload, klare Kinderangabe, optionale Platzzahl und direkte Karten-/Adress-Synchronisierung
- Die Eingabe im Mitgliederbereich ist jetzt bewusst vereinfacht: zuerst nur Pflichtangaben, optionale Angaben in einklappbaren Bereichen
- Kategorien werden jetzt über eine Sucheingabe mit bestehenden Vorschlägen gepflegt; neue Kategorien werden automatisch angelegt und sind für Seiten-Admins im Systembereich zusammenführbar

View File

@@ -9,7 +9,7 @@ $dashboardUserId = (int)($_SESSION['user_id'] ?? 0);
$editingEvent = isset($editEvent) && $editEvent !== null;
$editingListing = isset($editListing) && $editListing !== null;
$editing = $editingEvent || $editingListing;
$currentEntryKind = $editingListing ? (string)($editListing['listing_type'] ?? 'editorial_event') : 'event';
$currentEntryKind = $editingListing ? (string)($editListing['listing_type'] ?? 'editorial_event') : (($section ?? 'events') === 'places' ? 'place' : 'event');
$actionEvent = $editing ? 'event_update' : 'event_add';
$startVal = '';
if ($editingEvent && !empty($editEvent['starts_at'])) {
@@ -18,8 +18,8 @@ if ($editingEvent && !empty($editEvent['starts_at'])) {
$startVal = date('Y-m-d', strtotime((string)$editListing['starts_at']));
}
$withChildrenValue = $editingEvent ? (((int)($editEvent['allow_kids'] ?? 1)) === 1 ? 'yes' : 'no') : (($editingListing && $currentEntryKind === 'editorial_event' && !empty($editListing['supports_registration'])) ? 'yes' : 'yes');
$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');
$eventTitle = $editingEvent ? 'Event bearbeiten' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort bearbeiten' : 'Veranstaltung bearbeiten') : ((($section ?? 'events') === 'places') ? 'Ausflugsziel anlegen' : 'Neues Event anlegen'));
$submitLabel = $editingEvent ? 'Event speichern' : ($editingListing ? ($currentEntryKind === 'place' ? 'Ort speichern' : 'Veranstaltung speichern') : ((($section ?? 'events') === 'places') ? 'Ausflugsziel anlegen' : 'Event 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'] ?? '');
@@ -87,9 +87,12 @@ $sectionLinks = [
'profile' => 'Profil',
'children' => 'Kinder',
'events' => 'Events',
'places' => 'Ausflugsziele',
'community' => 'Community',
'settings' => 'Einstellungen',
];
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places');
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
if (!empty($canManageSystemSettings)) {
$sectionLinks['system'] = 'System';
}
@@ -275,12 +278,12 @@ if (!empty($canManageSystemSettings)) {
<?php if ($section === 'events'): ?>
<section class="account-panel" id="events">
<div class="account-panel__head">
<h2>Deine Einträge</h2>
<p class="muted">Lege eigene Events, dauerhafte Orte und sonstige zeitlich begrenzte Veranstaltungen sauber getrennt an.</p>
<h2>Deine Events</h2>
<p class="muted">Hier verwaltest du nur echte eigene Events und Events, an denen du teilnimmst.</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">Eintrag anlegen</button>
<button class="btn" type="button" data-modal-open="modalEvent">Eigenes Event anlegen</button>
</div>
<div class="card" style="margin-bottom:18px;">
<strong>Eigene Events</strong>
@@ -353,11 +356,72 @@ if (!empty($canManageSystemSettings)) {
</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>
<strong>Events mit deiner Teilnahme</strong>
<p class="muted small" style="margin:8px 0 0;">Events anderer Nutzer, bei denen du eingetragen bist.</p>
</div>
<?php if (!$eventsJoinedUpcoming): ?>
<p class="muted small">Aktuell nimmst du an keinen zukünftigen Events teil.</p>
<?php else: ?>
<ul class="dash-list" style="margin-top:10px;">
<?php foreach ($eventsJoinedUpcoming 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>
<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,
!empty($entry['host_name']) ? 'Von ' . (string)$entry['host_name'] : null,
!empty($entry['participation_status']) ? 'Status: ' . (string)$entry['participation_status'] : null,
]))), ENT_QUOTES) ?>
</div>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<details style="margin-top:12px;">
<summary style="cursor:pointer;">Vergangene Teilnahmen anzeigen</summary>
<?php if (!$eventsJoinedPast): ?>
<p class="muted small">Keine vergangenen Event-Teilnahmen.</p>
<?php else: ?>
<ul class="dash-list" style="margin-top:10px;">
<?php foreach ($eventsJoinedPast as $entry): ?>
<li>
<div>
<strong><?= htmlspecialchars((string)$entry['title'], ENT_QUOTES) ?></strong>
<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,
!empty($entry['host_name']) ? 'Von ' . (string)$entry['host_name'] : null,
!empty($entry['participation_status']) ? 'Status: ' . (string)$entry['participation_status'] : null,
]))), ENT_QUOTES) ?>
</div>
</div>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</details>
</div>
</section>
<?php endif; ?>
<?php if ($section === 'places'): ?>
<section class="account-panel" id="places">
<div class="account-panel__head">
<h2>Ausflugsziele</h2>
<p class="muted">Hier pflegst du Orte und sonstige Veranstaltungen als mögliche Ausflugsziele.</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">Ausflugsziel anlegen</button>
</div>
<?php if (!$otherListings): ?>
<p class="muted small">Noch keine Orte oder sonstigen Veranstaltungen angelegt.</p>
<p class="muted small">Noch keine Orte oder Veranstaltungen angelegt.</p>
<?php else: ?>
<ul class="dash-list" style="margin-top:10px;">
<?php foreach ($otherListings as $entry): ?>
@@ -365,7 +429,7 @@ if (!empty($canManageSystemSettings)) {
<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>
<span class="badge"><?= ($entry['listing_type'] ?? '') === 'place' ? 'Ort' : 'Veranstaltung' ?></span>
<?php if (!empty($entry['category_title'])): ?>
<span class="badge"><?= htmlspecialchars((string)$entry['category_title'], ENT_QUOTES) ?></span>
<?php endif; ?>
@@ -373,13 +437,12 @@ if (!empty($canManageSystemSettings)) {
<?= 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?');">
<a class="btn ghost" href="/dashboard?section=places&edit_listing=<?= (int)$entry['id'] ?>#places">Bearbeiten</a>
<form method="post" action="/dashboard?section=places#places" 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>
@@ -934,7 +997,7 @@ if (!empty($canManageSystemSettings)) {
<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" enctype="multipart/form-data">
<form class="stack gap-12" style="margin-top: 10px;" method="post" action="/dashboard?section=<?= htmlspecialchars($modalSectionTarget, ENT_QUOTES) ?>#<?= htmlspecialchars($modalSectionTarget, ENT_QUOTES) ?>" enctype="multipart/form-data">
<input type="hidden" name="action" value="<?= htmlspecialchars($actionEvent, ENT_QUOTES) ?>">
<input type="hidden" name="entry_kind" id="entryKind" value="<?= htmlspecialchars($currentEntryKind, ENT_QUOTES) ?>">
<?php if ($editingEvent && $editEvent): ?>
@@ -950,17 +1013,16 @@ if (!empty($canManageSystemSettings)) {
</option>
<?php endforeach; ?>
</datalist>
<?php if (!$editing): ?>
<?php if (!$editing && $showEventTypeChoice): ?>
<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>
<p class="muted small" id="entryKindHint" style="margin:0;">Ort: dauerhaft verfügbar oder Veranstaltung: zeitlich begrenzter Hinweis.</p>
</div>
<?php else: ?>
<?php elseif ($editing): ?>
<div class="card">
<strong>Eintragstyp</strong>
<p class="muted small" style="margin:8px 0 0;">
@@ -1047,28 +1109,25 @@ if (!empty($canManageSystemSettings)) {
<div id="eventAddressStatus" class="muted small" style="margin:0;"></div>
<input type="hidden" id="evLat" name="lat" value="<?= htmlspecialchars($latValue, ENT_QUOTES) ?>">
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($lngValue, ENT_QUOTES) ?>">
<details class="card" id="mapDetails">
<summary style="cursor:pointer; font-weight:600;">Adresse auf Karte wählen</summary>
<div class="stack gap-6" style="margin-top:12px;">
<div class="card" id="mapDetails">
<div class="stack gap-6">
<div class="flex gap-8" style="flex-wrap:wrap; align-items:center;">
<strong>Karte</strong>
<button class="btn ghost" type="button" id="btnEventMapCurrentLocation">📍 Aktuellen Standort auf Karte zeigen</button>
</div>
<div class="stack gap-6">
<button class="btn ghost" type="button" id="btnMap">Auf Karte suchen</button>
<div id="mapWrapper" class="map-wrapper" hidden>
<div class="form-row">
<label class="label" for="mapSearch">Adresse suchen (optional)</label>
<div class="flex gap-8">
<input id="mapSearch" class="input" placeholder="Straße, PLZ, Ort">
<button class="btn ghost" type="button" id="btnMapSearch">Suchen</button>
</div>
<p class="muted small">Klick auf die Karte setzt den Treffpunkt. Zoom/Scroll möglich.</p>
<div id="mapWrapper" class="map-wrapper">
<div class="form-row">
<label class="label" for="mapSearch">Adresse suchen (optional)</label>
<div class="flex gap-8">
<input id="mapSearch" class="input" placeholder="Straße, PLZ, Ort">
<button class="btn ghost" type="button" id="btnMapSearch">Suchen</button>
</div>
<div id="mapContainer" class="map-container"></div>
<p class="muted small">Klick auf die Karte setzt den Treffpunkt. Änderungen an der Adresse aktualisieren die Karte automatisch.</p>
</div>
<div id="mapContainer" class="map-container"></div>
</div>
</div>
</details>
</div>
<details class="card" id="placeContactWrap">
<summary style="cursor:pointer; font-weight:600;">Kontaktangaben</summary>
<div class="form-grid" style="margin-top:12px;">
@@ -1165,6 +1224,11 @@ if (!empty($canManageSystemSettings)) {
const id = btn.getAttribute('data-modal-open');
const modal = document.getElementById(id);
if (modal) modal.classList.add('open');
if (id === 'modalEvent' && window.PKTConsent?.has?.('external_services')) {
ensureLeaflet(() => {
setTimeout(() => initMap(), 80);
});
}
});
});
document.querySelectorAll('[data-modal-close]').forEach(btn => {
@@ -1180,7 +1244,6 @@ if (!empty($canManageSystemSettings)) {
});
// Map picker for events (Leaflet)
const btnMap = document.getElementById('btnMap');
const mapWrapper = document.getElementById('mapWrapper');
const mapContainer = document.getElementById('mapContainer');
const btnMapSearch = document.getElementById('btnMapSearch');
@@ -1520,13 +1583,6 @@ if (!empty($canManageSystemSettings)) {
}
}
btnMap?.addEventListener('click', () => {
mapWrapper.hidden = false;
ensureLeaflet(() => {
setTimeout(() => initMap(), 50);
});
});
btnMapSearch?.addEventListener('click', () => {
const q = (mapSearch?.value || '').trim();
if (!q) return;

View File

@@ -178,7 +178,7 @@ final class AccountPages
$listingCatalog = $pdo ? new ListingCatalog($pdo) : null;
$section = (string)($_GET['section'] ?? 'profile');
$canManageSystemSettings = $communityAccess ? $communityAccess->canManageApplications($userId) : false;
$allowedSections = ['profile', 'children', 'events', 'community', 'settings'];
$allowedSections = ['profile', 'children', 'events', 'places', 'community', 'settings'];
if ($canManageSystemSettings) {
$allowedSections[] = 'system';
}
@@ -691,6 +691,8 @@ final class AccountPages
$eventsUpcoming = [];
$eventsPast = [];
$eventsJoinedUpcoming = [];
$eventsJoinedPast = [];
$editEvent = null;
$otherListings = [];
$editListing = null;
@@ -717,6 +719,32 @@ final class AccountPages
$stmt?->execute(['id' => $userId]);
$eventsPast = $stmt?->fetchAll(\PDO::FETCH_ASSOC) ?: [];
$stmt = $pdo?->prepare(
'SELECT e.id, e.title, e.teaser_public, e.image_path, e.starts_at, e.city, e.visibility, e.status,
ep.status AS participation_status,
u.display_name AS host_name
FROM event_participants ep
INNER JOIN events e ON e.id = ep.event_id
INNER JOIN users u ON u.id = e.created_by
WHERE ep.user_id = :id AND e.created_by <> :id AND e.starts_at >= NOW()
ORDER BY e.starts_at ASC'
);
$stmt?->execute(['id' => $userId]);
$eventsJoinedUpcoming = $stmt?->fetchAll(\PDO::FETCH_ASSOC) ?: [];
$stmt = $pdo?->prepare(
'SELECT e.id, e.title, e.teaser_public, e.image_path, e.starts_at, e.city, e.visibility, e.status,
ep.status AS participation_status,
u.display_name AS host_name
FROM event_participants ep
INNER JOIN events e ON e.id = ep.event_id
INNER JOIN users u ON u.id = e.created_by
WHERE ep.user_id = :id AND e.created_by <> :id AND e.starts_at < NOW()
ORDER BY e.starts_at DESC'
);
$stmt?->execute(['id' => $userId]);
$eventsJoinedPast = $stmt?->fetchAll(\PDO::FETCH_ASSOC) ?: [];
if (isset($_GET['edit_event'])) {
$editId = (int)$_GET['edit_event'];
$stmt = $pdo?->prepare('SELECT * FROM events WHERE id = :id AND created_by = :uid AND starts_at >= NOW() LIMIT 1');
@@ -759,6 +787,8 @@ final class AccountPages
'editChild',
'eventsUpcoming',
'eventsPast',
'eventsJoinedUpcoming',
'eventsJoinedPast',
'editEvent',
'otherListings',
'editListing',