Bestpreise
This commit is contained in:
1435
llm_context.txt
1435
llm_context.txt
File diff suppressed because it is too large
Load Diff
@@ -5,28 +5,36 @@ export const dynamic = 'force-dynamic';
|
|||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const res = await query(`
|
const res = await query(`
|
||||||
SELECT
|
SELECT
|
||||||
h.*,
|
h.*,
|
||||||
t.name AS trip_name,
|
t.name AS trip_name,
|
||||||
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.nights AS latest_nights,
|
ph.nights AS latest_nights,
|
||||||
ph.persons AS latest_persons
|
ph.persons AS latest_persons,
|
||||||
FROM hotels h
|
bp.best_price,
|
||||||
LEFT JOIN trips t ON h.trip_id = t.id
|
bp.best_price_per_night
|
||||||
LEFT JOIN LATERAL (
|
FROM hotels h
|
||||||
SELECT price_numeric, price_per_night, price_per_person, nights, persons
|
LEFT JOIN trips t ON h.trip_id = t.id
|
||||||
FROM hotel_price_history
|
LEFT JOIN LATERAL (
|
||||||
WHERE hotel_id::text = h.id::text
|
SELECT price_numeric, price_per_night, price_per_person, nights, persons
|
||||||
ORDER BY tracked_at DESC NULLS LAST, id DESC
|
FROM hotel_price_history
|
||||||
LIMIT 1
|
WHERE hotel_id::text = h.id::text
|
||||||
) ph ON true
|
ORDER BY tracked_at DESC NULLS LAST, id DESC
|
||||||
ORDER BY h.checkin_date ASC NULLS LAST, h.id ASC
|
LIMIT 1
|
||||||
`);
|
) ph ON true
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
return NextResponse.json({ success: true, hotels: res.rows });
|
SELECT
|
||||||
|
MIN(price_numeric)::float AS best_price,
|
||||||
|
MIN(price_per_night)::float AS best_price_per_night
|
||||||
|
FROM hotel_price_history
|
||||||
|
WHERE hotel_id::text = h.id::text AND price_numeric > 0
|
||||||
|
) bp ON true
|
||||||
|
ORDER BY h.checkin_date ASC NULLS LAST, h.id ASC
|
||||||
|
`);
|
||||||
|
return NextResponse.json({ success: true, hotels: res.rows });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Fehler in GET /api/hotels:', error.message);
|
console.error('Fehler in GET /api/hotels:', error.message);
|
||||||
try {
|
try {
|
||||||
@@ -67,4 +75,7 @@ export async function POST(req: NextRequest) {
|
|||||||
console.error('Fehler in POST /api/hotels:', error.message);
|
console.error('Fehler in POST /api/hotels:', error.message);
|
||||||
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ interface Hotel {
|
|||||||
latest_price_per_person?: number | null;
|
latest_price_per_person?: number | null;
|
||||||
latest_nights?: number | null;
|
latest_nights?: number | null;
|
||||||
latest_persons?: number | null;
|
latest_persons?: number | null;
|
||||||
|
best_price?: number | null;
|
||||||
|
best_price_per_night?: number | null;
|
||||||
rating?: number | null;
|
rating?: number | null;
|
||||||
review_count?: number | null;
|
review_count?: number | null;
|
||||||
image_url?: string | null;
|
image_url?: string | null;
|
||||||
@@ -938,24 +940,63 @@ export default function TravelDashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Preis-Box */}
|
{/* Preis-Box mit Aktuellem Kurs & Bestpreis */}
|
||||||
{totalPrice !== null ? (
|
{totalPrice !== null ? (
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', marginBottom: '0.8rem', background: '#ecfdf5', padding: '0.5rem 0.7rem', borderRadius: '8px', border: '1px solid #a7f3d0', alignItems: 'center' }}>
|
<div style={{
|
||||||
<span style={{ fontWeight: '800', color: '#047857', fontSize: '0.92rem' }}>
|
background: '#f8fafc',
|
||||||
💰 Gesamt: {totalPrice.toLocaleString('de-DE')} €
|
border: '1px solid #e2e8f0',
|
||||||
</span>
|
borderRadius: '10px',
|
||||||
<span style={{ color: '#059669', fontSize: '0.8rem', fontWeight: '600' }}>
|
padding: '0.65rem 0.85rem',
|
||||||
| 🌙 {pricePerNight?.toLocaleString('de-DE')} € / N.
|
marginBottom: '0.8rem',
|
||||||
</span>
|
display: 'flex',
|
||||||
<span style={{ color: '#059669', fontSize: '0.8rem' }}>
|
flexDirection: 'column',
|
||||||
| 👤 {pricePerPersonPerNight?.toLocaleString('de-DE')} € / P.
|
gap: '0.4rem'
|
||||||
</span>
|
}}>
|
||||||
</div>
|
{/* Zeile 1: Aktueller Preis */}
|
||||||
) : (
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '0.4rem' }}>
|
||||||
<div style={{ marginBottom: '0.8rem', fontSize: '0.82rem', color: '#6b7280', fontStyle: 'italic' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', flexWrap: 'wrap' }}>
|
||||||
💰 Noch kein Preis erfasst
|
<span style={{ fontSize: '0.8rem', fontWeight: '700', color: '#475569' }}>Aktuell:</span>
|
||||||
</div>
|
<span style={{ fontWeight: '800', color: '#0f172a', fontSize: '0.95rem' }}>
|
||||||
)}
|
💰 {totalPrice.toLocaleString('de-DE')} €
|
||||||
|
</span>
|
||||||
|
<span style={{ color: '#64748b', fontSize: '0.78rem' }}>
|
||||||
|
(🌙 {pricePerNight?.toLocaleString('de-DE')} € / N. · 👤 {pricePerPersonPerNight?.toLocaleString('de-DE')} € / P.)
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hotel.best_price && totalPrice > hotel.best_price ? (
|
||||||
|
<span style={{ background: '#fee2e2', color: '#991b1b', border: '1px solid #fecaca', fontSize: '0.72rem', padding: '0.15rem 0.4rem', borderRadius: '4px', fontWeight: '700' }}>
|
||||||
|
▲ +{(totalPrice - hotel.best_price).toLocaleString('de-DE')} € über Tiefst
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span style={{ background: '#ecfdf5', color: '#047857', border: '1px solid #a7f3d0', fontSize: '0.72rem', padding: '0.15rem 0.4rem', borderRadius: '4px', fontWeight: '800' }}>
|
||||||
|
🔥 Tiefstpreis
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Zeile 2: Bestpreis / Historischer Tiefststand */}
|
||||||
|
{hotel.best_price && (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderTop: '1px dashed #e2e8f0', paddingTop: '0.35rem', fontSize: '0.78rem' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}>
|
||||||
|
<span style={{ fontWeight: '700', color: '#059669' }}>🏆 Bestpreis:</span>
|
||||||
|
<span style={{ fontWeight: '800', color: '#047857' }}>
|
||||||
|
{Number(hotel.best_price).toLocaleString('de-DE')} €
|
||||||
|
</span>
|
||||||
|
{hotel.best_price_per_night && (
|
||||||
|
<span style={{ color: '#64748b', fontSize: '0.74rem' }}>
|
||||||
|
(🌙 {Math.round(Number(hotel.best_price_per_night)).toLocaleString('de-DE')} € / N.)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ marginBottom: '0.8rem', fontSize: '0.82rem', color: '#6b7280', fontStyle: 'italic' }}>
|
||||||
|
💰 Noch kein Preis erfasst
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Meta Tags */}
|
{/* Meta Tags */}
|
||||||
<div style={{ display: 'flex', gap: '0.8rem', fontSize: '0.8rem', marginBottom: '0.8rem', color: '#374151', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: '0.8rem', fontSize: '0.8rem', marginBottom: '0.8rem', color: '#374151', flexWrap: 'wrap' }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user