Phase 4: Filter- und Suchfunktion
Neues Modul js/filters/ mit reiner Filterlogik (filterEngine.js: applyFilters(), getDistanceKm()) und UI (filterPanel.js) fuer alle Filter aus anforderung.md Abschnitt 3: Kategorie, Entfernung (mit "Standort verwenden"-Button), Alter der Kinder, Kosten, Indoor/Outdoor, 5 Ausstattungsmerkmale, Aufenthaltsdauer, Mindestbewertung sowie Freitextsuche ueber Name/Ort/Region/Adresse. Trefferzaehler, "Filter zuruecksetzen" und "Auf Ergebnisse zoomen" ergaenzt. Store erweitert um updateFilters()/getFilteredPlaces() (Selector). Karte und Liste rendern jetzt die gefilterte statt die volle Ausflugsziel-Liste; die Karten-Rerender-Optimierung vergleicht dafuer die Trefferliste per Id-Schluessel statt nur state.places, damit reine Filter-/Standortaenderungen ohne Places-Aenderung erkannt werden. Layout: Filter-Panel als aufklappbares <details> auf Mobile, eigener Grid-Bereich neben der Karte auf Desktop. docs/plan.md entsprechend fortgeschrieben (Phase 2-5 als umgesetzt markiert, Phase 5 Navigation war bereits Teil von Phase 2).
This commit is contained in:
+40
-24
@@ -1,18 +1,39 @@
|
||||
// Ergebnisliste (Cards), rendert aus store.state.places. Filterung folgt
|
||||
// in einer späteren Phase - aktuell werden alle Places angezeigt.
|
||||
// Ergebnisliste (Cards), rendert die gefilterte Trefferliste
|
||||
// (EventMap.store.getFilteredPlaces()) statt state.places direkt, damit
|
||||
// Kategorie-/Entfernungs-/etc.-Filter aus js/filters/ berücksichtigt werden.
|
||||
// Rendering ausschließlich über textContent/DOM-APIs (el()), niemals
|
||||
// innerHTML mit Place-Daten.
|
||||
|
||||
(function () {
|
||||
const { subscribe, getState, setState } = EventMap.store;
|
||||
const { subscribe, getState, setState, getFilteredPlaces } = EventMap.store;
|
||||
const { getCategoryById } = EventMap.config;
|
||||
const { getDistanceKm } = EventMap.filters;
|
||||
const { el, clearChildren } = EventMap.utils;
|
||||
|
||||
function createCard(place) {
|
||||
function formatDistance(km) {
|
||||
return km < 10 ? `${km.toFixed(1)} km entfernt` : `${Math.round(km)} km entfernt`;
|
||||
}
|
||||
|
||||
function createCard(place, userLocation) {
|
||||
const category = getCategoryById(place.category);
|
||||
|
||||
const select = () => setState({ selectedPlaceId: place.id });
|
||||
|
||||
const children = [
|
||||
el('h3', { className: 'place-card__name', text: place.name }),
|
||||
el('span', {
|
||||
className: 'place-card__category',
|
||||
style: { backgroundColor: category.color },
|
||||
text: `${category.icon} ${category.label}`,
|
||||
}),
|
||||
el('p', { className: 'place-card__desc', text: place.shortDescription }),
|
||||
];
|
||||
|
||||
const distanceKm = getDistanceKm(place, userLocation);
|
||||
if (distanceKm !== null) {
|
||||
children.push(el('p', { className: 'place-card__distance', text: formatDistance(distanceKm) }));
|
||||
}
|
||||
|
||||
return el(
|
||||
'li',
|
||||
{
|
||||
@@ -29,31 +50,25 @@
|
||||
},
|
||||
},
|
||||
},
|
||||
[
|
||||
el('h3', { className: 'place-card__name', text: place.name }),
|
||||
el('span', {
|
||||
className: 'place-card__category',
|
||||
style: { backgroundColor: category.color },
|
||||
text: `${category.icon} ${category.label}`,
|
||||
}),
|
||||
el('p', { className: 'place-card__desc', text: place.shortDescription }),
|
||||
]
|
||||
children
|
||||
);
|
||||
}
|
||||
|
||||
function render(container, places) {
|
||||
function render(container, filteredPlaces, totalPlacesCount, userLocation) {
|
||||
clearChildren(container);
|
||||
|
||||
if (!places || places.length === 0) {
|
||||
container.appendChild(
|
||||
el('p', { className: 'list-empty', text: 'Keine Ausflugsziele vorhanden.' })
|
||||
);
|
||||
if (!filteredPlaces || filteredPlaces.length === 0) {
|
||||
const message =
|
||||
totalPlacesCount > 0
|
||||
? 'Keine Ausflugsziele gefunden. Filter anpassen?'
|
||||
: 'Keine Ausflugsziele vorhanden.';
|
||||
container.appendChild(el('p', { className: 'list-empty', text: message }));
|
||||
return;
|
||||
}
|
||||
|
||||
const list = el('ul', { className: 'place-list' });
|
||||
for (const place of places) {
|
||||
list.appendChild(createCard(place));
|
||||
for (const place of filteredPlaces) {
|
||||
list.appendChild(createCard(place, userLocation));
|
||||
}
|
||||
container.appendChild(list);
|
||||
}
|
||||
@@ -66,11 +81,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
render(container, getState().places);
|
||||
const renderFromState = (state) => {
|
||||
render(container, getFilteredPlaces(), state.places.length, state.userLocation);
|
||||
};
|
||||
|
||||
subscribe((state) => {
|
||||
render(container, state.places);
|
||||
});
|
||||
renderFromState(getState());
|
||||
subscribe(renderFromState);
|
||||
}
|
||||
|
||||
EventMap.ui.initList = initList;
|
||||
|
||||
Reference in New Issue
Block a user