This commit is contained in:
2025-12-29 01:21:25 +01:00
parent 196950db09
commit ba257dab28
3 changed files with 79 additions and 79 deletions

View File

@@ -114,7 +114,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'lat' => $lat, 'lat' => $lat,
'lng' => $lng, 'lng' => $lng,
'start' => $_POST['starts_at'] ?? null, 'start' => $_POST['starts_at'] ?? null,
'allow' => isset($_POST['allow_kids']) ? 1 : 0, 'allow' => isset($_POST['allow_kids']) ? 0 : 1, // checkbox = Treffen ohne Kinder
'vis' => $_POST['visibility'] ?? 'public', 'vis' => $_POST['visibility'] ?? 'public',
'status' => 'published', 'status' => 'published',
]); ]);
@@ -334,9 +334,6 @@ $eventsPast = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
</div> </div>
</div> </div>
</div> </div>
<div class="flex gap-8" style="flex-wrap: wrap;">
<a class="btn ghost" href="/dashboard?edit_event=<?= (int)$e['id'] ?>#events">Bearbeiten</a>
</div>
</li> </li>
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>

View File

@@ -1,6 +1,38 @@
<?php <?php
$app = app(); $app = app();
$flash = $app->flash()->get(); $flash = $app->flash()->get();
$eventsForJs = [];
try {
$pdo = $app->pdo();
if ($pdo) {
$stmt = $pdo->prepare('SELECT id, title, teaser_public, description, city, region, zip, starts_at, allow_kids, visibility, location_label, lat, lng FROM events WHERE starts_at >= NOW() AND status != "cancelled" ORDER BY starts_at ASC LIMIT 50');
$stmt->execute();
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
foreach ($rows as $r) {
$eventsForJs[] = [
'id' => (int)$r['id'],
'title' => $r['title'],
'teaser' => $r['teaser_public'],
'description' => $r['description'],
'city' => $r['city'],
'zip' => $r['zip'],
'region' => $r['region'],
'topic' => 'general',
'startsAt' => $r['starts_at'],
'allowKids' => ((int)$r['allow_kids'] === 1),
'ageGroup' => '',
'visibility' => $r['visibility'],
'contact' => '',
'locationLabel' => $r['location_label'],
'lat' => $r['lat'] !== null ? (float)$r['lat'] : null,
'lng' => $r['lng'] !== null ? (float)$r['lng'] : null,
];
}
}
} catch (Throwable $e) {
$eventsForJs = [];
}
?> ?>
<main> <main>
<?php if ($flash): ?> <?php if ($flash): ?>
@@ -63,7 +95,7 @@ $flash = $app->flash()->get();
<div class="section__head"> <div class="section__head">
<div> <div>
<p class="eyebrow">Termine entdecken</p> <p class="eyebrow">Termine entdecken</p>
<h2>Upcoming Events in deiner Nähe</h2> <h2>Die nächsten anstehenden Events</h2>
<p class="muted">Gäste sehen nur Basisinfos. Als Mitglied siehst du vollständige Details, kannst zusagen und neue Treffen anlegen.</p> <p class="muted">Gäste sehen nur Basisinfos. Als Mitglied siehst du vollständige Details, kannst zusagen und neue Treffen anlegen.</p>
</div> </div>
<div class="chips"> <div class="chips">
@@ -187,3 +219,6 @@ $flash = $app->flash()->get();
</div> </div>
</section> </section>
</main> </main>
<script>
window.__events = <?= json_encode($eventsForJs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
</script>

View File

@@ -49,75 +49,10 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
} }
// Demo-Events // Events from backend (injected on page); fallback to empty
const events = [ const events = Array.isArray(window.__events) ? window.__events : [];
{
id: 1,
title: 'Spielplatzrunde im Park',
teaser: 'Lockeres Treffen mit Kaffee, Sandspielzeug und Picknick.',
description: 'Wir treffen uns am großen Spielplatz im Kiez. Bringt Snacks mit, wir teilen. Für alle Altersstufen offen.',
city: 'Berlin',
zip: '10437',
region: 'Prenzlauer Berg',
topic: 'outdoor',
startsAt: '2025-08-10T10:00:00',
allowKids: true,
ageGroup: 'kids',
visibility: 'public',
contact: 'papa-berlin@example.com',
locationLabel: 'Mauerpark',
},
{
id: 2,
title: 'Väter-Kaffee & Austausch',
teaser: 'Elternzeit, Job, Schlaf wir reden über alles.',
description: 'Reservierter Tisch im Café. Fokus auf Austausch unter Vätern, Kinder optional.',
city: 'Hamburg',
zip: '22767',
region: 'Altona',
topic: 'kaffee',
startsAt: '2025-08-12T19:00:00',
allowKids: false,
ageGroup: '',
visibility: 'members',
contact: 'altona-dads@example.com',
locationLabel: 'Café Elbseite',
},
{
id: 3,
title: 'Sport & Spiel im Park',
teaser: 'Ballspiele und leichtes Workout, Kinder toben mit.',
description: 'Wir bringen Bälle und Slackline. Warm-up, danach freies Spiel. Bitte Wasser mitbringen.',
city: 'München',
zip: '80804',
region: 'Schwabing',
topic: 'sport',
startsAt: '2025-08-15T11:00:00',
allowKids: true,
ageGroup: 'school',
visibility: 'public',
contact: 'schwabing-sport@example.com',
locationLabel: 'Luitpoldpark',
},
{
id: 4,
title: 'Workshop: Erste Hilfe für Kids',
teaser: 'Erste Hilfe Basics speziell für Kinder-Unfälle.',
description: 'Zertifizierter Trainer, kleiner Materialbeitrag. Kinder können mitgebracht werden.',
city: 'Köln',
zip: '50667',
region: 'Innenstadt',
topic: 'workshop',
startsAt: '2025-08-20T18:30:00',
allowKids: true,
ageGroup: 'kids',
visibility: 'members',
contact: 'koeln-workshop@example.com',
locationLabel: 'Familienzentrum Mitte',
},
];
const state = { topic: 'all', age: '', region: '', query: '' }; const state = { topic: 'all', age: '', region: '', query: '', geo: null };
const el = { const el = {
list: document.getElementById('eventList'), list: document.getElementById('eventList'),
chips: document.querySelectorAll('[data-filter]'), chips: document.querySelectorAll('[data-filter]'),
@@ -133,6 +68,16 @@ document.addEventListener('DOMContentLoaded', () => {
return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }); return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
}; };
const haversine = (lat1, lon1, lat2, lon2) => {
const toRad = (d) => d * Math.PI / 180;
const R = 6371e3;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat/2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon/2) ** 2;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
};
const tag = (label) => `<span class="badge">${label}</span>`; const tag = (label) => `<span class="badge">${label}</span>`;
const renderCard = (item) => { const renderCard = (item) => {
@@ -170,16 +115,39 @@ document.addEventListener('DOMContentLoaded', () => {
}; };
const matchesFilter = (ev) => { const matchesFilter = (ev) => {
const topicOk = state.topic === 'all' || state.topic === ev.topic; const topicOk = state.topic === 'all' || state.topic === ev.topic || state.topic === '';
const ageOk = !state.age || ev.ageGroup === state.age || (state.age === 'baby' && ev.ageGroup === 'kids'); // simple fallback const ageOk = !state.age || ev.ageGroup === state.age || (state.age === 'baby' && ev.ageGroup === 'kids'); // simple fallback
const regionOk = !state.region || ev.region.toLowerCase().includes(state.region) || ev.city.toLowerCase().includes(state.region) || ev.zip.startsWith(state.region); const regionField = (ev.region || '').toLowerCase();
const queryOk = !state.query || (ev.title + ev.teaser + ev.description + ev.city + ev.region).toLowerCase().includes(state.query); const cityField = (ev.city || '').toLowerCase();
const zipField = (ev.zip || '');
const queryHaystack = (ev.title + ev.teaser + ev.description + (ev.city || '') + (ev.region || '')).toLowerCase();
const regionOk = !state.region || regionField.includes(state.region) || cityField.includes(state.region) || zipField.startsWith(state.region);
const queryOk = !state.query || queryHaystack.includes(state.query);
return topicOk && ageOk && regionOk && queryOk; return topicOk && ageOk && regionOk && queryOk;
}; };
const renderEvents = () => { const renderEvents = () => {
if (!el.list) return; if (!el.list) return;
const filtered = events.filter(matchesFilter); let filtered = events.filter(matchesFilter);
filtered.sort((a,b) => new Date(a.startsAt) - new Date(b.startsAt));
if (state.geo) {
const withCoords = filtered.filter(ev => ev.lat !== null && ev.lng !== null);
withCoords.forEach(ev => { ev._distance = haversine(state.geo.lat, state.geo.lng, ev.lat, ev.lng); });
withCoords.sort((a,b) => (a._distance || Infinity) - (b._distance || Infinity));
const near = withCoords.filter(ev => ev._distance <= 5000).slice(0,5);
const fallback = withCoords.slice(0,5);
filtered = (near.length ? near : fallback).map(ev => {
const copy = { ...ev };
if (ev._distance) {
copy.teaser = `${ev.teaser} · ca. ${(ev._distance/1000).toFixed(1)} km entfernt`;
}
return copy;
});
} else {
filtered = filtered.slice(0,5);
}
el.list.innerHTML = filtered.map(renderCard).join('') || '<p class="muted">Keine Events gefunden.</p>'; el.list.innerHTML = filtered.map(renderCard).join('') || '<p class="muted">Keine Events gefunden.</p>';
}; };
@@ -212,8 +180,8 @@ document.addEventListener('DOMContentLoaded', () => {
return; return;
} }
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
() => { (pos) => {
state.region = 'nähe'; state.geo = { lat: pos.coords.latitude, lng: pos.coords.longitude };
renderEvents(); renderEvents();
}, },
() => alert('Standort konnte nicht ermittelt werden.') () => alert('Standort konnte nicht ermittelt werden.')