"use client"; import { ArrowLeftRight, CheckCheck, Copy, Pencil, Scale, Split, Trash2 } from "lucide-react"; import { useState, useTransition } from "react"; import { toast } from "sonner"; import { IconBadge } from "@/components/appearance/swatch"; import { useQuickAdd } from "@/components/layout/quick-add"; import { ResponsiveDialog } from "@/components/responsive-dialog"; import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { AttachmentStrip } from "@/features/attachments/components/attachment-strip"; import { tr } from "@/i18n/tr"; import { cn } from "@/lib/utils"; import { formatDate, formatDateTime, formatMoney } from "@/shared/format"; import type { TransactionType } from "@/shared/transactions"; import { getTransactionAction, setTransactionDeletedAction } from "../actions"; import type { TransactionDetail, TransactionListItem } from "../service"; /** İşlem listesi, ayrıntı ve hesap ekstresinin paylaştığı parçalar. */ const t = tr.transactions; const weekday = new Intl.DateTimeFormat("tr-TR", { weekday: "long", timeZone: "Europe/Istanbul" }); /** "2026-11-05" → "05.11.2026 · Perşembe" */ export function dayLabel(ymd: string) { const d = new Date(`${ymd}T12:00:00+03:00`); return `${formatDate(d)} · ${weekday.format(d)}`; } export function signed(type: TransactionType, amountMinor: number) { return type === "income" ? amountMinor : type === "expense" ? -amountMinor : 0; } export function Amount({ type, amountMinor, className, }: { type: TransactionType; amountMinor: number; className?: string; }) { const text = type === "income" ? `+${formatMoney(amountMinor)}` : type === "expense" ? `−${formatMoney(amountMinor)}` : formatMoney(amountMinor); return ( {text} ); } export function RowIcon({ item }: { item: TransactionListItem }) { if (item.type === "transfer") { return ( ); } if (item.isAdjustment) { return ( ); } if (item.splitCount) { return ( ); } return item.category ? ( ) : ( ); } export function rowTitle(item: TransactionListItem) { if (item.isAdjustment) return t.adjustment; if (item.payeeName) return item.payeeName; if (item.description) return item.description; if (item.type === "transfer") return t.types.transfer; return item.category?.name ?? t.types[item.type]; } export function rowSubtitle(item: TransactionListItem) { if (item.type === "transfer") return t.list.transfer(item.account.name, item.toAccount?.name ?? "—"); const what = item.isAdjustment ? item.description : item.splitCount ? t.list.split(item.splitCount) : (item.category?.path ?? "—"); return `${what} · ${item.account.name}`; } function DetailDialog({ detail, loading, canWrite, onOpenChange, onEdit, onCopy, onDelete, }: { detail: TransactionDetail | null; loading: boolean; canWrite: boolean; onOpenChange: (o: boolean) => void; onEdit: (d: TransactionDetail) => void; onCopy: (d: TransactionDetail) => void; onDelete: (d: TransactionDetail) => void; }) { const d = t.detail; const row = (label: string, value: React.ReactNode) => (
{label}
{value}
); return ( {/* Mutabakat düzeltmesi düzenlenemez/kopyalanamaz; yalnızca silinebilir. */} {detail.isAdjustment ? null : ( <> )} ) : null } > {!detail ? (
) : (

{rowTitle(detail)}

{dayLabel(detail.date)}

{row(t.filters.type, {t.types[detail.type]})} {detail.status !== "posted" ? row(t.filters.status, t.statuses[detail.status]) : null} {row( detail.type === "transfer" ? t.fields.fromAccount : t.fields.account, detail.account.name, )} {detail.toAccount ? row(t.fields.toAccount, detail.toAccount.name) : null} {detail.category ? row(t.fields.category, detail.category.path) : null} {detail.isAdjustment ? row(t.fields.category, t.adjustment) : null} {detail.payeeName ? row(t.fields.payee, detail.payeeName) : null} {detail.description ? row(t.fields.description, detail.description) : null} {detail.tags.length ? row( t.fields.tags, {detail.tags.map((tag) => ( #{tag} ))} , ) : null} {detail.attachments.length ? row( tr.attachments.title, , ) : null} {detail.reconciledAccountIds.length ? row( tr.statement.reconcile.title, {t.reconciled} , ) : null} {row(d.createdBy, `${detail.createdBy.name} · ${formatDateTime(detail.createdAt)}`)} {new Date(detail.updatedAt).getTime() - new Date(detail.createdAt).getTime() > 1000 ? row(d.updatedBy, `${detail.updatedBy.name} · ${formatDateTime(detail.updatedAt)}`) : null}
{detail.splits.length ? (

{d.lines}

    {detail.splits.map((s, i) => (
  • {s.path} {s.note ? ( {s.note} ) : null} {formatMoney(s.amountMinor)}
  • ))}
) : null}
)}
); } /** * Ayrıntı penceresi + silme onayı + geri al bildirimi. Liste ve ekstre aynı davranışı kullanır; * düzenle/kopyala uygulama genelindeki işlem formunu açar. */ export function useTransactionDetail(canWrite: boolean) { const { openTransactionForm } = useQuickAdd(); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [deleting, setDeleting] = useState(null); const [, startTransition] = useTransition(); async function openDetail(id: string) { setLoading(true); setDetail(null); const res = await getTransactionAction(id); setLoading(false); if (res.ok) setDetail(res.data); else toast.error(res.error); } function close() { setDetail(null); setLoading(false); } function remove(d: TransactionDetail) { setDeleting(null); close(); startTransition(async () => { const res = await setTransactionDeletedAction(d.id, true); if (!res.ok) return void toast.error(res.error); toast.success(t.toast.deleted, { duration: 10_000, action: { label: t.actions.restore, onClick: async () => { const r = await setTransactionDeletedAction(d.id, false); if (r.ok) toast.success(t.toast.restored); else toast.error(r.error); }, }, }); }); } const element = ( <> !o && close()} onEdit={(d) => { close(); openTransactionForm({ kind: "edit", transaction: d }); }} onCopy={(d) => { close(); openTransactionForm({ kind: "copy", transaction: d }); }} onDelete={setDeleting} /> !o && setDeleting(null)}> {t.deleteTitle} {t.deleteDescription} {tr.common.cancel} ); return { openDetail, element }; }