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

This commit is contained in:
2026-08-11 21:23:58 +02:00
parent 5d979e60bf
commit f14e060f4a
6 changed files with 75 additions and 7 deletions

View File

@@ -35,6 +35,9 @@ 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. - 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`. - 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. - Bei `Ort` oder `Veranstaltung` werden Adresse und Kategorie direkt aus dem gewählten Datenbank-Eintrag übernommen.
- 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.
- Bei `Veranstaltung aus Datenbank` muss das Event-Datum innerhalb des hinterlegten Gültigkeitszeitraums dieser Veranstaltungs-Quelle liegen. - Bei `Veranstaltung aus Datenbank` muss das Event-Datum innerhalb des hinterlegten Gültigkeitszeitraums dieser Veranstaltungs-Quelle liegen.
- Kategorien werden im Mitgliederbereich per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt. - Kategorien werden im Mitgliederbereich per Sucheingabe mit bestehenden Vorschlägen und automatischer Neuanlage gepflegt.
- Eigene Events unterstützen optionalen Beschreibungstext, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl, optionales Bild sowie eine Location aus Datenbank oder benutzerdefinierte Adresse. - Eigene Events unterstützen optionalen Beschreibungstext, Ja/Nein-Angabe `Mit Kindern`, optionale Platzzahl, optionales Bild sowie eine Location aus Datenbank oder benutzerdefinierte Adresse.

View File

@@ -24,6 +24,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- neue Datenbasis für ein späteres Termin-, Ort- und Veranstaltungssystem angelegt - neue Datenbasis für ein späteres Termin-, Ort- und Veranstaltungssystem angelegt
- Mitgliederbereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen fokussiert - Mitgliederbereich `Events` ist wieder auf echte eigene Events und Event-Teilnahmen fokussiert
- Eigene Events haben jetzt optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, optionales Bild sowie eine Location aus Datenbank oder benutzerdefinierte Adresse - Eigene Events haben jetzt optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, optionales Bild sowie eine Location aus Datenbank oder benutzerdefinierte Adresse
- Bei eigener Event-Location aus der Datenbank werden Adresse und Kategorie nur informativ übernommen und nicht direkt im Event geändert
- Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Orte & Veranstaltungen` verschoben - Orte und sonstige Veranstaltungen wurden vorerst in einen separaten Bereich `Orte & Veranstaltungen` verschoben
- Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls nur als begründete Anfrage - Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls nur als begründete Anfrage
- Die Eingabemasken sind jetzt progressiv aufgebaut: erst Pflichtangaben, optionale Daten in einklappbaren Bereichen - Die Eingabemasken sind jetzt progressiv aufgebaut: erst Pflichtangaben, optionale Daten in einklappbaren Bereichen

View File

