import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { isAdminEmail } from "@/lib/admin"; import { tripService } from "@/server/services/trip.service"; import { formatRupiah } from "@/lib/utils"; import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates"; import { categoryMeta } from "@/lib/activity-category"; import { groupItineraryByDay } from "@/lib/itinerary"; import { AdminCancelTripButton } from "@/features/trip/components/admin-cancel-trip-button"; interface PageProps { params: Promise<{ id: string }>; } export default async function AdminTripDetailPage({ params }: PageProps) { const session = await getServerSession(authOptions); if (!session?.user) { redirect("/login?callbackUrl=/admin/trips"); } if (!isAdminEmail(session.user.email)) { return (

Halaman ini hanya untuk admin SeTrip.

); } const { id } = await params; let trip; try { trip = await tripService.getTripById(id); } catch { notFound(); } const cat = categoryMeta(trip.category); const activeParticipants = trip.participants.filter( (p) => p.status !== "CANCELLED" ); const confirmedCount = activeParticipants.filter( (p) => p.status === "CONFIRMED" ).length; const pendingCount = activeParticipants.filter( (p) => p.status === "PENDING" ).length; const cancelledCount = trip.participants.length - activeParticipants.length; const grouped = trip.itineraryItems.length ? groupItineraryByDay( trip.itineraryItems.map((i) => ({ day: i.day, startTime: i.startTime, endTime: i.endTime, activity: i.activity, order: i.order, })) ) : null; const canCancel = trip.status === "OPEN" || trip.status === "FULL"; return (
โ† Kembali ke list trips
{cat.icon} {cat.label}

{trip.title}

๐Ÿ“… {formatTripCalendarDateRangeLong(trip.date, trip.endDate)} ยท ๐Ÿ“ {trip.destination}, {trip.location}

Organizer:{" "} {trip.organizer.name} {" "} ({trip.organizer.email})

Trip ID:{" "} {trip.id}

Harga

{formatRupiah(trip.price)}

per orang

{canCancel && (

Intervensi Admin โ€” Cancel Trip

Pakai hanya saat organizer unreachable, safety issue, atau dispute tidak terselesaikan. Semua booking PAID akan auto-refund (full amount). Booking PENDING/AWAITING_PAY langsung CANCELLED.

)} {trip.description && (

Deskripsi

{trip.description}

)} {grouped && (

Itinerary

{[...grouped.entries()].map(([day, items]) => (

Hari {day}

    {items.map((item) => (
  1. {item.startTime} {item.endTime ? `โ€“${item.endTime}` : ""} {item.activity}
  2. ))}
))}
)}

Peserta ({activeParticipants.length})

{trip.participants.length === 0 ? (

Belum ada peserta.

) : ( )}
); } function StatusBadge({ status }: { status: string }) { const cls = status === "OPEN" ? "bg-emerald-100 text-emerald-800" : status === "FULL" ? "bg-amber-100 text-amber-800" : status === "CLOSED" ? "bg-red-100 text-red-800" : "bg-neutral-200 text-neutral-700"; return ( {status} ); } function ParticipantStatusBadge({ status }: { status: string }) { const cls = status === "CONFIRMED" ? "bg-emerald-100 text-emerald-800" : status === "PENDING" ? "bg-amber-100 text-amber-800" : "bg-neutral-200 text-neutral-600 line-through"; return ( {status} ); } function StatCard({ label, value, accent = "primary", }: { label: string; value: string; accent?: "primary" | "emerald" | "amber" | "neutral"; }) { const map = { primary: "text-primary-700", emerald: "text-emerald-700", amber: "text-amber-700", neutral: "text-neutral-700", }; return (

{label}

{value}

); }