From 503a98fcb36305d9edfc3e93bce994b006658a03 Mon Sep 17 00:00:00 2001 From: kovakmedya Date: Fri, 22 May 2026 16:06:06 +0300 Subject: [PATCH] feat(finance): clinic sees its own pending / rejected payments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clinics that record a payment now get visibility on what happened to it. Previously the row went into limbo — clinic clicked 'Ödeme Yap', balance didn't move (lab approval pending), and the clinic had no in-app place to confirm the submission landed. - /finance clinic-side now renders a new card 'Gönderdiğim Ödemeler' listing payments where tenantId == self AND status in (pending, rejected). Confirmed rows drop out (they're already reflected in the balance above). - Each row shows counterpart, amount, date, method, note plus a status badge: amber 'Onay bekliyor' or destructive 'Reddedildi'. - Pending rows expose a 'Geri al' button — fires deletePaymentAction so a clinic can withdraw a submission it sent in error before the lab acts on it. Rejected rows stay read-only for audit. - Card is hidden when the list is empty so the page stays tidy. --- .../components/my-pending-payments-card.tsx | 129 ++++++++++++++++++ src/app/(dashboard)/finance/page.tsx | 18 +++ 2 files changed, 147 insertions(+) create mode 100644 src/app/(dashboard)/finance/components/my-pending-payments-card.tsx diff --git a/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx b/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx new file mode 100644 index 0000000..a832786 --- /dev/null +++ b/src/app/(dashboard)/finance/components/my-pending-payments-card.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useActionState, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { Loader2, Trash2 } from "lucide-react"; +import { toast } from "sonner"; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { deletePaymentAction } from "@/lib/appwrite/payment-actions"; +import { + PAYMENT_METHOD_LABELS, + initialPaymentActionState, +} from "@/lib/appwrite/payment-types"; +import type { Payment } from "@/lib/appwrite/schema"; + +const dateFormatter = new Intl.DateTimeFormat("tr-TR", { + day: "2-digit", + month: "2-digit", + 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}`; + } +} + +export function MyPendingPaymentsCard({ + rows, + counterpartNames, +}: { + rows: Payment[]; + counterpartNames: Record; +}) { + if (rows.length === 0) return null; + return ( + + + Gönderdiğim Ödemeler + + Laboratuvar onayı bekleyen veya reddedilen bildirimleriniz. Onaylanan + ödemeler artık burada gözükmez — açık bakiyenize işlenir. + + + +
    + {rows.map((p) => ( + + ))} +
+
+
+ ); +} + +function Row({ + payment, + counterpartName, +}: { + payment: Payment; + counterpartName: string; +}) { + const router = useRouter(); + const [state, action, pending] = useActionState( + deletePaymentAction, + initialPaymentActionState, + ); + + useEffect(() => { + if (state.ok) { + toast.success("Bildirim silindi."); + router.refresh(); + } else if (state.error) { + toast.error(state.error); + } + }, [state, router]); + + const isPending = payment.status === "pending"; + const isRejected = payment.status === "rejected"; + + return ( +
  • +
    +

    {counterpartName}

    +

    + {dateFormatter.format(new Date(payment.paymentDate))} + {payment.method && ( + <> · {PAYMENT_METHOD_LABELS[payment.method] ?? payment.method} + )} + {payment.notes && <> · {payment.notes}} +

    +
    +
    +

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

    + + {isPending ? "Onay bekliyor" : isRejected ? "Reddedildi" : "Onaylandı"} + +
    + {isPending && ( +
    + + +
    + )} +
  • + ); +} diff --git a/src/app/(dashboard)/finance/page.tsx b/src/app/(dashboard)/finance/page.tsx index 38072df..ae2f1bc 100644 --- a/src/app/(dashboard)/finance/page.tsx +++ b/src/app/(dashboard)/finance/page.tsx @@ -12,6 +12,7 @@ import { import { requireTenant } from "@/lib/appwrite/tenant-guard"; import { BalancesCard } from "./components/balances-card"; import { FinanceTable } from "./components/finance-table"; +import { MyPendingPaymentsCard } from "./components/my-pending-payments-card"; import { PendingPaymentsCard } from "./components/pending-payments-card"; export const metadata = { @@ -61,6 +62,16 @@ export default async function FinancePage() { payments, }); const pendingForApproval = filterPendingForConfirmation(payments, ctx.tenantId, kind); + // Clinic-side: payments this clinic submitted that are either still waiting + // for the lab to confirm, or were rejected. Both shapes are useful so the + // clinic can chase the lab or fix a wrong submission. + const myPendingOrRejected = payments + .filter( + (p) => + p.tenantId === ctx.tenantId && + (p.status === "pending" || p.status === "rejected"), + ) + .sort((a, b) => (a.paymentDate < b.paymentDate ? 1 : -1)); const counterpartNames: Record = {}; for (const c of connections) { @@ -117,6 +128,13 @@ export default async function FinancePage() { + {!isLab && ( + + )} +