235 lines
8.3 KiB
TypeScript
235 lines
8.3 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
||
import { redirect } from "next/navigation";
|
||
import { auth } from "@/lib/auth";
|
||
import { Card, CardContent } from "@/components/ui/card";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import Link from "next/link";
|
||
|
||
export default async function ListingDetailPage({
|
||
params,
|
||
}: {
|
||
params: { slug: string };
|
||
}) {
|
||
const session = await auth();
|
||
|
||
if (!session) {
|
||
redirect("/login");
|
||
}
|
||
|
||
const listing = await prisma.listing.findUnique({
|
||
where: { slug: params.slug },
|
||
include: {
|
||
images: true,
|
||
notes: true,
|
||
sleepingOptions: true,
|
||
tags: {
|
||
include: {
|
||
tag: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!listing) {
|
||
redirect("/listings");
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-slate-50 p-8">
|
||
<div className="container mx-auto max-w-6xl">
|
||
<Link
|
||
href="/listings"
|
||
className="text-blue-600 hover:underline mb-6 inline-block"
|
||
>
|
||
← Zurück zur Übersicht
|
||
</Link>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
{/* Main Content */}
|
||
<div className="lg:col-span-2 space-y-6">
|
||
{/* Images */}
|
||
<Card>
|
||
<CardContent className="p-0 overflow-hidden">
|
||
{listing.coverImage ? (
|
||
<img
|
||
src={listing.coverImage}
|
||
alt={listing.title}
|
||
className="w-full h-96 object-cover"
|
||
/>
|
||
) : (
|
||
<div className="w-full h-96 bg-slate-200 flex items-center justify-center">
|
||
Kein Bild
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Details */}
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<h2 className="text-xl font-semibold mb-4">📋 Details</h2>
|
||
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||
<div>
|
||
<p className="text-sm text-slate-500">Schlafzimmer</p>
|
||
<p className="text-xl font-bold">{listing.bedrooms || "—"}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-slate-500">Betten</p>
|
||
<p className="text-xl font-bold">{listing.beds || "—"}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-slate-500">Badezimmer</p>
|
||
<p className="text-xl font-bold">{listing.bathrooms || "—"}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-slate-500">Max Gäste</p>
|
||
<p className="text-xl font-bold">{listing.maxSleepingPlaces || "—"}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{listing.description && (
|
||
<div>
|
||
<h3 className="font-medium mb-2">Beschreibung</h3>
|
||
<p className="text-slate-600">{listing.description}</p>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Sleep Analysis */}
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<h2 className="text-xl font-semibold mb-4">🛏️ Schlafplatz-Analyse</h2>
|
||
|
||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||
<div className="p-4 bg-slate-50 rounded">
|
||
<p className="text-sm text-slate-500">4 Personen geeignet</p>
|
||
<p className={`text-2xl font-bold ${listing.suitableFor4 ? "text-green-600" : "text-red-500"}`}>
|
||
{listing.suitableFor4 ? "✅ Ja" : "❌ Nein"}
|
||
</p>
|
||
</div>
|
||
<div className="p-4 bg-slate-50 rounded">
|
||
<p className="text-sm text-slate-500">Extra Matratzen für 4</p>
|
||
<p className={`text-2xl font-bold ${listing.extraMattressesNeededFor4 === 0 ? "text-green-600" : "text-amber-600"}`}>
|
||
{listing.extraMattressesNeededFor4 ?? "—"}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{listing.sleepingOptions.length > 0 && (
|
||
<div>
|
||
<h3 className="font-medium mb-2">Schlafmöglichkeiten</h3>
|
||
<div className="space-y-2">
|
||
{listing.sleepingOptions.map((opt) => (
|
||
<div key={opt.id} className="flex justify-between p-2 bg-slate-50 rounded">
|
||
<span>{opt.label || opt.bedType}</span>
|
||
<span className="font-medium">
|
||
{opt.quantity}× ({opt.spotsPerUnit} Plätze)
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Notes */}
|
||
{listing.notes.length > 0 && (
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<h2 className="text-xl font-semibold mb-4">📝 Notizen</h2>
|
||
<div className="space-y-3">
|
||
{listing.notes.map((note) => (
|
||
<div key={note.id} className="p-3 bg-slate-50 rounded">
|
||
<p className="text-sm">{note.body}</p>
|
||
<p className="text-xs text-slate-400 mt-1">
|
||
{new Date(note.createdAt).toLocaleDateString("de-DE")}
|
||
</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
</div>
|
||
|
||
{/* Sidebar */}
|
||
<div className="space-y-6">
|
||
{/* Price Card */}
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<h1 className="text-2xl font-bold mb-2">{listing.title}</h1>
|
||
<p className="text-slate-500 mb-4">📍 {listing.locationText || "Kein Ort"}</p>
|
||
|
||
<div className="flex items-baseline gap-2 mb-4">
|
||
<span className="text-4xl font-bold">
|
||
€{listing.nightlyPrice?.toFixed(2) || "—"}
|
||
</span>
|
||
<span className="text-slate-500">/ Nacht</span>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2 mb-4">
|
||
<span className="text-xl">⭐</span>
|
||
<span className="font-semibold">{listing.rating?.toFixed(2) || "—"}</span>
|
||
{listing.reviewCount && (
|
||
<span className="text-slate-500">
|
||
({listing.reviewCount} Bewertungen)
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{listing.hostName && (
|
||
<p className="text-sm text-slate-500 mb-4">
|
||
👤 Host: {listing.hostName}
|
||
</p>
|
||
)}
|
||
|
||
<div className="flex flex-wrap gap-2 mb-4">
|
||
{listing.tags.map((lt) => (
|
||
<Badge
|
||
key={lt.tag.id}
|
||
style={{ backgroundColor: lt.tag.color || "#6366f1" }}
|
||
className="text-white"
|
||
>
|
||
{lt.tag.name}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
|
||
<a
|
||
href={listing.airbnbUrl}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="block w-full bg-red-500 text-white text-center py-3 rounded-lg hover:bg-red-600 transition"
|
||
>
|
||
🔗 Auf Airbnb ansehen
|
||
</a>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Status */}
|
||
<Card>
|
||
<CardContent className="p-6">
|
||
<h3 className="font-semibold mb-2">Status</h3>
|
||
<Badge
|
||
className={
|
||
listing.status === "SHORTLIST"
|
||
? "bg-green-500"
|
||
: listing.status === "REJECTED"
|
||
? "bg-red-500"
|
||
: "bg-slate-500"
|
||
}
|
||
>
|
||
{listing.status}
|
||
</Badge>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|