import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { isAdminEmail } from "@/lib/admin";
import { emailRepo } from "@/server/repositories/email.repo";
import {
RetryEmailButton,
ResendEmailButton,
} from "@/features/email/components/email-row-actions";
type Tab = "failed" | "queue" | "sent";
const TABS: { key: Tab; label: string }[] = [
{ key: "failed", label: "Gagal" },
{ key: "queue", label: "Antrian" },
{ key: "sent", label: "Terkirim" },
];
interface PageProps {
searchParams: Promise<{ tab?: string; to?: string; template?: string }>;
}
export default async function AdminEmailsPage({ searchParams }: PageProps) {
const session = await getServerSession(authOptions);
if (!session?.user) redirect("/login?callbackUrl=/admin/emails");
if (!isAdminEmail(session.user.email)) {
return (
Halaman ini hanya untuk admin SeTrip.
);
}
const params = await searchParams;
const tab: Tab = TABS.some((t) => t.key === params.tab)
? (params.tab as Tab)
: "failed";
const filters = {
to: params.to?.trim() || undefined,
template: params.template?.trim() || undefined,
};
const stats = await emailRepo.stats();
const jobs =
tab === "sent"
? []
: await emailRepo.listJobs(
tab === "failed" ? ["FAILED"] : ["PENDING", "PROCESSING"],
filters
);
const sent = tab === "sent" ? await emailRepo.listSent(filters) : [];
return (
{/* Kartu ringkasan */}
0 ? "amber" : "ok"}
hint="Job menunggu cron / retry"
/>
0 ? "red" : "ok"}
hint="Job gagal dalam sehari terakhir"
/>
0 ? "red" : "ok"}
hint="Gagal & habis 5 attempt — cron berhenti retry"
/>
{/* Tabs */}
{/* Filter */}
{tab === "sent" ? (
) : (
)}
);
}
function StatCard({
label,
value,
tone,
hint,
}: {
label: string;
value: number;
tone: "ok" | "amber" | "red";
hint: string;
}) {
const cls =
tone === "red"
? "border-red-200 bg-red-50/60"
: tone === "amber"
? "border-amber-200 bg-amber-50/60"
: "border-emerald-200 bg-emerald-50/50";
const valueCls =
tone === "red"
? "text-red-700"
: tone === "amber"
? "text-amber-700"
: "text-emerald-700";
return (
);
}
function JobTable({
rows,
tab,
}: {
rows: Awaited>;
tab: "failed" | "queue";
}) {
if (rows.length === 0) {
return (
);
}
return (
| Penerima |
Template |
Status |
Attempt |
{tab === "failed" ? "Error terakhir" : "Dijadwalkan"}
|
Aksi |
{rows.map((r) => (
| {r.to} |
{r.template} |
|
{r.attempts}
{r.attempts >= 5 && (
(mati)
)}
|
{tab === "failed"
? r.lastError
? truncate(r.lastError, 90)
: "—"
: formatDateTime(r.scheduledAt)}
|
|
))}
);
}
function SentTable({
rows,
}: {
rows: Awaited>;
}) {
if (rows.length === 0) {
return ;
}
return (
| Penerima |
Template |
Subject |
Terkirim |
Aksi |
{rows.map((r) => (
| {r.to} |
{r.template} |
{truncate(r.subject, 60)} |
{formatDateTime(r.sentAt)}
|
|
))}
);
}
function EmptyState({ message }: { message: string }) {
return (
);
}
function EmailBadge({ value }: { value: string }) {
const cls =
value === "SUCCESS"
? "bg-emerald-100 text-emerald-800"
: value === "FAILED"
? "bg-red-100 text-red-800"
: "bg-amber-100 text-amber-800";
return (
{value}
);
}
function formatDateTime(d: Date): string {
return d.toLocaleString("id-ID", {
day: "2-digit",
month: "short",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}
function truncate(s: string, max: number): string {
return s.length > max ? `${s.slice(0, max)}…` : s;
}