Bewertung
This commit is contained in:
27
src/app/api/hotels/[id]/ratings/route.ts
Normal file
27
src/app/api/hotels/[id]/ratings/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { query } from '@/lib/db';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
// POST: Bewertung speichern oder aktualisieren
|
||||||
|
export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
|
||||||
|
try {
|
||||||
|
const { member_id, grade, comment } = await req.json();
|
||||||
|
if (!member_id || !grade) {
|
||||||
|
return NextResponse.json({ error: 'Mitreisender und Note erforderlich' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await query(
|
||||||
|
`INSERT INTO hotel_ratings (hotel_id, member_id, grade, comment, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, NOW())
|
||||||
|
ON CONFLICT (hotel_id, member_id)
|
||||||
|
DO UPDATE SET grade = EXCLUDED.grade, comment = EXCLUDED.comment, updated_at = NOW()
|
||||||
|
RETURNING *`,
|
||||||
|
[params.id, member_id, grade, comment ? comment.trim() : null]
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, rating: res.rows[0] });
|
||||||
|
} catch (e: any) {
|
||||||
|
return NextResponse.json({ success: false, error: e.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,33 +6,53 @@ export const dynamic = 'force-dynamic';
|
|||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const res = await query(`
|
const res = await query(`
|
||||||
|
SELECT
|
||||||
|
h.*,
|
||||||
|
t.name AS trip_name,
|
||||||
|
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.nights AS latest_nights,
|
||||||
|
ph.persons AS latest_persons,
|
||||||
|
bp.best_price,
|
||||||
|
hr.avg_grade,
|
||||||
|
hr.total_votes,
|
||||||
|
hr.ratings_json
|
||||||
|
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
|
||||||
|
FROM hotel_price_history
|
||||||
|
WHERE hotel_id::text = h.id::text
|
||||||
|
ORDER BY tracked_at DESC NULLS LAST, id DESC
|
||||||
|
LIMIT 1
|
||||||
|
) ph ON true
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT MIN(price_numeric)::float AS best_price
|
||||||
|
FROM hotel_price_history
|
||||||
|
WHERE hotel_id::text = h.id::text AND price_numeric > 0
|
||||||
|
) bp ON true
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
SELECT
|
SELECT
|
||||||
h.*,
|
ROUND(AVG(hr_sub.grade), 1)::float AS avg_grade,
|
||||||
t.name AS trip_name,
|
COUNT(hr_sub.id)::int AS total_votes,
|
||||||
ph.price_numeric::float AS latest_price,
|
COALESCE(
|
||||||
ph.price_per_night::float AS latest_price_per_night,
|
json_agg(
|
||||||
ph.price_per_person::float AS latest_price_per_person,
|
json_build_object(
|
||||||
ph.nights AS latest_nights,
|
'member_id', hr_sub.member_id,
|
||||||
ph.persons AS latest_persons,
|
'name', tm.name,
|
||||||
bp.best_price,
|
'color', tm.color,
|
||||||
bp.best_price_per_night
|
'grade', hr_sub.grade,
|
||||||
FROM hotels h
|
'comment', hr_sub.comment
|
||||||
LEFT JOIN trips t ON h.trip_id = t.id
|
)
|
||||||
LEFT JOIN LATERAL (
|
ORDER BY hr_sub.updated_at DESC
|
||||||
SELECT price_numeric, price_per_night, price_per_person, nights, persons
|
),
|
||||||
FROM hotel_price_history
|
'[]'
|
||||||
WHERE hotel_id::text = h.id::text
|
) AS ratings_json
|
||||||
ORDER BY tracked_at DESC NULLS LAST, id DESC
|
FROM hotel_ratings hr_sub
|
||||||
LIMIT 1
|
JOIN trip_members tm ON hr_sub.member_id = tm.id
|
||||||
) ph ON true
|
WHERE hr_sub.hotel_id = h.id
|
||||||
LEFT JOIN LATERAL (
|
) hr ON true
|
||||||
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 });
|
return NextResponse.json({ success: true, hotels: res.rows });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
55
src/app/api/trips/[id]/members/route.ts
Normal file
55
src/app/api/trips/[id]/members/route.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { query } from '@/lib/db';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
// GET: Alle Mitreisenden eines Trips laden
|
||||||
|
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
|
||||||
|
try {
|
||||||
|
const res = await query(
|
||||||
|
`SELECT * FROM trip_members WHERE trip_id = $1 ORDER BY created_at ASC`,
|
||||||
|
[params.id]
|
||||||
|
);
|
||||||
|
return NextResponse.json({ success: true, members: res.rows });
|
||||||
|
} catch (e: any) {
|
||||||
|
return NextResponse.json({ success: false, error: e.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: Neuen Mitreisenden hinzufügen
|
||||||
|
export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
|
||||||
|
try {
|
||||||
|
const { name, color } = await req.json();
|
||||||
|
if (!name) {
|
||||||
|
return NextResponse.json({ error: 'Name ist erforderlich' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultColors = ['#2563eb', '#db2777', '#059669', '#d97706', '#7c3aed', '#0891b2'];
|
||||||
|
const randomColor = color || defaultColors[Math.floor(Math.random() * defaultColors.length)];
|
||||||
|
|
||||||
|
const res = await query(
|
||||||
|
`INSERT INTO trip_members (trip_id, name, color)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
RETURNING *`,
|
||||||
|
[params.id, name.trim(), randomColor]
|
||||||
|
);
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true, member: res.rows[0] });
|
||||||
|
} catch (e: any) {
|
||||||
|
return NextResponse.json({ success: false, error: e.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: Mitreisenden entfernen
|
||||||
|
export async function DELETE(req: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const memberId = searchParams.get('memberId');
|
||||||
|
if (!memberId) return NextResponse.json({ error: 'memberId fehlt' }, { status: 400 });
|
||||||
|
|
||||||
|
await query(`DELETE FROM trip_members WHERE id = $1`, [memberId]);
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (e: any) {
|
||||||
|
return NextResponse.json({ success: false, error: e.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
1443
src/app/page.tsx
1443
src/app/page.tsx
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user