From 824e76a1079097e47910a3fa3d14a273ae83ea7b Mon Sep 17 00:00:00 2001 From: Dicken Date: Sat, 18 Jul 2026 10:57:49 +0200 Subject: [PATCH] feat: KoePi bestellmagazin-Filter, KW ohne Klammern erkannt, kommende Prospekte nach Woche unterteilt, Bier-Icon-Prospekte nach oben sortiert --- backend/src/tools/koepi/routes.js | 14 +++++---- frontend/src/tools/koepi.jsx | 48 ++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/backend/src/tools/koepi/routes.js b/backend/src/tools/koepi/routes.js index 913ed37..1b4e49f 100644 --- a/backend/src/tools/koepi/routes.js +++ b/backend/src/tools/koepi/routes.js @@ -166,11 +166,15 @@ async function fetchLeafletDetail(leafletId, cache) { } } -// Extrahiert "(KW29 Do-Sa)"-artige Klammerzusätze aus dem Leaflet-Namen, falls -// vorhanden (nicht jede Filiale/jeder Prospekt hat sowas) +// Extrahiert eine "(KW29 Do-Sa)"-artige Angabe aus dem Leaflet-Namen. Manche +// Ketten (z.B. ALDI) schreiben die KW-Angabe ohne umschließende Klammern +// ("... KW32 ...") — die wird dann selbst eingeklammert, damit die Anzeige +// überall einheitlich aussieht. function extractWeekInfo(name) { - const m = (name || '').match(/\([^)]*KW\s*\d+[^)]*\)/i); - return m ? m[0] : ''; + const bracket = (name || '').match(/\([^)]*KW\s*\d+[^)]*\)/i); + if (bracket) return bracket[0]; + const bare = (name || '').match(/KW\s*\d+(?:\s*[A-Za-zÄÖÜäöüß-]+)?/i); + return bare ? `(${bare[0]})` : ''; } // Löst die feste Liste gewünschter Filialen (LOCAL_STORE_SEED_LEAFLETS) einmalig @@ -561,7 +565,7 @@ async function scrapeProspekte() { // "Sondermagazin"-Prospekte ausblenden (keine normalen Wochenangebote) // Unerwünschte Prospekt-Typen ausblenden (Sondermagazine, Reise-Prospekte etc.) - const EXCLUDED_TITLE_KEYWORDS = ['sondermagazin', 'reisen']; + const EXCLUDED_TITLE_KEYWORDS = ['sondermagazin', 'reisen', 'bestellmagazin']; const withoutSondermagazin = all.filter(b => { const t = (b.title || '').toLowerCase(); return !EXCLUDED_TITLE_KEYWORDS.some(kw => t.includes(kw)); diff --git a/frontend/src/tools/koepi.jsx b/frontend/src/tools/koepi.jsx index 7d3c543..5880a8c 100644 --- a/frontend/src/tools/koepi.jsx +++ b/frontend/src/tools/koepi.jsx @@ -45,6 +45,39 @@ function fmtDate(str) { catch { return str; } } +// Prospekte mit Bier-Icon (König Pilsener im Angebot) immer nach oben, +// ansonsten Reihenfolge vom Backend (chronologisch) beibehalten +function sortBeerFirst(arr) { + return [...arr].sort((a, b) => (b.hasKoenigPilsener?1:0) - (a.hasKoenigPilsener?1:0)); +} + +// ISO-Kalenderwoche aus einem Datum berechnen +function getISOWeek(dateStr) { + const d = new Date(dateStr); + d.setHours(0,0,0,0); + d.setDate(d.getDate() + 4 - (d.getDay() || 7)); + const yearStart = new Date(d.getFullYear(), 0, 1); + return { week: Math.ceil((((d - yearStart) / 86400000) + 1) / 7), year: d.getFullYear() }; +} + +// Kommende Prospekte nach Kalenderwoche gruppieren, Gruppen chronologisch sortiert +function groupByWeek(arr) { + const groups = new Map(); + for (const b of arr) { + if (!b.validFrom) { // kein Datum -> eigene Sammelgruppe ganz unten + const key = 'unbekannt'; + if (!groups.has(key)) groups.set(key, { label: 'Ohne Datum', sortKey: Infinity, items: [] }); + groups.get(key).items.push(b); + continue; + } + const { week, year } = getISOWeek(b.validFrom); + const key = `${year}-${week}`; + if (!groups.has(key)) groups.set(key, { label: `KW${week}`, sortKey: year*100+week, items: [] }); + groups.get(key).items.push(b); + } + return [...groups.values()].sort((a,b) => a.sortKey - b.sortKey); +} + // ── Angebots-Karte (marktguru) ──────────────────────────────────────────────── function OfferCard({ offer }) { const color = publisherColor(offer.retailer); @@ -301,7 +334,7 @@ export default function Koepi({ toast }) { <>
DEINE MÄRKTE — AKTUELL ({prospekte.current.length})
- {prospekte.current.map(b=>)} + {sortBeerFirst(prospekte.current).map(b=>)}
)} @@ -311,9 +344,16 @@ export default function Koepi({ toast }) { color:'rgba(255,255,255,0.35)' }}> KOMMENDE PROSPEKTE ({prospekte.future.length}) -
- {prospekte.future.map(b=>)} -
+ {groupByWeek(prospekte.future).map(group => ( +
+
+ {group.label} +
+
+ {sortBeerFirst(group.items).map(b=>)} +
+
+ ))} )} {!prospekte.current?.length && !prospekte.future?.length && (