diff --git a/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx b/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx index a832786..845fdef 100644 --- a/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx +++ b/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx @@ -1,8 +1,9 @@ "use client"; import { useActionState, useEffect } from "react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Loader2, Trash2 } from "lucide-react"; +import { FileText, Loader2, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; @@ -115,15 +116,23 @@ function Row({ {isPending ? "Onay bekliyor" : isRejected ? "Reddedildi" : "Onaylandı"} - {isPending && ( -
- - -
- )} +
+ + {isPending && ( +
+ + +
+ )} +
); } diff --git a/src/app/(dashboard)/finance/payments/[paymentId]/receipt/components/receipt-controls.tsx b/src/app/(dashboard)/finance/payments/[paymentId]/receipt/components/receipt-controls.tsx new file mode 100644 index 0000000..290e568 --- /dev/null +++ b/src/app/(dashboard)/finance/payments/[paymentId]/receipt/components/receipt-controls.tsx @@ -0,0 +1,23 @@ +"use client"; + +import Link from "next/link"; +import { ArrowLeft, Printer } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +export function ReceiptControls() { + return ( +
+ + +
+ ); +} diff --git a/src/app/(dashboard)/finance/payments/[paymentId]/receipt/page.tsx b/src/app/(dashboard)/finance/payments/[paymentId]/receipt/page.tsx new file mode 100644 index 0000000..b196808 --- /dev/null +++ b/src/app/(dashboard)/finance/payments/[paymentId]/receipt/page.tsx @@ -0,0 +1,186 @@ +import { notFound, redirect } from "next/navigation"; +import { Query } from "node-appwrite"; + +import { ReceiptControls } from "./components/receipt-controls"; +import { + DATABASE_ID, + TABLES, + type Payment, + type TenantSettings, +} from "@/lib/appwrite/schema"; +import { createAdminClient } from "@/lib/appwrite/server"; +import { PAYMENT_METHOD_LABELS } from "@/lib/appwrite/payment-types"; +import { requireTenant } from "@/lib/appwrite/tenant-guard"; + +export const metadata = { + title: "DLS — Makbuz", +}; + +const dateFormatter = new Intl.DateTimeFormat("tr-TR", { + day: "2-digit", + month: "long", + year: "numeric", +}); + +function formatMoney(amount: number, currency: string): string { + try { + return new Intl.NumberFormat("tr-TR", { style: "currency", currency }).format(amount); + } catch { + return `${amount.toFixed(2)} ${currency}`; + } +} + +async function loadTenantSettings(tenantId: string): Promise { + const { tablesDB } = createAdminClient(); + try { + const result = await tablesDB.listRows({ + databaseId: DATABASE_ID, + tableId: TABLES.tenantSettings, + queries: [Query.equal("tenantId", tenantId), Query.limit(1)], + }); + return (result.rows[0] as unknown as TenantSettings) ?? null; + } catch { + return null; + } +} + +export default async function PaymentReceiptPage({ + params, +}: { + params: Promise<{ paymentId: string }>; +}) { + const { paymentId } = await params; + + let ctx; + try { + ctx = await requireTenant(); + } catch { + redirect("/onboarding"); + } + + const { tablesDB } = createAdminClient(); + let payment: Payment; + try { + payment = (await tablesDB.getRow( + DATABASE_ID, + TABLES.payments, + paymentId, + )) as unknown as Payment; + } catch { + notFound(); + } + + // Only the two parties can see the receipt. + if ( + payment.tenantId !== ctx.tenantId && + payment.counterpartTenantId !== ctx.tenantId + ) { + notFound(); + } + + // 'inflow' means the lab received money from the clinic. + // From the row alone we know whose tenantId is which side because the lab + // always issues inflow and the clinic outflow. Resolve them so the + // receipt header reads naturally regardless of who recorded the row. + const labId = payment.direction === "inflow" ? payment.tenantId : payment.counterpartTenantId; + const clinicId = payment.direction === "inflow" ? payment.counterpartTenantId : payment.tenantId; + const [lab, clinic] = await Promise.all([ + loadTenantSettings(labId), + loadTenantSettings(clinicId), + ]); + + const statusLabel = + payment.status === "confirmed" + ? "Onaylı" + : payment.status === "pending" + ? "Onay bekliyor" + : payment.status === "rejected" + ? "Reddedildi" + : "—"; + + return ( +
+
+ +
+
+

+ Tahsilat Makbuzu +

+

+ {lab?.companyName ?? "Laboratuvar"} +

+ {lab?.companyTaxId && ( +

VKN: {lab.companyTaxId}

+ )} + {lab?.companyAddress && ( +

+ {lab.companyAddress} +

+ )} +
+ +
+
+

+ Tahsil edilen +

+

{clinic?.companyName ?? "Klinik"}

+ {clinic?.companyTaxId && ( +

VKN: {clinic.companyTaxId}

+ )} +
+
+

+ Ödeme tarihi +

+

+ {dateFormatter.format(new Date(payment.paymentDate))} +

+
+
+ +
+

+ Tutar +

+

+ {formatMoney(payment.amount, payment.currency)} +

+
+ +
+
+

+ Ödeme yöntemi +

+

+ {payment.method + ? (PAYMENT_METHOD_LABELS[payment.method] ?? payment.method) + : "—"} +

+
+
+

+ Durum +

+

{statusLabel}

+
+ {payment.notes && ( +
+

+ Not +

+

{payment.notes}

+
+ )} +
+ + +
+
+
+ ); +}