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) => (
{item.startTime}
{item.endTime ? `โ${item.endTime}` : ""}
{item.activity}
))}
))}
)}
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 (
);
}