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):
|
// 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
|
||||||
|
|||||||
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';
|
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>
|
||||||
|
|||||||