UX: Nearby-POIs einklappbar + klickbar + nur relevante Typen

- NEARBY_TYPES: Bänke/Wasserstellen entfernt → nur Restaurant, Tierarzt, Zoobedarf
- Gruppen einklappbar (Chevron dreht sich)
- POI-Namen anklickbar → öffnet Apple Maps/Google Maps am POI-Standort
- CSS: Card-Stil für Gruppen, gute Touch-Targets
This commit is contained in:
rene 2026-04-19 11:29:46 +02:00
parent 9a56978f81
commit 3144e100f3
3 changed files with 95 additions and 29 deletions

View file

@ -2339,24 +2339,50 @@ html.modal-open {
} }
.rk-nearby-group { .rk-nearby-group {
margin-bottom: var(--space-3); margin-bottom: var(--space-3);
border: 1px solid var(--c-border-light);
border-radius: var(--radius-md);
overflow: hidden;
} }
.rk-nearby-group-label { .rk-nearby-group-header {
font-size: var(--text-sm); display: flex;
font-weight: var(--weight-medium); align-items: center;
color: var(--c-text-secondary); justify-content: space-between;
margin-bottom: var(--space-1); width: 100%;
padding: var(--space-2) var(--space-3);
background: var(--c-surface-2);
border: none;
cursor: pointer;
font-size: var(--text-sm);
font-weight: var(--weight-semibold);
color: var(--c-text);
text-align: left;
-webkit-tap-highlight-color: transparent;
} }
.rk-nearby-group-header:hover { background: var(--c-border-light); }
.rk-nearby-items { padding: var(--space-1) 0; }
.rk-nearby-item { .rk-nearby-item {
display: flex; display: flex;
flex-wrap: wrap; align-items: center;
justify-content: space-between;
gap: var(--space-2); gap: var(--space-2);
align-items: baseline; padding: var(--space-2) var(--space-3);
padding: var(--space-1) 0;
border-bottom: 1px solid var(--c-border-light); border-bottom: 1px solid var(--c-border-light);
width: 100%;
background: none;
border-left: none;
border-right: none;
border-top: none;
text-align: left;
font: inherit;
} }
.rk-nearby-item:last-child { border-bottom: none; } .rk-nearby-item:last-child { border-bottom: none; }
.rk-nearby-name { font-size: var(--text-sm); color: var(--c-text); } .rk-nearby-item--link {
.rk-nearby-detail { font-size: var(--text-xs); color: var(--c-text-muted); } cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.rk-nearby-item--link:hover { background: var(--c-surface-2); }
.rk-nearby-name { font-size: var(--text-sm); color: var(--c-text); display: block; }
.rk-nearby-detail { font-size: var(--text-xs); color: var(--c-text-muted); display: block; margin-top: 1px; }
.rk-nearby-phone { color: var(--c-primary); text-decoration: none; } .rk-nearby-phone { color: var(--c-primary); text-decoration: none; }
/* Hundetauglichkeit-Auswahl im Formular */ /* Hundetauglichkeit-Auswahl im Formular */

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '226'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '227'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => { const App = (() => {

View file

@ -34,12 +34,11 @@ window.Page_routes = (() => {
const TERRAIN_LABEL = { wald: '🌲 Wald', asphalt: '🛣️ Asphalt', wiese: '🌿 Wiese', mix: '🔀 Mix' }; const TERRAIN_LABEL = { wald: '🌲 Wald', asphalt: '🛣️ Asphalt', wiese: '🌿 Wiese', mix: '🔀 Mix' };
const HUNDE_LABEL = { eingeschränkt: '🐾', gut: '🐾🐾', sehr_gut: '🐾🐾🐾', premium: '🐾🐾🐾🐾' }; const HUNDE_LABEL = { eingeschränkt: '🐾', gut: '🐾🐾', sehr_gut: '🐾🐾🐾', premium: '🐾🐾🐾🐾' };
// POI-Typen die entlang einer Route gezeigt werden // POI-Typen entlang der Route — nur relevante/interessante Orte
const NEARBY_TYPES = [ const NEARBY_TYPES = [
{ type: 'restaurant', icon: '🍽️', label: 'Restaurant/Café' }, { type: 'restaurant', icon: '🍽️', label: 'Restaurant/Café' },
{ type: 'parkplatz', icon: '🅿️', label: 'Parkplatz' }, { type: 'tierarzt', icon: '🏥', label: 'Tierarzt' },
{ type: 'drinking_water', icon: '💧', label: 'Wasserstelle' }, { type: 'shop', icon: '🐾', label: 'Zoobedarf' },
{ type: 'bank', icon: '🪑', label: 'Bank' },
]; ];
// _esc und _emptyState ersetzt durch UI.escape() / UI.emptyState() // _esc und _emptyState ersetzt durch UI.escape() / UI.emptyState()
@ -936,7 +935,6 @@ window.Page_routes = (() => {
if (!el) return; if (!el) return;
if (!pois.length) { el.innerHTML = ''; return; } if (!pois.length) { el.innerHTML = ''; return; }
// Gruppieren nach Typ
const byType = {}; const byType = {};
pois.forEach(p => { pois.forEach(p => {
const key = p._label; const key = p._label;
@ -944,22 +942,64 @@ window.Page_routes = (() => {
byType[key].items.push(p); byType[key].items.push(p);
}); });
let gIdx = 0;
el.innerHTML = ` el.innerHTML = `
<div class="rk-nearby-title">${UI.icon('map-pin')} Entlang der Route</div> <div class="rk-nearby-title">${UI.icon('map-pin')} Entlang der Route</div>
${Object.values(byType).map(group => ` ${Object.values(byType).map(group => {
<div class="rk-nearby-group"> const id = `rk-ng-${gIdx++}`;
<div class="rk-nearby-group-label">${group.icon} ${UI.escape(group.label)} (${group.items.length})</div> return `
${group.items.slice(0, 5).map(p => ` <div class="rk-nearby-group">
<div class="rk-nearby-item"> <button class="rk-nearby-group-header" data-target="${id}" type="button">
<span class="rk-nearby-name">${UI.escape(p.name || group.label)}</span> <span>${group.icon} ${UI.escape(group.label)} (${group.items.length})</span>
${p.opening_hours ? `<span class="rk-nearby-detail">${UI.icon('clock')} ${UI.escape(p.opening_hours)}</span>` : ''} <svg class="ph-icon rk-nearby-chevron" aria-hidden="true" style="transition:transform 0.2s">
${p.phone ? `<a href="tel:${UI.escape(p.phone)}" class="rk-nearby-detail rk-nearby-phone">${UI.icon('phone')} ${UI.escape(p.phone)}</a>` : ''} <use href="/icons/phosphor.svg#caret-down"></use>
</svg>
</button>
<div id="${id}" class="rk-nearby-items">
${group.items.map(p => `
<button class="rk-nearby-item rk-nearby-item--link" type="button"
data-lat="${p.lat}" data-lon="${p.lon}"
data-name="${UI.escape(p.name || group.label)}">
<div>
<span class="rk-nearby-name">${UI.escape(p.name || group.label)}</span>
${p.opening_hours ? `<span class="rk-nearby-detail">${UI.icon('clock')} ${UI.escape(p.opening_hours)}</span>` : ''}
${p.phone ? `<span class="rk-nearby-detail">${UI.icon('phone')} ${UI.escape(p.phone)}</span>` : ''}
</div>
<svg class="ph-icon" style="width:14px;height:14px;color:var(--c-text-muted);flex-shrink:0" aria-hidden="true">
<use href="/icons/phosphor.svg#map-pin"></use>
</svg>
</button>
`).join('')}
</div> </div>
`).join('')} </div>`;
${group.items.length > 5 ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);padding:2px 0">+${group.items.length-5} weitere</div>` : ''} }).join('')}
</div>
`).join('')}
`; `;
// Einklapp-Logik
el.querySelectorAll('.rk-nearby-group-header').forEach(btn => {
const target = document.getElementById(btn.dataset.target);
const chevron = btn.querySelector('.rk-nearby-chevron');
let open = true;
btn.addEventListener('click', () => {
open = !open;
target.style.display = open ? '' : 'none';
chevron.style.transform = open ? '' : 'rotate(-90deg)';
});
});
// POI auf Karte zeigen
el.querySelectorAll('.rk-nearby-item--link').forEach(btn => {
btn.addEventListener('click', () => {
const lat = parseFloat(btn.dataset.lat);
const lon = parseFloat(btn.dataset.lon);
const name = btn.dataset.name;
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const url = isIOS
? `maps://maps.apple.com/?q=${encodeURIComponent(name)}&ll=${lat},${lon}`
: `https://www.google.com/maps/search/?api=1&query=${lat},${lon}`;
window.open(url, '_blank');
});
});
} }
// ---------------------------------------------------------- // ----------------------------------------------------------