feat: KoePi bestellmagazin-Filter, KW ohne Klammern erkannt, kommende Prospekte nach Woche unterteilt, Bier-Icon-Prospekte nach oben sortiert

This commit is contained in:
2026-07-18 10:57:49 +02:00
parent c1693c93c8
commit 824e76a107
2 changed files with 53 additions and 9 deletions

View File

@@ -166,11 +166,15 @@ async function fetchLeafletDetail(leafletId, cache) {
} }
} }
// Extrahiert "(KW29 Do-Sa)"-artige Klammerzusätze aus dem Leaflet-Namen, falls // Extrahiert eine "(KW29 Do-Sa)"-artige Angabe aus dem Leaflet-Namen. Manche
// vorhanden (nicht jede Filiale/jeder Prospekt hat sowas) // 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) { function extractWeekInfo(name) {
const m = (name || '').match(/\([^)]*KW\s*\d+[^)]*\)/i); const bracket = (name || '').match(/\([^)]*KW\s*\d+[^)]*\)/i);
return m ? m[0] : ''; 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 // 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) // "Sondermagazin"-Prospekte ausblenden (keine normalen Wochenangebote)
// Unerwünschte Prospekt-Typen ausblenden (Sondermagazine, Reise-Prospekte etc.) // 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 withoutSondermagazin = all.filter(b => {
const t = (b.title || '').toLowerCase(); const t = (b.title || '').toLowerCase();
return !EXCLUDED_TITLE_KEYWORDS.some(kw => t.includes(kw)); return !EXCLUDED_TITLE_KEYWORDS.some(kw => t.includes(kw));

View File

@@ -45,6 +45,39 @@ function fmtDate(str) {
catch { return 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) ──────────────────────────────────────────────── // ── Angebots-Karte (marktguru) ────────────────────────────────────────────────
function OfferCard({ offer }) { function OfferCard({ offer }) {
const color = publisherColor(offer.retailer); const color = publisherColor(offer.retailer);
@@ -301,7 +334,7 @@ export default function Koepi({ toast }) {
<> <>
<div style={{ ...S.head, marginBottom:12 }}>DEINE MÄRKTE AKTUELL ({prospekte.current.length})</div> <div style={{ ...S.head, marginBottom:12 }}>DEINE MÄRKTE AKTUELL ({prospekte.current.length})</div>
<div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:12, marginBottom:24 }}> <div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:12, marginBottom:24 }}>
{prospekte.current.map(b=><ProspektCard key={b.id} b={b}/>)} {sortBeerFirst(prospekte.current).map(b=><ProspektCard key={b.id} b={b}/>)}
</div> </div>
</> </>
)} )}
@@ -311,9 +344,16 @@ export default function Koepi({ toast }) {
color:'rgba(255,255,255,0.35)' }}> color:'rgba(255,255,255,0.35)' }}>
KOMMENDE PROSPEKTE ({prospekte.future.length}) KOMMENDE PROSPEKTE ({prospekte.future.length})
</div> </div>
<div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:12, opacity:0.75 }}> {groupByWeek(prospekte.future).map(group => (
{prospekte.future.map(b=><ProspektCard key={b.id} b={b}/>)} <div key={group.label} style={{ marginBottom:20 }}>
</div> <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:10, letterSpacing:1, marginBottom:8 }}>
{group.label}
</div>
<div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:12, opacity:0.75 }}>
{sortBeerFirst(group.items).map(b=><ProspektCard key={b.id} b={b}/>)}
</div>
</div>
))}
</> </>
)} )}
{!prospekte.current?.length && !prospekte.future?.length && ( {!prospekte.current?.length && !prospekte.future?.length && (