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}
+
+ )}
+
+
+
+
+
+
+ );
+}