diff --git a/src/app/api/hotels/route.ts b/src/app/api/hotels/route.ts index c51af53..d6bf2eb 100644 --- a/src/app/api/hotels/route.ts +++ b/src/app/api/hotels/route.ts @@ -12,6 +12,7 @@ SELECT ph.price_numeric::float AS latest_price, ph.price_per_night::float AS latest_price_per_night, 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.persons AS latest_persons, bp.best_price, @@ -21,7 +22,7 @@ SELECT FROM hotels h LEFT JOIN trips t ON h.trip_id = t.id 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 WHERE hotel_id::text = h.id::text ORDER BY tracked_at DESC NULLS LAST, id DESC diff --git a/src/app/api/webhooks/scraper/route.ts b/src/app/api/webhooks/scraper/route.ts index 9469305..1ca9442 100644 --- a/src/app/api/webhooks/scraper/route.ts +++ b/src/app/api/webhooks/scraper/route.ts @@ -32,18 +32,25 @@ export async function POST(req: NextRequest) { const numericPrice = parseGermanPrice(body.price); - // 1. In hotel_price_history eintragen mit echten Spalten - if (numericPrice !== null && numericPrice > 0) { - try { - await query( - `INSERT INTO hotel_price_history (hotel_id, price_numeric, price_raw, currency, nights, persons, tracked_at) - VALUES ($1::uuid, $2, $3, 'EUR', $4, $5, NOW())`, - [hotelId, numericPrice, String(body.price || ''), body.nights || 1, body.persons || 2] - ); - } catch (err: any) { - console.error('DB Insert Error (hotel_price_history):', err.message); - } - } +// 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) { + try { + await query( + `INSERT INTO hotel_price_history (hotel_id, price_numeric, genius_price_numeric, price_raw, currency, nights, persons, tracked_at) + VALUES ($1::uuid, $2, $3, $4, 'EUR', $5, $6, NOW())`, + [hotelId, numericPrice, geniusPriceNumeric, String(body.price || ''), body.nights || 1, body.persons || 2] + ); + } catch (err: any) { + console.error('DB Insert Error (hotel_price_history):', err.message); + } +} // 2. Stammdaten aktualisieren try { diff --git a/src/app/page.tsx b/src/app/page.tsx index 59af5d0..4588331 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -62,6 +62,7 @@ interface Hotel { latest_price?: number | null; latest_price_per_night?: number | null; latest_price_per_person?: number | null; + latest_genius_price?: number | null; latest_nights?: number | null; latest_persons?: number | null; best_price?: number | null; @@ -182,7 +183,7 @@ export default function TravelDashboard() { const dataTrips = await resTrips.json(); setTrips(dataTrips.trips || []); } - } catch (e) {} + } catch (e) { } try { const resHotels = await fetch('/api/hotels'); @@ -190,7 +191,7 @@ export default function TravelDashboard() { const dataHotels = await resHotels.json(); setHotels(dataHotels.hotels || []); } - } catch (e) {} + } catch (e) { } try { const resAttractions = await fetch('/api/attractions'); @@ -198,7 +199,7 @@ export default function TravelDashboard() { const dataAttractions = await resAttractions.json(); setAttractions(dataAttractions.attractions || []); } - } catch (e) {} + } catch (e) { } } finally { setLoading(false); } @@ -572,7 +573,7 @@ export default function TravelDashboard() { return (
- + {/* HEADER */}

@@ -804,11 +805,11 @@ export default function TravelDashboard() { const hotelNights = calculateNights(hotel.checkin_date, hotel.checkout_date) || hotel.latest_nights || nights || 1; const persons = hotel.latest_persons || 2; const totalPrice = hotel.latest_price !== null && hotel.latest_price !== undefined ? Number(hotel.latest_price) : null; - const pricePerNight = hotel.latest_price_per_night - ? Math.round(Number(hotel.latest_price_per_night)) + const pricePerNight = hotel.latest_price_per_night + ? Math.round(Number(hotel.latest_price_per_night)) : (totalPrice ? Math.round(totalPrice / hotelNights) : null); - const pricePerPersonPerNight = hotel.latest_price_per_person - ? Math.round(Number(hotel.latest_price_per_person)) + const pricePerPersonPerNight = hotel.latest_price_per_person + ? Math.round(Number(hotel.latest_price_per_person)) : (totalPrice ? Math.round(totalPrice / (hotelNights * persons)) : null); const hasGrade = hotel.avg_grade !== null && hotel.avg_grade !== undefined; @@ -969,31 +970,67 @@ export default function TravelDashboard() {

)} - {/* PREIS-BOX MIT BESTPREIS-VERGLEICH */} + {/* PREIS-BOX MIT BESTPREIS-VERGLEICH & GENIUS */} {totalPrice !== null ? (
-
-
- Aktuell: - - πŸ’° {totalPrice.toLocaleString('de-DE')} € - - - (πŸŒ™ {pricePerNight?.toLocaleString('de-DE')} € / N. Β· πŸ‘€ {pricePerPersonPerNight?.toLocaleString('de-DE')} € / P.) - + + {hotel.latest_genius_price ? ( + <> + {/* Durchgestrichener Normalpreis bei aktivem Genius */} +
+ RegulΓ€r: {totalPrice.toLocaleString('de-DE')} € + -10% GENIUS +
+ + {/* Aktive Genius-Zeile mit Tiefstpreis-Badge */} +
+
+ Genius: + + πŸ’™ {Number(hotel.latest_genius_price).toLocaleString('de-DE')} € + + + (πŸŒ™ {Math.round(Number(hotel.latest_genius_price) / hotelNights).toLocaleString('de-DE')} € / N.) + +
+ + {hotel.best_price && Number(hotel.latest_genius_price) > Number(hotel.best_price) ? ( + + β–² +{(Number(hotel.latest_genius_price) - Number(hotel.best_price)).toLocaleString('de-DE')} € + + ) : ( + + πŸ”₯ Tiefstpreis + + )} +
+ + ) : ( + /* Standard-Preiszeile mit Tiefstpreis-Badge */ +
+
+ Aktuell: + + πŸ’° {totalPrice.toLocaleString('de-DE')} € + + + (πŸŒ™ {pricePerNight?.toLocaleString('de-DE')} € / N. Β· πŸ‘€ {pricePerPersonPerNight?.toLocaleString('de-DE')} € / P.) + +
+ + {hotel.best_price && totalPrice > Number(hotel.best_price) ? ( + + β–² +{(totalPrice - Number(hotel.best_price)).toLocaleString('de-DE')} € + + ) : ( + + πŸ”₯ Tiefstpreis + + )}
+ )} - {hotel.best_price && totalPrice > Number(hotel.best_price) ? ( - - β–² +{(totalPrice - Number(hotel.best_price)).toLocaleString('de-DE')} € - - ) : ( - - πŸ”₯ Tiefstpreis - - )} -
- + {/* Allzeit-Bestpreis Zeile */} {hotel.best_price && (
πŸ† Bestpreis: