fix: KoePi Mobile-Layout, Bilddateiname korrigiert, Haendler-Logos statt Text, 'reisen'-Filter

This commit is contained in:
2026-07-18 02:45:47 +02:00
parent 263610b0bc
commit dadf2525b7
12 changed files with 50 additions and 17 deletions

View File

@@ -396,12 +396,12 @@ function hasQty(desc, count, vol) {
// Bild pro Angebot exakt nach Mengenangabe wählen (siehe Vorgabe): // Bild pro Angebot exakt nach Mengenangabe wählen (siehe Vorgabe):
// 20x0,5 -> kasten_050 | 20x0,33 -> kasten_steini | 24x0,33 -> kasten_033 // 20x0,5 -> kasten_050 | 20x0,33 -> kasten_steini | 24x0,33 -> kasten_033
// 11x0,5 -> kasten_11er | 6x0,33 -> traeger | 24x0,5 -> palette_050 // 11x0,5 -> kasten_11er | 6x0,33 -> traeger | 24x0,5 -> palette_050
// 20x0,5 UND 24x0,33 gleichzeitig -> kasten_033_055 | sonst -> koepi_platzhalter // 20x0,5 UND 24x0,33 gleichzeitig -> kasten_033_05 | sonst -> koepi_platzhalter
function pickOfferImage(description) { function pickOfferImage(description) {
const d = (description || '').toLowerCase(); const d = (description || '').toLowerCase();
const q2005 = hasQty(d, 20, '0,5'); const q2005 = hasQty(d, 20, '0,5');
const q2433 = hasQty(d, 24, '0,33'); const q2433 = hasQty(d, 24, '0,33');
if (q2005 && q2433) return '/koepi/kasten_033_055.png'; if (q2005 && q2433) return '/koepi/kasten_033_05.png';
if (hasQty(d, 20, '0,33')) return '/koepi/kasten_steini.png'; if (hasQty(d, 20, '0,33')) return '/koepi/kasten_steini.png';
if (q2433) return '/koepi/kasten_033.png'; if (q2433) return '/koepi/kasten_033.png';
if (hasQty(d, 11, '0,5')) return '/koepi/kasten_11er.png'; if (hasQty(d, 11, '0,5')) return '/koepi/kasten_11er.png';
@@ -560,7 +560,12 @@ async function scrapeProspekte() {
} }
// "Sondermagazin"-Prospekte ausblenden (keine normalen Wochenangebote) // "Sondermagazin"-Prospekte ausblenden (keine normalen Wochenangebote)
const withoutSondermagazin = all.filter(b => !(b.title || '').toLowerCase().includes('sondermagazin')); // Unerwünschte Prospekt-Typen ausblenden (Sondermagazine, Reise-Prospekte etc.)
const EXCLUDED_TITLE_KEYWORDS = ['sondermagazin', 'reisen'];
const withoutSondermagazin = all.filter(b => {
const t = (b.title || '').toLowerCase();
return !EXCLUDED_TITLE_KEYWORDS.some(kw => t.includes(kw));
});
const targeted = withoutSondermagazin.filter(b => b.isTarget); const targeted = withoutSondermagazin.filter(b => b.isTarget);
// Chronologisch in "aktuell laufend" und "kommend" trennen // Chronologisch in "aktuell laufend" und "kommend" trennen

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -19,6 +19,25 @@ function publisherColor(name) {
return '#888'; return '#888';
} }
// Logo-Bild je Händler — Reihenfolge wichtig: "Netto Getränke-Discount" muss
// VOR dem generischen "netto"-Check geprüft werden. Kein Eintrag (z.B.
// Kaufland) -> Aufrufer fällt auf den farbigen Text-Badge zurück.
function retailerLogo(name) {
if (!name) return null;
const n = name.toLowerCase();
if (n.includes('netto') && n.includes('getränke')) return '/koepi/netto_getraenkemarkt.png';
if (n.includes('netto')) return '/koepi/netto.png';
if (n.includes('rewe dortmund')) return null;
if (n.includes('rewe')) return '/koepi/rewe.png';
if (n.includes('edeka') || n.includes('e center')) return '/koepi/edeka.png';
if (n.includes('trinkgut')) return '/koepi/trinkgut.png';
if (n.includes('penny')) return '/koepi/penny.png';
if (n.includes('lidl')) return '/koepi/lidl.png';
if (n.includes('aldi')) return '/koepi/aldi.png';
if (n.includes('hornbach')) return '/koepi/hornbach.png';
return null;
}
function fmtDate(str) { function fmtDate(str) {
if (!str) return null; if (!str) return null;
try { return new Date(str).toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' }); } try { return new Date(str).toLocaleDateString('de-DE', { day:'2-digit', month:'2-digit', year:'numeric' }); }
@@ -28,6 +47,7 @@ function fmtDate(str) {
// ── Angebots-Karte (marktguru) ──────────────────────────────────────────────── // ── Angebots-Karte (marktguru) ────────────────────────────────────────────────
function OfferCard({ offer }) { function OfferCard({ offer }) {
const color = publisherColor(offer.retailer); const color = publisherColor(offer.retailer);
const logo = retailerLogo(offer.retailer);
return ( return (
<div style={{ <div style={{
...S.card, padding:'12px 14px', ...S.card, padding:'12px 14px',
@@ -45,11 +65,15 @@ function OfferCard({ offer }) {
{/* Händler + Adresse */} {/* Händler + Adresse */}
<div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}> <div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}>
<span style={{ {logo ? (
background:`${color}22`, border:`1px solid ${color}55`, <img src={logo} alt={offer.retailer} style={{ height:20, maxWidth:100, objectFit:'contain', borderRadius:3 }}/>
borderRadius:4, padding:'2px 7px', ) : (
color, fontFamily:'monospace', fontSize:10, fontWeight:700, <span style={{
}}>{offer.retailer}</span> background:`${color}22`, border:`1px solid ${color}55`,
borderRadius:4, padding:'2px 7px',
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
}}>{offer.retailer}</span>
)}
{offer.oldPrice && <span style={{ background:'#e2001a', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>-29%</span>} {offer.oldPrice && <span style={{ background:'#e2001a', color:'#fff', borderRadius:4, padding:'2px 6px', fontSize:9, fontFamily:'monospace', fontWeight:700 }}>-29%</span>}
</div> </div>
{offer.address && ( {offer.address && (
@@ -97,6 +121,7 @@ function OfferCard({ offer }) {
// ── Prospekt-Karte (marktguru) ──────────────────────────────────────────────── // ── Prospekt-Karte (marktguru) ────────────────────────────────────────────────
function ProspektCard({ b }) { function ProspektCard({ b }) {
const color = publisherColor(b.publisher); const color = publisherColor(b.publisher);
const logo = retailerLogo(b.publisher);
return ( return (
<a href={b.url} target="_blank" rel="noopener noreferrer" style={{ textDecoration:'none' }}> <a href={b.url} target="_blank" rel="noopener noreferrer" style={{ textDecoration:'none' }}>
<div style={{ <div style={{
@@ -111,10 +136,14 @@ function ProspektCard({ b }) {
)} )}
<div style={{ padding:'10px 12px' }}> <div style={{ padding:'10px 12px' }}>
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, flexWrap:'wrap' }}> <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, flexWrap:'wrap' }}>
<div style={{ background:`${color}22`, border:`1px solid ${color}44`, {logo ? (
borderRadius:4, padding:'2px 8px', color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}> <img src={logo} alt={b.publisher} style={{ height:20, maxWidth:110, objectFit:'contain', borderRadius:3 }}/>
{b.publisher} ) : (
</div> <div style={{ background:`${color}22`, border:`1px solid ${color}44`,
borderRadius:4, padding:'2px 8px', color, fontFamily:'monospace', fontSize:10, fontWeight:700 }}>
{b.publisher}
</div>
)}
{b.hasKoenigPilsener && ( {b.hasKoenigPilsener && (
<span title="König Pilsener gerade im Angebot" style={{ fontSize:13 }}>🍺</span> <span title="König Pilsener gerade im Angebot" style={{ fontSize:13 }}>🍺</span>
)} )}
@@ -195,16 +224,15 @@ export default function Koepi({ toast }) {
}; };
return ( return (
<div style={{ maxWidth:700, margin:'0 auto' }}> <div style={{ maxWidth:700, margin:'0 auto', padding:'0 14px' }}>
{/* Header */} {/* Header */}
<div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:20 }}> <div style={{ marginBottom:20 }}>
<h2 style={{ margin:0, fontSize:15, fontFamily:'monospace', color:'rgba(255,255,255,0.55)', letterSpacing:2, fontWeight:400, whiteSpace:'nowrap', flexShrink:0 }}> <h2 style={{ margin:'0 0 10px 0', fontSize:15, fontFamily:'monospace', color:'rgba(255,255,255,0.55)', letterSpacing:2, fontWeight:400 }}>
🍺 KÖPI König Pilsener 🍺 KÖPI König Pilsener
</h2> </h2>
<div style={{ flex:1 }}/>
{isAdmin && ( {isAdmin && (
<div style={{ display:'flex', gap:8, overflowX:'auto', scrollbarWidth:'none', <div style={{ display:'flex', gap:8, overflowX:'auto', scrollbarWidth:'none',
WebkitOverflowScrolling:'touch', paddingBottom:2, minWidth:0 }}> WebkitOverflowScrolling:'touch', paddingBottom:2 }}>
<button onClick={testDailyCheck} disabled={cronRunning} style={{ ...S.btn('#f59e0b',true), fontSize:10, opacity:cronRunning?0.5:1, flexShrink:0, whiteSpace:'nowrap' }}> <button onClick={testDailyCheck} disabled={cronRunning} style={{ ...S.btn('#f59e0b',true), fontSize:10, opacity:cronRunning?0.5:1, flexShrink:0, whiteSpace:'nowrap' }}>
{cronRunning ? '⏳ läuft…' : '🔔 Cron testen'} {cronRunning ? '⏳ läuft…' : '🔔 Cron testen'}
</button> </button>