From 121fbdba9d34f8c6bb2faf99ab54ac18eb36ff88 Mon Sep 17 00:00:00 2001 From: kovakmedya Date: Thu, 30 Apr 2026 07:36:31 +0300 Subject: [PATCH] feat(banking C): credit cards + monthly statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Final banking step. User-managed: each month, paste your bank statement totals into the form. We don't auto-compute interest — bank's number is authoritative. Schema: - credit_cards: bankName, cardName, last4, creditLimit, statementDay, dueDay, interestRate (monthly nominal %), bankAccountId (optional FK, payments expense from this account), archived, notes. - credit_card_statements: cardId, period (YYYY-MM), statementDate, dueDate, totalDebt, minimumPayment, paidAmount, status (pending/partial/paid/overdue), financeEntryId (link to last payment). - Indexes on (tenantId, archived) for cards and (tenantId, cardId) + (tenantId, status, dueDate) for statements. Server (lib/appwrite/credit-card-actions.ts): - create/update/archive(toggle)/delete for cards. Card delete cascades through statements + their finance_entries. - createStatementAction: computes status from dueDate + paidAmount. - payStatementAction: partial-payment friendly. Creates a single finance_entry expense for the pay amount, capped at remaining balance, bankAccountId carried from the card. Recomputes status: paid (full), partial (some + pre-due), overdue (past due with anything < total). - deleteStatementAction: removes linked finance_entry too. - All audit-logged. UI (/finance/cards): - 3 stat cards: aktif kart sayısı, bekleyen toplam, vadesi geçmiş ekstre. - Per-card panel: bank/name/last4, limit + statement/due day + monthly interest, current outstanding. Statement table inside card with status badges, Öde button (opens partial-payment dialog), delete button. - CardFormSheet for card CRUD, StatementFormSheet for statement creation with default period/dates derived from card's statementDay/dueDay. Sidebar Finans submenu now functional: Banka hesapları → /finance/banks, Krediler → /finance/loans, Kredi kartları → /finance/cards. --- .../cards/components/card-form-sheet.tsx | 225 ++++++++ .../finance/cards/components/cards-client.tsx | 522 ++++++++++++++++++ .../cards/components/statement-form-sheet.tsx | 197 +++++++ .../finance/cards/components/types.ts | 43 ++ src/app/(dashboard)/finance/cards/page.tsx | 80 +++ src/lib/appwrite/credit-card-actions.ts | 489 ++++++++++++++++ src/lib/appwrite/credit-card-queries.ts | 49 ++ src/lib/appwrite/credit-card-types.ts | 7 + src/lib/appwrite/schema.ts | 34 ++ src/lib/validation/credit-cards.ts | 73 +++ 10 files changed, 1719 insertions(+) create mode 100644 src/app/(dashboard)/finance/cards/components/card-form-sheet.tsx create mode 100644 src/app/(dashboard)/finance/cards/components/cards-client.tsx create mode 100644 src/app/(dashboard)/finance/cards/components/statement-form-sheet.tsx create mode 100644 src/app/(dashboard)/finance/cards/components/types.ts create mode 100644 src/app/(dashboard)/finance/cards/page.tsx create mode 100644 src/lib/appwrite/credit-card-actions.ts create mode 100644 src/lib/appwrite/credit-card-queries.ts create mode 100644 src/lib/appwrite/credit-card-types.ts create mode 100644 src/lib/validation/credit-cards.ts diff --git a/src/app/(dashboard)/finance/cards/components/card-form-sheet.tsx b/src/app/(dashboard)/finance/cards/components/card-form-sheet.tsx new file mode 100644 index 0000000..7b52839 --- /dev/null +++ b/src/app/(dashboard)/finance/cards/components/card-form-sheet.tsx @@ -0,0 +1,225 @@ +"use client"; + +import { useActionState, useEffect } from "react"; +import { Loader2, Save } from "lucide-react"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { Textarea } from "@/components/ui/textarea"; +import { + createCreditCardAction, + updateCreditCardAction, +} from "@/lib/appwrite/credit-card-actions"; +import { initialCreditCardState } from "@/lib/appwrite/credit-card-types"; + +import type { BankAccountOption, CreditCardRow } from "./types"; + +const NONE = "__none__"; + +export function CardFormSheet({ + open, + onOpenChange, + card, + bankAccounts, +}: { + open: boolean; + onOpenChange: (v: boolean) => void; + card?: CreditCardRow | null; + bankAccounts: BankAccountOption[]; +}) { + const isEdit = Boolean(card); + const action = isEdit ? updateCreditCardAction : createCreditCardAction; + const [state, formAction, isPending] = useActionState(action, initialCreditCardState); + + useEffect(() => { + if (state.ok) { + toast.success(isEdit ? "Kart güncellendi." : "Kart eklendi."); + onOpenChange(false); + } else if (state.error) { + toast.error(state.error); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state]); + + return ( + + + + {isEdit ? "Kartı düzenle" : "Yeni kredi kartı"} + + Hesap kesim ve son ödeme günleri her ay otomatik kullanılır. Ekstreler kart başına manuel girilir. + + + +
{ + if (fd.get("bankAccountId") === NONE) fd.set("bankAccountId", ""); + formAction(fd); + }} + className="flex flex-1 flex-col" + > + {isEdit && card && } + +
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +

+ Ekstre ödemeleri seçilen hesaba expense olarak yazılır. +

+
+ +
+ +