diff --git a/backend/src/tools/koepi/routes.js b/backend/src/tools/koepi/routes.js index 218260d..be07738 100644 --- a/backend/src/tools/koepi/routes.js +++ b/backend/src/tools/koepi/routes.js @@ -384,27 +384,39 @@ async function fetchOffersViaPuppeteer() { } } +// Prüft, ob "COUNT x VOL" (z.B. "20 x 0,5") in der Beschreibung vorkommt — +// robust gegen Leerzeichen um das "x", Komma/Punkt als Dezimaltrennzeichen, +// und verhindert Fehltreffer wie "0,5" innerhalb von "0,55" +function hasQty(desc, count, vol) { + const volPattern = vol.replace(',', '[,.]'); + const re = new RegExp(`${count}\\s*x\\s*${volPattern}(?!\\d)`, 'i'); + return re.test(desc); +} + +// Bild pro Angebot exakt nach Mengenangabe wählen (siehe Vorgabe): +// 20x0,5 -> kasten_050 | 20x0,33 -> kasten_steini | 24x0,33 -> kasten_033 +// 11x0,5 -> kasten_11er | 6x0,33 -> traeger | 24x0,5 -> palette_050 +// 20x0,5 UND 24x0,33 gleichzeitig -> kasten_033_055 | sonst -> koepi_platzhalter +function pickOfferImage(description) { + const d = (description || '').toLowerCase(); + const q2005 = hasQty(d, 20, '0,5'); + const q2433 = hasQty(d, 24, '0,33'); + if (q2005 && q2433) return '/koepi/kasten_033_055.png'; + if (hasQty(d, 20, '0,33')) return '/koepi/kasten_steini.png'; + if (q2433) return '/koepi/kasten_033.png'; + if (hasQty(d, 11, '0,5')) return '/koepi/kasten_11er.png'; + if (hasQty(d, 6, '0,33')) return '/koepi/traeger.png'; + if (hasQty(d, 24, '0,5')) return '/koepi/palette_050.png'; + if (q2005) return '/koepi/kasten_050.png'; + return '/koepi/koepi_platzhalter.png'; +} + // Gemeinsame Weiterverarbeitung, egal ob die Rohdaten über die API oder per // Puppeteer/HTML-Scraping ermittelt wurden function processOffers(offers) { // Lokale Bilder auswählen basierend auf Beschreibung for (const offer of offers) { - const desc = (offer.description || '').toLowerCase(); - if (desc.includes('steini')) { - offer.imageLocal = '/koepi/kasten_steini.png'; - } else if (desc.includes('11 x 0,5') || desc.includes('11x0,5')) { - offer.imageLocal = '/koepi/kasten_lang.png'; - } else if (desc.includes('6 x') || desc.includes('6x') || (desc.includes('flasche') && !desc.includes('kasten'))) { - offer.imageLocal = '/koepi/traeger.png'; - } else if (desc.includes('24 x 0,33') || desc.includes('24x0,33')) { - offer.imageLocal = '/koepi/kasten_033.png'; - } else if (desc.includes('0,5')) { - offer.imageLocal = '/koepi/kasten_050.png'; - } else if (desc.includes('0,33')) { - offer.imageLocal = '/koepi/kasten_033.png'; - } else { - offer.imageLocal = '/koepi/kasten_050.png'; - } + offer.imageLocal = pickOfferImage(offer.description); offer.image = null; // CDN nicht mehr nötig } @@ -547,12 +559,30 @@ async function scrapeProspekte() { for (const b of all) b.hasKoenigPilsener = false; } + // "Sondermagazin"-Prospekte ausblenden (keine normalen Wochenangebote) + const withoutSondermagazin = all.filter(b => !(b.title || '').toLowerCase().includes('sondermagazin')); + const targeted = withoutSondermagazin.filter(b => b.isTarget); + + // Chronologisch in "aktuell laufend" und "kommend" trennen + const todayIso = new Date().toISOString().slice(0, 10); + const isCurrent = b => { + const from = (b.validFrom || '').slice(0, 10); + const to = (b.validTo || '').slice(0, 10); + if (!from) return true; // ohne Datum lieber anzeigen als verstecken + if (from > todayIso) return false; // startet erst noch + if (to && to < todayIso) return false; // schon abgelaufen + return true; + }; + const byValidFrom = (a, b) => (a.validFrom || '').localeCompare(b.validFrom || ''); + const current = targeted.filter(isCurrent).sort(byValidFrom); + const future = targeted.filter(b => !isCurrent(b)).sort(byValidFrom); + const result = { - targeted: all.filter(b => b.isTarget), - others: all.filter(b => !b.isTarget), + current, + future, scrapedAt: new Date().toISOString(), }; - console.log(`🍺 KöPi Prospekte: ${result.targeted.length} eigene Filiale(n), ${result.others.length} andere Filialen derselben Ketten, ${result.targeted.filter(b=>b.hasKoenigPilsener).length} davon mit König Pilsener im Angebot`); + console.log(`🍺 KöPi Prospekte: ${current.length} aktuell laufend, ${future.length} kommend, ${current.filter(b=>b.hasKoenigPilsener).length} davon aktuell mit König Pilsener im Angebot`); cacheSet(cacheKey, result); return result; } diff --git a/frontend/public/koepi/kasten_033_05.png b/frontend/public/koepi/kasten_033_05.png new file mode 100644 index 0000000..57ef7c0 Binary files /dev/null and b/frontend/public/koepi/kasten_033_05.png differ diff --git a/frontend/src/tools/koepi.jsx b/frontend/src/tools/koepi.jsx index 1920d06..647cce8 100644 --- a/frontend/src/tools/koepi.jsx +++ b/frontend/src/tools/koepi.jsx @@ -140,7 +140,6 @@ export default function Koepi({ toast }) { const [offers, setOffers] = useState(null); const [prospekte, setProspekte] = useState(null); const [loading, setLoading] = useState(false); - const [showAll, setShowAll] = useState(false); const isAdmin = getMyRole() === 'admin'; const loadOffers = async (force=false) => { @@ -199,20 +198,21 @@ export default function Koepi({ toast }) {
{/* Header */}
-

+

🍺 KÖPI — König Pilsener

{isAdmin && ( - <> - - - - + +
)}
@@ -263,27 +263,26 @@ export default function Koepi({ toast }) { {/* Prospekte */} {!loading && tab==='prospekte' && prospekte && (
- {prospekte.targeted?.length > 0 && ( + {prospekte.current?.length > 0 && ( <> -
DEINE MÄRKTE ({prospekte.targeted.length})
-
- {prospekte.targeted.map(b=>)} +
DEINE MÄRKTE — AKTUELL ({prospekte.current.length})
+
+ {prospekte.current.map(b=>)}
)} - {prospekte.others?.length > 0 && ( + {prospekte.future?.length > 0 && ( <> - - {showAll && ( -
- {prospekte.others.map(b=>)} -
- )} +
+ KOMMENDE PROSPEKTE ({prospekte.future.length}) +
+
+ {prospekte.future.map(b=>)} +
)} - {!prospekte.targeted?.length && !prospekte.others?.length && ( + {!prospekte.current?.length && !prospekte.future?.length && (
Keine Prospekte gefunden.