@@ -35,6 +35,9 @@ 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. - 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`. - 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. - When a database-backed place or event is selected, the address and category are taken directly from that source entry.
- 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.
- When `event from database` is selected, the event date must fall inside that source event's valid date window. - When `event from database` is selected, the event date must fall inside that source event's valid date window.
- Categories in the member area are handled via a search input with existing suggestions and automatic creation when needed. - Categories in the member area are handled via a search input with existing suggestions and automatic creation when needed.
- Own events support an optional description, a clear `with children` yes/no field, optional capacity, an optional image, and either a database-backed location or a custom address. - Own events support an optional description, a clear `with children` yes/no field, optional capacity, an optional image, and either a database-backed location or a custom address.

View File

@@ -24,6 +24,7 @@ Papa-Kind-Treff is a PHP-based platform for fathers focused on local events, app
- new data foundation prepared for a broader event, place, and listing system - new data foundation prepared for a broader event, place, and listing system
- member area `Events` is now limited again to real own events and event participations - member area `Events` is now limited again to real own events and event participations
- own events now support an optional description, explicit child suitability, optional capacity, an optional image, and either a database-backed location or a custom address - own events now support an optional description, explicit child suitability, optional capacity, an optional image, and either a database-backed location or a custom address
- when an own event uses a database-backed location, address and category are shown as informational values and are not edited directly inside the event
- places and other event-style entries were moved for now into a separate member-area section `Places & Events` - places and other event-style entries were moved for now into a separate member-area section `Places & Events`
- new places and event-style entries must be approved by an admin before publication; change and deletion wishes also run as reasoned requests - new places and event-style entries must be approved by an admin before publication; change and deletion wishes also run as reasoned requests
- the member-area entry forms now follow a progressive approach: required fields first, optional data inside collapsible sections - the member-area entry forms now follow a progressive approach: required fields first, optional data inside collapsible sections

View File

@@ -43,6 +43,8 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf:
- Orte und sonstige Veranstaltungen laufen jetzt separat unter `Orte & Veranstaltungen` - Orte und sonstige Veranstaltungen laufen jetzt separat unter `Orte & Veranstaltungen`
- Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls als begründete Anfrage - Neue Orte und Veranstaltungen müssen vor Veröffentlichung erst durch einen Admin freigegeben werden; Änderungs- und Löschwünsche laufen ebenfalls als begründete Anfrage
- Eigene Events unterstützen aktuell optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, Bild-Upload sowie eine Location aus Datenbank oder benutzerdefinierte Adresse - Eigene Events unterstützen aktuell optionalen Beschreibungstext, klare Kinderangabe, optionale Platzzahl, Bild-Upload sowie eine Location aus Datenbank oder benutzerdefinierte Adresse
- Bei eigener Event-Location aus der Datenbank werden Adresse und Kategorie nur informativ aus dem gewählten Ort bzw. der gewählten Veranstaltung übernommen und nicht manuell geändert
- Aus dem Formular für eigene Events kann direkt in das Anlegen eines neuen Orts oder einer neuen Veranstaltung verzweigt werden
- Die Eingabe im Mitgliederbereich ist jetzt bewusst vereinfacht: zuerst nur Pflichtangaben, optionale Angaben in einklappbaren Bereichen - 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 - 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
- Im Mitgliederbereich `Events` gibt es jetzt zusätzlich einen ICS-Download und einen persönlichen abonnierbaren Kalender-Feed für alle eigenen Events und Event-Teilnahmen - Im Mitgliederbereich `Events` gibt es jetzt zusätzlich einen ICS-Download und einen persönlichen abonnierbaren Kalender-Feed für alle eigenen Events und Event-Teilnahmen

View File

@@ -6,10 +6,15 @@ $avatarPlaceholder = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/
$avatarStyleKeys = array_keys($avatarBuilder ?? []); $avatarStyleKeys = array_keys($avatarBuilder ?? []);
$currentAvatarStyle = (string)($profile['avatar_style'] ?? ($avatarStyleKeys[0] ?? 'lorelei')); $currentAvatarStyle = (string)($profile['avatar_style'] ?? ($avatarStyleKeys[0] ?? 'lorelei'));
$dashboardUserId = (int)($_SESSION['user_id'] ?? 0); $dashboardUserId = (int)($_SESSION['user_id'] ?? 0);
$requestedCreateListingKind = isset($_GET['create_listing']) && in_array((string)$_GET['create_listing'], ['place', 'editorial_event'], true)
? (string)$_GET['create_listing']
: null;
$editingEvent = isset($editEvent) && $editEvent !== null; $editingEvent = isset($editEvent) && $editEvent !== null;
$editingListing = isset($editListing) && $editListing !== null; $editingListing = isset($editListing) && $editListing !== null;
$editing = $editingEvent || $editingListing; $editing = $editingEvent || $editingListing;
$currentEntryKind = $editingListing ? (string)($editListing['listing_type'] ?? 'editorial_event') : (($section ?? 'events') === 'places' ? 'place' : 'event'); $currentEntryKind = $editingListing
? (string)($editListing['listing_type'] ?? 'editorial_event')
: ((($section ?? 'events') === 'places') ? ($requestedCreateListingKind ?? 'place') : 'event');
$actionEvent = $editing ? 'event_update' : 'event_add'; $actionEvent = $editing ? 'event_update' : 'event_add';
$startVal = ''; $startVal = '';
if ($editingEvent && !empty($editEvent['starts_at'])) { if ($editingEvent && !empty($editEvent['starts_at'])) {
@@ -116,7 +121,7 @@ $sectionLinks = [
'community' => 'Community', 'community' => 'Community',
'settings' => 'Einstellungen', 'settings' => 'Einstellungen',
]; ];
$showEventTypeChoice = !$editing && (($section ?? 'events') === 'places'); $showEventTypeChoice = !$editing && (($section ?? 'events') === 'places') && $requestedCreateListingKind === null;
$modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events'; $modalSectionTarget = ($editingListing || ($section ?? '') === 'places') ? 'places' : 'events';
if (!empty($canManageSystemSettings)) { if (!empty($canManageSystemSettings)) {
$sectionLinks['system'] = 'System'; $sectionLinks['system'] = 'System';
@@ -1189,7 +1194,13 @@ if (!empty($canManageSystemSettings)) {
<label class="label" for="eventLocationSourceInput" id="eventLocationSourceLabel">Ort aus Datenbank</label> <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) ?>"> <input id="eventLocationSourceInput" class="input" list="eventLocationOptionList" placeholder="Eintrag aus der Datenbank auswählen" value="<?= htmlspecialchars($eventLocationSourceInputValue, ENT_QUOTES) ?>">
</div> </div>
<div class="stack gap-6"> <?php if (($section ?? 'events') === 'events' && !$editingListing): ?>
<div class="flex gap-8" style="flex-wrap:wrap;" id="eventCreateListingActionsWrap">
<a class="btn ghost" href="/dashboard?section=places&create_listing=place#places">Neuen Ort anlegen</a>
<a class="btn ghost" href="/dashboard?section=places&create_listing=editorial_event#places">Neue Veranstaltung anlegen</a>
</div>
<?php endif; ?>
<div class="stack gap-6" id="categoryInputWrap">
<label class="label" for="categoryInput">Kategorie</label> <label class="label" for="categoryInput">Kategorie</label>
<input id="categoryInput" name="category_input" class="input" list="listingCategoryList" placeholder="z. B. Spielplatz, Café, Zirkus" value="<?= htmlspecialchars($categoryInputValue, ENT_QUOTES) ?>"> <input id="categoryInput" name="category_input" class="input" list="listingCategoryList" placeholder="z. B. Spielplatz, Café, Zirkus" value="<?= htmlspecialchars($categoryInputValue, ENT_QUOTES) ?>">
<input type="hidden" id="listingCategorySlug" name="category_slug" value="<?= htmlspecialchars($categorySlugValue, ENT_QUOTES) ?>"> <input type="hidden" id="listingCategorySlug" name="category_slug" value="<?= htmlspecialchars($categorySlugValue, ENT_QUOTES) ?>">
@@ -1200,6 +1211,10 @@ if (!empty($canManageSystemSettings)) {
</datalist> </datalist>
<div id="categoryInputStatus" class="muted small" style="margin:0;"></div> <div id="categoryInputStatus" class="muted small" style="margin:0;"></div>
</div> </div>
<div class="card" id="eventCategoryInfoWrap" hidden>
<strong>Kategorie</strong>
<div class="muted small" id="eventCategoryInfoText" style="margin-top:8px;">Keine Kategorie ausgewählt.</div>
</div>
<div class="form-grid" id="eventDateRow"> <div class="form-grid" id="eventDateRow">
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="evDate" id="eventDateLabel">Datum</label> <label class="label" for="evDate" id="eventDateLabel">Datum</label>
@@ -1218,12 +1233,12 @@ if (!empty($canManageSystemSettings)) {
<label class="label" for="maxParticipants">Platzzahl (optional)</label> <label class="label" for="maxParticipants">Platzzahl (optional)</label>
<input id="maxParticipants" name="max_participants" class="input" type="number" min="1" value="<?= htmlspecialchars($capacityValue, ENT_QUOTES) ?>" placeholder="leer = offen"> <input id="maxParticipants" name="max_participants" class="input" type="number" min="1" value="<?= htmlspecialchars($capacityValue, ENT_QUOTES) ?>" placeholder="leer = offen">
</div> </div>
<div class="stack gap-6" id="eventValidUntilWrap"> <div class="stack gap-6" id="eventValidUntilWrap"<?= $currentEntryKind === 'event' ? ' hidden' : '' ?>>
<label class="label" for="recurrenceUntil">Gültig bis</label> <label class="label" for="recurrenceUntil">Gültig bis</label>
<input id="recurrenceUntil" name="recurrence_until" class="input" type="date" value="<?= htmlspecialchars($recurrenceUntilValue, ENT_QUOTES) ?>"> <input id="recurrenceUntil" name="recurrence_until" class="input" type="date" value="<?= htmlspecialchars($recurrenceUntilValue, ENT_QUOTES) ?>">
</div> </div>
</div> </div>
<div class="stack gap-6" id="eventRecurrenceWrap"> <div class="stack gap-6" id="eventRecurrenceWrap"<?= $currentEntryKind === 'event' ? ' hidden' : '' ?>>
<label class="label" for="recurrenceMode">Wiederholung</label> <label class="label" for="recurrenceMode">Wiederholung</label>
<select id="recurrenceMode" name="recurrence_mode" class="select"> <select id="recurrenceMode" name="recurrence_mode" class="select">
<option value="single" <?= $recurrenceModeValue === 'single' ? 'selected' : '' ?>>Einmalig</option> <option value="single" <?= $recurrenceModeValue === 'single' ? 'selected' : '' ?>>Einmalig</option>
@@ -1264,6 +1279,10 @@ if (!empty($canManageSystemSettings)) {
<button class="btn ghost" type="button" id="btnEventAddressSearch">Adresse suchen</button> <button class="btn ghost" type="button" id="btnEventAddressSearch">Adresse suchen</button>
<button class="btn ghost" type="button" id="btnEventBrowserLocation">📍 Aktuellen Standort</button> <button class="btn ghost" type="button" id="btnEventBrowserLocation">📍 Aktuellen Standort</button>
</div> </div>
<div class="card" id="eventAddressInfoWrap" hidden>
<strong>Adresse</strong>
<div class="muted small" id="eventAddressInfoText" style="margin-top:8px;">Kein Ort ausgewählt.</div>
</div>
<div id="eventAddressStatus" class="muted small" style="margin:0;"></div> <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="evLat" name="lat" value="<?= htmlspecialchars($latValue, ENT_QUOTES) ?>">
<input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($lngValue, ENT_QUOTES) ?>"> <input type="hidden" id="evLng" name="lng" value="<?= htmlspecialchars($lngValue, ENT_QUOTES) ?>">
@@ -1426,9 +1445,14 @@ if (!empty($canManageSystemSettings)) {
const eventLocationSourceInput = document.getElementById('eventLocationSourceInput'); const eventLocationSourceInput = document.getElementById('eventLocationSourceInput');
const eventLocationSourceIdInput = document.getElementById('eventLocationSourceId'); const eventLocationSourceIdInput = document.getElementById('eventLocationSourceId');
const eventLocationSourceLabel = document.getElementById('eventLocationSourceLabel'); const eventLocationSourceLabel = document.getElementById('eventLocationSourceLabel');
const categoryInputWrap = document.getElementById('categoryInputWrap');
const eventCategoryInfoWrap = document.getElementById('eventCategoryInfoWrap');
const eventCategoryInfoText = document.getElementById('eventCategoryInfoText');
const eventAddressFieldsWrap = document.getElementById('eventAddressFieldsWrap'); const eventAddressFieldsWrap = document.getElementById('eventAddressFieldsWrap');
const eventAddressStreetWrap = document.getElementById('eventAddressStreetWrap'); const eventAddressStreetWrap = document.getElementById('eventAddressStreetWrap');
const eventAddressActionsWrap = document.getElementById('eventAddressActionsWrap'); const eventAddressActionsWrap = document.getElementById('eventAddressActionsWrap');
const eventAddressInfoWrap = document.getElementById('eventAddressInfoWrap');
const eventAddressInfoText = document.getElementById('eventAddressInfoText');
const eventValidUntilWrap = document.getElementById('eventValidUntilWrap'); const eventValidUntilWrap = document.getElementById('eventValidUntilWrap');
const eventPricingWrap = document.getElementById('eventPricingWrap'); const eventPricingWrap = document.getElementById('eventPricingWrap');
const visibilityDetails = document.getElementById('visibilityDetails'); const visibilityDetails = document.getElementById('visibilityDetails');
@@ -1522,10 +1546,43 @@ if (!empty($canManageSystemSettings)) {
if (eventAddressStatus) eventAddressStatus.textContent = ''; if (eventAddressStatus) eventAddressStatus.textContent = '';
} }
function syncEventLocationInfo() {
const usesDatabaseLocation = (entryKindInput?.value || 'event') === 'event' && (eventLocationModeSelect?.value || 'custom') !== 'custom';
const addressLabel = [streetInput?.value || '', `${zipInput?.value || ''} ${cityInput?.value || ''}`.trim(), regionInput?.value || '']
.filter(Boolean)
.join(', ');
setSectionState(categoryInputWrap, !usesDatabaseLocation);
if (eventCategoryInfoWrap) {
eventCategoryInfoWrap.hidden = !usesDatabaseLocation;
}
if (eventCategoryInfoText) {
eventCategoryInfoText.textContent = (categoryInput?.value || '').trim() || 'Keine Kategorie ausgewählt.';
}
if (eventAddressInfoWrap) {
eventAddressInfoWrap.hidden = !usesDatabaseLocation;
}
if (eventAddressInfoText) {
eventAddressInfoText.textContent = addressLabel || 'Kein Ort ausgewählt.';
}
if (eventAddressStatus) {
eventAddressStatus.hidden = usesDatabaseLocation;
}
}
function clearEventLocationSelection() { function clearEventLocationSelection() {
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = ''; if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = '';
if (eventLocationSourceInput) eventLocationSourceInput.value = ''; if (eventLocationSourceInput) eventLocationSourceInput.value = '';
if (streetInput) streetInput.value = '';
if (zipInput) zipInput.value = '';
if (cityInput) cityInput.value = '';
if (regionInput) regionInput.value = '';
if (latInput) latInput.value = '';
if (lngInput) lngInput.value = '';
if (categoryInput) categoryInput.value = '';
if (categorySlugInput) categorySlugInput.value = '';
if (categoryInputStatus) categoryInputStatus.textContent = ''; if (categoryInputStatus) categoryInputStatus.textContent = '';
syncEventLocationInfo();
} }
function syncEventLocationMode() { function syncEventLocationMode() {
@@ -1546,7 +1603,6 @@ if (!empty($canManageSystemSettings)) {
} }
if (categoryInput) { if (categoryInput) {
categoryInput.readOnly = usesDatabaseLocation;
if (usesDatabaseLocation) { if (usesDatabaseLocation) {
categoryInput.removeAttribute('list'); categoryInput.removeAttribute('list');
} else { } else {
@@ -1564,6 +1620,7 @@ if (!empty($canManageSystemSettings)) {
} else if (isOwnEvent) { } else if (isOwnEvent) {
if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = ''; if (eventLocationSourceIdInput) eventLocationSourceIdInput.value = '';
} }
syncEventLocationInfo();
} }
function syncEventEntryKind(nextKind) { function syncEventEntryKind(nextKind) {
@@ -1621,6 +1678,7 @@ if (!empty($canManageSystemSettings)) {
return; return;
} }
applyEventLocationOption(option); applyEventLocationOption(option);
syncEventLocationInfo();
}); });
syncEventEntryKind(entryKindInput?.value || 'event'); syncEventEntryKind(entryKindInput?.value || 'event');
@@ -2115,7 +2173,7 @@ if (!empty($canManageSystemSettings)) {
setNeedsValidation(false); setNeedsValidation(false);
})(); })();
<?php if ($editing): ?> <?php if ($editing || $requestedCreateListingKind !== null): ?>
(function(){ (function(){
const modal = document.getElementById('modalEvent'); const modal = document.getElementById('modalEvent');
if (modal) { if (modal) {