chore: initial project import
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
This commit is contained in:
115
app/super-admin/billing/invoices/[invoiceId]/page.tsx
Normal file
115
app/super-admin/billing/invoices/[invoiceId]/page.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
import Link from "next/link";
|
||||
import { ShellPage } from "@/components/page-templates";
|
||||
import { Badge, SectionCard } from "@/components/ui";
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
function formatDate(value: Date | null) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("id-ID", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit"
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function formatMoney(value: number) {
|
||||
return new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: "IDR",
|
||||
maximumFractionDigits: 0
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function statusTone(status: string) {
|
||||
if (status === "PAID") {
|
||||
return "success";
|
||||
}
|
||||
|
||||
if (status === "OVERDUE") {
|
||||
return "danger";
|
||||
}
|
||||
|
||||
return "warning";
|
||||
}
|
||||
|
||||
export default async function SuperAdminInvoiceDetailPage({
|
||||
params
|
||||
}: {
|
||||
params: Promise<{ invoiceId: string }>;
|
||||
}) {
|
||||
const session = await getSession();
|
||||
if (!session || session.role !== "super_admin") {
|
||||
redirect("/unauthorized");
|
||||
}
|
||||
|
||||
const { invoiceId } = await params;
|
||||
const invoice = await prisma.billingInvoice.findUnique({
|
||||
where: { id: invoiceId },
|
||||
include: {
|
||||
tenant: { select: { name: true, slug: true } },
|
||||
plan: { select: { name: true, code: true } }
|
||||
}
|
||||
});
|
||||
|
||||
if (!invoice) {
|
||||
redirect("/super-admin/billing/invoices?error=invoice_not_found");
|
||||
}
|
||||
|
||||
return (
|
||||
<ShellPage shell="super-admin" title="Invoice Detail" description="Invoice view untuk super admin.">
|
||||
<div className="grid gap-6 xl:grid-cols-2">
|
||||
<SectionCard title="Summary">
|
||||
<div className="space-y-2 text-sm text-on-surface-variant">
|
||||
<p>
|
||||
<strong className="text-on-surface">Invoice:</strong> {invoice.invoiceNumber}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Tenant:</strong>{" "}
|
||||
<Link href={`/super-admin/tenants/${invoice.tenantId}`} className="text-brand hover:underline">
|
||||
{invoice.tenant.name} ({invoice.tenant.slug})
|
||||
</Link>
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Plan:</strong> {invoice.plan.name} ({invoice.plan.code})
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Period:</strong> {formatDate(invoice.periodStart)} - {formatDate(invoice.periodEnd)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Total amount:</strong> {formatMoney(invoice.totalAmount)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Subtotal:</strong> {formatMoney(invoice.subtotal)} | Tax: {formatMoney(invoice.taxAmount)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Status:</strong> <Badge tone={statusTone(invoice.paymentStatus)}>{invoice.paymentStatus}</Badge>
|
||||
</p>
|
||||
</div>
|
||||
</SectionCard>
|
||||
<SectionCard title="Timeline">
|
||||
<div className="space-y-2 text-sm text-on-surface-variant">
|
||||
<p>
|
||||
<strong className="text-on-surface">Issued:</strong> {formatDate(invoice.createdAt)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Due date:</strong> {formatDate(invoice.dueDate)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Paid at:</strong> {formatDate(invoice.paidAt)}
|
||||
</p>
|
||||
<p>
|
||||
<strong className="text-on-surface">Updated:</strong> {formatDate(invoice.updatedAt)}
|
||||
</p>
|
||||
</div>
|
||||
</SectionCard>
|
||||
</div>
|
||||
</ShellPage>
|
||||
);
|
||||
}
|
||||
60
app/super-admin/billing/invoices/page.tsx
Normal file
60
app/super-admin/billing/invoices/page.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { getSession } from "@/lib/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { ShellPage } from "@/components/page-templates";
|
||||
import { TablePlaceholder } from "@/components/placeholders";
|
||||
import Link from "next/link";
|
||||
|
||||
function formatDate(value: Date | null) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("id-ID", {
|
||||
month: "short",
|
||||
year: "numeric"
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
function formatMoney(value: number) {
|
||||
return new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: "IDR",
|
||||
maximumFractionDigits: 0
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
export default async function SuperAdminInvoicesPage() {
|
||||
const session = await getSession();
|
||||
if (!session || session.role !== "super_admin") {
|
||||
redirect("/unauthorized");
|
||||
}
|
||||
|
||||
const invoices = await prisma.billingInvoice.findMany({
|
||||
include: { tenant: true, plan: true },
|
||||
orderBy: { createdAt: "desc" }
|
||||
});
|
||||
|
||||
return (
|
||||
<ShellPage shell="super-admin" title="Invoices" description="Invoice seluruh tenant.">
|
||||
<TablePlaceholder
|
||||
title="Invoices"
|
||||
columns={["Invoice", "Tenant", "Period", "Amount", "Status"]}
|
||||
rows={invoices.map((invoice) => [
|
||||
<Link
|
||||
key={`${invoice.id}-invoice`}
|
||||
href={`/super-admin/billing/invoices/${invoice.id}`}
|
||||
className="text-brand hover:underline"
|
||||
>
|
||||
{invoice.invoiceNumber}
|
||||
</Link>,
|
||||
invoice.tenant.name,
|
||||
`${formatDate(invoice.periodStart)} - ${formatDate(invoice.periodEnd)} (${invoice.plan.name})`,
|
||||
formatMoney(invoice.totalAmount),
|
||||
invoice.paymentStatus
|
||||
])}
|
||||
/>
|
||||
</ShellPage>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user