"use client"; import { createFinanceTransactionRecord, deleteFinanceTransactionRecord, updateFinanceTransactionRecord, } from "@/app/(dashboard)/finance/actions"; import { Badge, Button, Card, CardContent, Input, Label, Textarea } from "poyraz-ui/atoms"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "poyraz-ui/molecules"; import { ArrowDownRight, ArrowUpRight, ChevronLeft, ChevronRight, Pencil, Plus, Trash2, Wallet, Brain, Loader2, } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import { StatCard } from "@/components/system/stat-card"; export type FinanceRelationOption = { id: string; name: string; client_id?: string | null; }; export type FinanceTransactionItem = { id: string; type: "income" | "expense"; amount: number; currency: string; transaction_date: string; category: string | null; payment_status: "planned" | "pending" | "paid" | "cancelled"; client_id: string | null; project_id: string | null; clientName: string | null; projectName: string | null; description: string | null; }; const typeLabels = { income: "Gelir", expense: "Gider", }; const paymentStatusLabels = { planned: "Planlandı", pending: "Bekliyor", paid: "Ödendi", cancelled: "İptal edildi", }; const paymentStatusClasses = { planned: "border-blue-200 bg-blue-50 text-blue-700", pending: "border-amber-200 bg-amber-50 text-amber-700", paid: "border-emerald-200 bg-emerald-50 text-emerald-700", cancelled: "border-zinc-200 bg-zinc-50 text-zinc-700", }; const currencyOptions = [ { value: "USD", label: "Dolar (USD)" }, { value: "EUR", label: "Euro (EUR)" }, { value: "TRY", label: "Türk lirası (TRY)" }, { value: "GBP", label: "Sterlin (GBP)" }, { value: "CAD", label: "Kanada doları (CAD)" }, { value: "AUD", label: "Avustralya doları (AUD)" }, ]; // Dizilim ve featured alanı, özet şeridinde hangi metriklerin önce // gösterileceğini tek bir yerden değiştirmeyi sağlar. const financeSummaryCardConfig = [ { key: "afterTax", label: "Vergi Sonrası Net", tone: "green", icon: Wallet, featured: true }, { key: "net", label: "Brüt kazanç", tone: "primary", icon: Wallet, featured: true }, { key: "income", label: "Aylık gelir", tone: "green", icon: ArrowUpRight, featured: false }, { key: "expense", label: "Aylık gider", tone: "rose", icon: ArrowDownRight, featured: false }, { key: "pending", label: "Bekleyen", tone: "amber", icon: Wallet, featured: false }, { key: "tax", label: "KDV Tahmini (%20)", tone: "amber", icon: Wallet, featured: false }, ] as const; type FinanceClientProps = { transactions: FinanceTransactionItem[]; clients: FinanceRelationOption[]; projects: FinanceRelationOption[]; }; export function FinanceClient({ transactions, clients, projects }: FinanceClientProps) { const [query, setQuery] = useState(""); const [monthFilter, setMonthFilter] = useState(() => new Date().toISOString().slice(0, 7)); const summaryTrackRef = useRef(null); const normalizedQuery = query.trim().toLowerCase(); const filteredByMonth = transactions.filter((transaction) => transaction.transaction_date.startsWith(monthFilter), ); const filteredTransactions = normalizedQuery ? filteredByMonth.filter((transaction) => [ transaction.description, transaction.category, transaction.clientName, transaction.projectName, typeLabels[transaction.type], ] .filter(Boolean) .some((value) => value!.toLowerCase().includes(normalizedQuery)), ) : filteredByMonth; const summary = useMemo(() => calculateSummary(filteredByMonth), [filteredByMonth]); const categoryBreakdown = useMemo(() => calculateExpenseCategories(filteredByMonth), [filteredByMonth]); const summaryCards = financeSummaryCardConfig.map((card) => ({ ...card, value: formatCurrency(summary[card.key]), })); const scrollSummary = (direction: -1 | 1) => { const track = summaryTrackRef.current; if (!track) return; track.scrollBy({ left: direction * Math.max(track.clientWidth * 0.72, 260), behavior: "smooth", }); }; return (

Finans işlemleri

