import { redirect } from "next/navigation"; import Link from "next/link"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { organizerRepo } from "@/server/repositories/organizer.repo"; import { refundRepo } from "@/server/repositories/refund.repo"; import { payoutRepo } from "@/server/repositories/payout.repo"; const REFUND_REASON_LABEL: Record = { USER_CANCELLATION: "Peserta cancel", ORGANIZER_CANCELLED: "Organizer cancel", TRIP_ISSUE: "Masalah trip", ADMIN_ADJUSTMENT: "Penyesuaian admin", DISPUTE_RESOLVED: "Dispute selesai", OTHER: "Lainnya", }; function formatIDR(n: number) { return `Rp${n.toLocaleString("id-ID")}`; } function timeAgo(d: Date) { const diff = Date.now() - new Date(d).getTime(); const h = Math.floor(diff / 3600000); if (h < 1) return `${Math.max(1, Math.floor(diff / 60000))} mnt lalu`; if (h < 24) return `${h} jam lalu`; const days = Math.floor(h / 24); return `${days} hari lalu`; } export default async function AdminDashboardPage() { const session = await getServerSession(authOptions); if (!session?.user) redirect("/login?callbackUrl=/admin"); if (!session.user.isAdmin) { return (

Halaman ini hanya untuk admin SeTrip.

); } const [ pendingVerif, approvedVerif, rejectedVerif, pendingRefund, approvedRefund, succeededRefund, heldPayout, releasedPayout, paidPayout, recentPendingVerif, recentPendingRefund, recentApprovedRefund, recentReleasedPayout, ] = await Promise.all([ organizerRepo.countByStatus("PENDING"), organizerRepo.countByStatus("APPROVED"), organizerRepo.countByStatus("REJECTED"), refundRepo.countByStatus("PENDING"), refundRepo.countByStatus("APPROVED"), refundRepo.countByStatus("SUCCEEDED"), payoutRepo.countByStatus("HELD"), payoutRepo.countByStatus("RELEASED"), payoutRepo.countByStatus("PAID"), organizerRepo.listRecent("PENDING", 3), refundRepo.listRecent("PENDING", 3), refundRepo.listRecent("APPROVED", 3), payoutRepo.listRecent("RELEASED", 3), ]); const stats: Array<{ label: string; value: number; hint: string; href: string; accent: "amber" | "blue" | "primary"; }> = [ { label: "Verifikasi menunggu", value: pendingVerif, hint: "KYC organizer perlu ditinjau", href: "/admin/verifications?tab=PENDING", accent: "amber", }, { label: "Refund baru", value: pendingRefund, hint: "Perlu disetujui / ditolak", href: "/admin/refunds?tab=PENDING", accent: "amber", }, { label: "Refund siap transfer", value: approvedRefund, hint: "Refund APPROVED — transfer ke peserta lalu mark SUCCEEDED", href: "/admin/refunds?tab=APPROVED", accent: "blue", }, { label: "Payout siap transfer", value: releasedPayout, hint: "Escrow lepas — transfer ke organizer", href: "/admin/payouts?tab=RELEASED", accent: "blue", }, ]; const accentClasses: Record = { amber: "bg-amber-50 text-amber-900 ring-amber-200", blue: "bg-blue-50 text-blue-900 ring-blue-200", primary: "bg-primary-50 text-primary-900 ring-primary-200", }; const totalAttention = pendingVerif + pendingRefund + approvedRefund + releasedPayout; return (

Admin

Dashboard

Halo {session.user.name}.{" "} {totalAttention > 0 ? ( <> Ada {totalAttention}{" "} hal yang menunggu tindakan kamu. ) : ( <>Tidak ada antrian pending — semua sudah beres ✨ )}

{/* Stats row */}
{stats.map((s) => ( 0 ? "ring-neutral-200" : "ring-neutral-100" }`} >
{s.label} Buka →

{s.value}

{s.hint}

))}
{/* Pending Verifikasi */}

Verifikasi Organizer

{approvedVerif} disetujui · {rejectedVerif} ditolak ·{" "} {pendingVerif} menunggu

Tinjau pending ({pendingVerif})
{recentPendingVerif.length === 0 ? (

Tidak ada pengajuan menunggu review.

) : (
    {recentPendingVerif.map((v) => (
  • {v.fullName}

    {v.user.name} · {v.user.email} · {timeAgo(v.createdAt)}

    Buka
  • ))}
)}
{/* Refund — Pending & Siap Transfer */}

Refund

{succeededRefund} selesai · {approvedRefund} siap transfer ·{" "} {pendingRefund} baru

Tinjau refund

Pending ({pendingRefund})

{recentPendingRefund.length === 0 ? (

Tidak ada refund baru.

) : (
    {recentPendingRefund.map((r) => (
  • {formatIDR(r.amount)} ·{" "} {REFUND_REASON_LABEL[r.reason] ?? r.reason}

    {r.booking.user.name} · {r.booking.trip.title} ·{" "} {timeAgo(r.createdAt)}

  • ))}
)}

Siap transfer ({approvedRefund})

{recentApprovedRefund.length === 0 ? (

Tidak ada refund siap transfer.

) : (
    {recentApprovedRefund.map((r) => (
  • {formatIDR(r.amount)} ·{" "} {REFUND_REASON_LABEL[r.reason] ?? r.reason}

    {r.booking.user.name} · {r.booking.trip.title} ·{" "} {timeAgo(r.createdAt)}

  • ))}
)}
{/* Payout — escrow ke organizer */}

Payout Organizer (Escrow)

{paidPayout} dibayar · {releasedPayout} siap transfer ·{" "} {heldPayout} ditahan

Transfer payout ({releasedPayout})
{recentReleasedPayout.length === 0 ? (

Tidak ada payout siap transfer.

) : (
    {recentReleasedPayout.map((p) => (
  • {formatIDR(p.amount)} · {p.organizer.name}

    {p.trip.title} ·{" "} {p.releasedAt ? `release ${timeAgo(p.releasedAt)}` : `hold sampai ${new Date(p.heldUntil).toLocaleDateString("id-ID")}`}

    Buka
  • ))}
)}

Refund APPROVED: admin transfer manual ke peserta lalu tandai SUCCEEDED.

Payout RELEASED: escrow dilepas karena trip sudah selesai + 3 hari. Admin transfer ke organizer lalu tandai PAID.

); }