This commit is contained in:
2026-08-16 09:51:23 +00:00
parent 9f5f4911f2
commit 472d225fda
3 changed files with 87 additions and 42 deletions

View File

@@ -12,6 +12,7 @@ SELECT
ph.price_numeric::float AS latest_price, ph.price_numeric::float AS latest_price,
ph.price_per_night::float AS latest_price_per_night, ph.price_per_night::float AS latest_price_per_night,
ph.price_per_person::float AS latest_price_per_person, ph.price_per_person::float AS latest_price_per_person,
ph.genius_price_numeric::float AS latest_genius_price,
ph.nights AS latest_nights, ph.nights AS latest_nights,
ph.persons AS latest_persons, ph.persons AS latest_persons,
bp.best_price, bp.best_price,
@@ -21,7 +22,7 @@ SELECT
FROM hotels h FROM hotels h
LEFT JOIN trips t ON h.trip_id = t.id LEFT JOIN trips t ON h.trip_id = t.id
LEFT JOIN LATERAL ( LEFT JOIN LATERAL (
SELECT price_numeric, price_per_night, price_per_person, nights, persons SELECT price_numeric, genius_price_numeric, price_per_night, price_per_person, nights, persons
FROM hotel_price_history FROM hotel_price_history
WHERE hotel_id::text = h.id::text WHERE hotel_id::text = h.id::text
ORDER BY tracked_at DESC NULLS LAST, id DESC ORDER BY tracked_at DESC NULLS LAST, id DESC

View File

@@ -32,13 +32,20 @@ export async function POST(req: NextRequest) {
const numericPrice = parseGermanPrice(body.price); const numericPrice = parseGermanPrice(body.price);
// 1. In hotel_price_history eintragen mit echten Spalten // Prüfen, ob das Genius-Badge vom Scraper erkannt wurde (z.B. body.hasGenius oder body.isGenius)
const hasGeniusBadge = Boolean(body.hasGenius || body.isGenius || body.genius);
// Wenn Genius vorhanden ist: 10% abziehen und runden
const geniusPriceNumeric = (numericPrice !== null && hasGeniusBadge)
? Math.round(numericPrice * 0.9)
: null;
if (numericPrice !== null && numericPrice > 0) { if (numericPrice !== null && numericPrice > 0) {
try { try {
await query( await query(
`INSERT INTO hotel_price_history (hotel_id, price_numeric, price_raw, currency, nights, persons, tracked_at) `INSERT INTO hotel_price_history (hotel_id, price_numeric, genius_price_numeric, price_raw, currency, nights, persons, tracked_at)
VALUES ($1::uuid, $2, $3, 'EUR', $4, $5, NOW())`, VALUES ($1::uuid, $2, $3, $4, 'EUR', $5, $6, NOW())`,
[hotelId, numericPrice, String(body.price || ''), body.nights || 1, body.persons || 2] [hotelId, numericPrice, geniusPriceNumeric, String(body.price || ''), body.nights || 1, body.persons || 2]
); );
} catch (err: any) { } catch (err: any) {
console.error('DB Insert Error (hotel_price_history):', err.message); console.error('DB Insert Error (hotel_price_history):', err.message);

View File

@@ -62,6 +62,7 @@ interface Hotel {
latest_price?: number | null; latest_price?: number | null;
latest_price_per_night?: number | null; latest_price_per_night?: number | null;
latest_price_per_person?: number | null; latest_price_per_person?: number | null;
latest_genius_price?: number | null;
latest_nights?: number | null; latest_nights?: number | null;
latest_persons?: number | null; latest_persons?: number | null;
best_price?: number | null; best_price?: number | null;
@@ -969,9 +970,43 @@ export default function TravelDashboard() {
</div> </div>
)} )}
{/* PREIS-BOX MIT BESTPREIS-VERGLEICH */} {/* PREIS-BOX MIT BESTPREIS-VERGLEICH & GENIUS */}
{totalPrice !== null ? ( {totalPrice !== null ? (
<div style={{ background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: '10px', padding: '0.6rem 0.8rem', marginBottom: '0.8rem', display: 'flex', flexDirection: 'column', gap: '0.35rem' }}> <div style={{ background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: '10px', padding: '0.6rem 0.8rem', marginBottom: '0.8rem', display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
{hotel.latest_genius_price ? (
<>
{/* Durchgestrichener Normalpreis bei aktivem Genius */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: '0.76rem', color: '#64748b' }}>
<span>Regulär: <span style={{ textDecoration: 'line-through' }}>{totalPrice.toLocaleString('de-DE')} €</span></span>
<span style={{ background: '#16a34a', color: '#fff', fontSize: '0.68rem', fontWeight: '800', padding: '0.1rem 0.35rem', borderRadius: '4px' }}>-10% GENIUS</span>
</div>
{/* Aktive Genius-Zeile mit Tiefstpreis-Badge */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '0.3rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', flexWrap: 'wrap' }}>
<span style={{ fontSize: '0.78rem', fontWeight: '700', color: '#003580' }}>Genius:</span>
<span style={{ fontWeight: '900', color: '#003580', fontSize: '0.95rem' }}>
💙 {Number(hotel.latest_genius_price).toLocaleString('de-DE')} €
</span>
<span style={{ color: '#0052b4', fontSize: '0.76rem' }}>
(🌙 {Math.round(Number(hotel.latest_genius_price) / hotelNights).toLocaleString('de-DE')} € / N.)
</span>
</div>
{hotel.best_price && Number(hotel.latest_genius_price) > Number(hotel.best_price) ? (
<span style={{ background: '#fee2e2', color: '#991b1b', border: '1px solid #fecaca', fontSize: '0.7rem', padding: '0.1rem 0.35rem', borderRadius: '4px', fontWeight: '700' }}>
▲ +{(Number(hotel.latest_genius_price) - Number(hotel.best_price)).toLocaleString('de-DE')} €
</span>
) : (
<span style={{ background: '#ecfdf5', color: '#047857', border: '1px solid #a7f3d0', fontSize: '0.7rem', padding: '0.1rem 0.35rem', borderRadius: '4px', fontWeight: '800' }}>
🔥 Tiefstpreis
</span>
)}
</div>
</>
) : (
/* Standard-Preiszeile mit Tiefstpreis-Badge */
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '0.3rem' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '0.3rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', flexWrap: 'wrap' }}>
<span style={{ fontSize: '0.78rem', fontWeight: '700', color: '#475569' }}>Aktuell:</span> <span style={{ fontSize: '0.78rem', fontWeight: '700', color: '#475569' }}>Aktuell:</span>
@@ -993,7 +1028,9 @@ export default function TravelDashboard() {
</span> </span>
)} )}
</div> </div>
)}
{/* Allzeit-Bestpreis Zeile */}
{hotel.best_price && ( {hotel.best_price && (
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', borderTop: '1px dashed #e2e8f0', paddingTop: '0.3rem', fontSize: '0.75rem' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', borderTop: '1px dashed #e2e8f0', paddingTop: '0.3rem', fontSize: '0.75rem' }}>
<span style={{ fontWeight: '700', color: '#059669' }}>🏆 Bestpreis:</span> <span style={{ fontWeight: '700', color: '#059669' }}>🏆 Bestpreis:</span>