Gelir, gider, ödeme durumu ve proje/müşteri bağlantılarını takip et.

Finans özeti

Öne çıkan metrikler önce gösterilir; diğer kartlar arasında kaydırarak ilerleyebilirsin.

{ if (event.key === "ArrowLeft") { event.preventDefault(); scrollSummary(-1); } if (event.key === "ArrowRight") { event.preventDefault(); scrollSummary(1); } }} className="tiny-scrollbar flex snap-x snap-mandatory gap-3 overflow-x-auto scroll-smooth pb-3 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" > {summaryCards.map((card) => ( ))}

İşlem listesi

{filteredTransactions.length} kayıt görüntüleniyor.

setQuery(event.target.value)} placeholder="Kategori, müşteri, proje veya açıklama ara" className="sm:w-80" /> setMonthFilter(event.target.value)} className="sm:w-44" />
{filteredTransactions.length > 0 ? (
İşlem Tarih Tutar Durum İşlem
{filteredTransactions.map((transaction) => ( ))}
) : ( )}

Gider kategorileri

Aylık gider dağılımı

{categoryBreakdown.length > 0 ? (
{categoryBreakdown.map((item) => (
{item.category} {formatCurrency(item.amount)}
))}
) : (

Bu ay gider kaydı yok.

)}
); } function TransactionRow({ transaction, clients, projects, }: { transaction: FinanceTransactionItem; clients: FinanceRelationOption[]; projects: FinanceRelationOption[]; }) { const isIncome = transaction.type === "income"; return (
{isIncome ? : }
{transaction.description || typeLabels[transaction.type]}
{transaction.category || "Kategori yok"} · {transaction.projectName || transaction.clientName || "Bağlantı yok"}
{formatDate(transaction.transaction_date)}
{isIncome ? "+" : "-"} {formatCurrency(transaction.amount, transaction.currency)}
{paymentStatusLabels[transaction.payment_status]}
); } function FinanceDialog({ mode, transaction, clients, projects, }: { mode: "create" | "edit"; transaction?: FinanceTransactionItem; clients: FinanceRelationOption[]; projects: FinanceRelationOption[]; }) { const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const action = mode === "create" ? createFinanceTransactionRecord : updateFinanceTransactionRecord; async function handleSubmit(formData: FormData) { setIsSubmitting(true); try { await action(formData); setOpen(false); toast.success(mode === "create" ? "İşlem eklendi." : "İşlem güncellendi."); } catch (error) { toast.error( error instanceof Error ? error.message : "Finans işlemi kaydedilirken beklenmeyen bir hata oluştu.", ); } finally { setIsSubmitting(false); } } return (
{transaction ? : null} {mode === "create" ? "Yeni finans işlemi" : "Finans işlemini düzenle"} Gelir veya gider kaydını müşteri/proje bağlantısıyla kaydet.
); } function FinanceFormFields({ transaction, clients, projects, }: { transaction?: FinanceTransactionItem; clients: FinanceRelationOption[]; projects: FinanceRelationOption[]; }) { const [clientId, setClientId] = useState(transaction?.client_id || "__none"); const [projectId, setProjectId] = useState(transaction?.project_id || "__none"); const selectedProject = projectId === "__none" ? null : projects.find((project) => project.id === projectId) || null; const shouldLockClient = Boolean(selectedProject); const filteredProjects = clientId === "__none" || shouldLockClient ? projects : projects.filter((project) => project.client_id === clientId); const currencyValue = transaction?.currency || "USD"; const hasCustomCurrency = !currencyOptions.some((currency) => currency.value === currencyValue); function handleClientChange(nextClientId: string) { setClientId(nextClientId); if ( projectId !== "__none" && nextClientId !== "__none" && !projects.some((project) => project.id === projectId && project.client_id === nextClientId) ) { setProjectId("__none"); } } function handleProjectChange(nextProjectId: string) { setProjectId(nextProjectId); if (nextProjectId === "__none") { return; } const nextProject = projects.find((project) => project.id === nextProjectId); setClientId(nextProject?.client_id || "__none"); } return (
Gelir Gider Planlandı Bekliyor Ödendi İptal edildi
{shouldLockClient ? : null}