import { redirect } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import type { Prisma } from "@/generated/prisma/client"; import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type TransactionRow, TransactionsTable } from "./transactions-table"; const PAID_STATUS = ["completed", "paid", "success", "approved"] as const; const FAILED_STATUS = [ "failed", "denied", "refunded", "cancelled", "canceled", ] as const; const PENDING_STATUS = ["pending", "created", "processing"] as const; function statusKind( status: string | null | undefined, ): TransactionRow["statusKind"] { if (!status) return "neutral"; const s = status.toLowerCase(); if ((PAID_STATUS as readonly string[]).includes(s)) return "ok"; if ((FAILED_STATUS as readonly string[]).includes(s)) return "danger"; return "neutral"; } function formatAmount(amount: number, currency: string): string { const n = Number(amount); const value = Number.isFinite(n) ? n.toFixed(2) : "0.00"; return `${value} ${currency}`; } const SORT_MAP: Record< string, Prisma.WebsitePaypalTransactionsOrderByWithRelationInput > = { id: { id: "desc" }, amount: { amount: "desc" }, date: { createdAt: "desc" }, status: { status: "desc" }, package: { description: "desc" }, payer: { userId: "desc" }, }; export default async function AdminTransactions({ searchParams, }: { searchParams: Promise>; }) { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) { redirect("/admin"); } const t = await getTranslations("pages.admin.transactions"); const raw = await searchParams; const parsed = parseListParams(new URLSearchParams(raw)); const statusFilter = raw.filter_status ?? ""; const where: Prisma.WebsitePaypalTransactionsWhereInput = {}; if (statusFilter === "paid") { where.status = { in: [...PAID_STATUS] }; } else if (statusFilter === "failed") { where.status = { in: [...FAILED_STATUS] }; } else if (statusFilter === "pending") { where.status = { in: [...PENDING_STATUS] }; } if (parsed.search.trim()) { const q = parsed.search.trim(); const asId = Number(q); let userIds: number[] = []; try { const users = await prisma.user.findMany({ where: { username: { contains: q } }, select: { id: true }, take: 50, }); userIds = users.map((u) => u.id); } catch { userIds = []; } where.AND = [ ...(where.status ? [{ status: where.status }] : []), { OR: [ { transactionId: { contains: q } }, { description: { contains: q } }, ...(Number.isFinite(asId) && asId > 0 ? [{ userId: asId }] : []), ...(userIds.length ? [{ userId: { in: userIds } }] : []), ], }, ]; delete where.status; } let total = 0; try { total = await prisma.websitePaypalTransactions.count({ where }); } catch { total = 0; } const pagination = calcPagination(total, parsed.page, parsed.perPage); const sortKey = parsed.sort ?? "id"; const baseOrder = SORT_MAP[sortKey] ?? { id: "desc" as const }; const orderField = Object.keys(baseOrder)[0] as keyof typeof baseOrder; const orderBy = { [orderField]: parsed.order, } as Prisma.WebsitePaypalTransactionsOrderByWithRelationInput; let transactions: Awaited< ReturnType > = []; try { transactions = await prisma.websitePaypalTransactions.findMany({ where, orderBy, skip: pagination.offset, take: pagination.perPage, }); } catch { transactions = []; } const userIds = [...new Set(transactions.map((x) => x.userId))]; let names = new Map(); if (userIds.length > 0) { try { const users = await prisma.user.findMany({ where: { id: { in: userIds } }, select: { id: true, username: true }, }); names = new Map(users.map((u) => [u.id, u.username])); } catch { names = new Map(); } } const rows: TransactionRow[] = transactions.map((x) => ({ id: Number(x.id), payer: names.get(x.userId) ?? `#${x.userId}`, payerId: x.userId, amount: formatAmount(x.amount, x.currency), package: x.description ?? "—", status: x.status ?? "", statusKind: statusKind(x.status), transactionId: x.transactionId, date: formatDate(x.createdAt), })); const pagePaid = rows.filter((r) => r.statusKind === "ok").length; const pageFailed = rows.filter((r) => r.statusKind === "danger").length; return (
0 ? "ok" : "neutral"} icon="💳" /> 0 ? "danger" : "neutral"} icon="⚠️" />

{t("title")}

{total === 0 && !parsed.search && !statusFilter ? (
{t("noTransactions")}
) : ( )}
); }