fix: KoePi Mobile-Layout, Bilddateiname korrigiert, Haendler-Logos statt Text, 'reisen'-Filter
@@ -396,12 +396,12 @@ function hasQty(desc, count, vol) {
|
||||
// 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
|
||||
// 20x0,5 UND 24x0,33 gleichzeitig -> kasten_033_05 | 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 (q2005 && q2433) return '/koepi/kasten_033_05.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';
|
||||
@@ -560,7 +560,12 @@ async function scrapeProspekte() {
|
||||
}
|
||||
|
||||
// "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);
|
||||
|
||||
// Chronologisch in "aktuell laufend" und "kommend" trennen
|
||||
|
||||
BIN
frontend/public/koepi/aldi.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
frontend/public/koepi/edeka.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
frontend/public/koepi/hornbach.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
frontend/public/koepi/kaufland.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
frontend/public/koepi/lidl.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
frontend/public/koepi/netto.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
frontend/public/koepi/netto_getraenkemarkt.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
frontend/public/koepi/penny.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/public/koepi/rewe.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
frontend/public/koepi/trinkgut.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -19,6 +19,25 @@ function publisherColor(name) {
|
||||
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) {
|
||||
if (!str) return null;
|
||||
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) ────────────────────────────────────────────────
|
||||
function OfferCard({ offer }) {
|
||||
const color = publisherColor(offer.retailer);
|
||||
const logo = retailerLogo(offer.retailer);
|
||||
return (
|
||||
<div style={{
|
||||
...S.card, padding:'12px 14px',
|
||||
@@ -45,11 +65,15 @@ function OfferCard({ offer }) {
|
||||
|
||||
{/* Händler + Adresse */}
|
||||
<div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}>
|
||||
<span style={{
|
||||
background:`${color}22`, border:`1px solid ${color}55`,
|
||||
borderRadius:4, padding:'2px 7px',
|
||||
color, fontFamily:'monospace', fontSize:10, fontWeight:700,
|
||||
}}>{offer.retailer}</span>
|
||||
{logo ? (
|
||||
<img src={logo} alt={offer.retailer} style={{ height:20, maxWidth:100, objectFit:'contain', borderRadius:3 }}/>
|
||||
) : (
|
||||
<span style={{
|
||||
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>}
|
||||
</div>
|
||||
{offer.address && (
|
||||
@@ -97,6 +121,7 @@ function OfferCard({ offer }) {
|
||||
// ── Prospekt-Karte (marktguru) ────────────────────────────────────────────────
|
||||
function ProspektCard({ b }) {
|
||||
const color = publisherColor(b.publisher);
|
||||
const logo = retailerLogo(b.publisher);
|
||||
return (
|
||||
<a href={b.url} target="_blank" rel="noopener noreferrer" style={{ textDecoration:'none' }}>
|
||||
<div style={{
|
||||
@@ -111,10 +136,14 @@ function ProspektCard({ b }) {
|
||||
)}
|
||||
<div style={{ padding:'10px 12px' }}>
|
||||
<div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4, flexWrap:'wrap' }}>
|
||||
<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>
|
||||
{logo ? (
|
||||
<img src={logo} alt={b.publisher} style={{ height:20, maxWidth:110, objectFit:'contain', borderRadius:3 }}/>
|
||||
) : (
|
||||
<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 && (
|
||||
<span title="König Pilsener gerade im Angebot" style={{ fontSize:13 }}>🍺</span>
|
||||
)}
|
||||
@@ -195,16 +224,15 @@ export default function Koepi({ toast }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth:700, margin:'0 auto' }}>
|
||||
<div style={{ maxWidth:700, margin:'0 auto', padding:'0 14px' }}>
|
||||
{/* Header */}
|
||||
<div style={{ display:'flex', alignItems:'center', gap:10, 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 }}>
|
||||
<div style={{ marginBottom:20 }}>
|
||||
<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
|
||||
</h2>
|
||||
<div style={{ flex:1 }}/>
|
||||
{isAdmin && (
|
||||
<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' }}>
|
||||
{cronRunning ? '⏳ läuft…' : '🔔 Cron testen'}
|
||||
</button>
|
||||
|
||||