"use client";
import {
ArrowLeft,
CheckCheck,
ChevronLeft,
ChevronRight,
List,
Loader2,
Scale,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import { toast } from "sonner";
import { MoneyInput } from "@/components/money-input";
import { PageHeader } from "@/components/page-header";
import { ResponsiveDialog } from "@/components/responsive-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { tr } from "@/i18n/tr";
import { cn } from "@/lib/utils";
import { todayIstanbul } from "@/shared/dates";
import { formatDate, formatMoney } from "@/shared/format";
import { previewReconciliationAction, reconcileAccountAction } from "../actions";
import type { AccountStatement, ReconcilePreview, StatementRow } from "../statement";
import { RowIcon, rowSubtitle, rowTitle, useTransactionDetail } from "./transaction-parts";
const t = tr.statement;
const r = t.reconcile;
const monthFmt = new Intl.DateTimeFormat("tr-TR", {
month: "long",
year: "numeric",
timeZone: "Europe/Istanbul",
});
const monthLabel = (ym: string) => monthFmt.format(new Date(`${ym}-15T12:00:00+03:00`));
const dateLabel = (ymd: string) => formatDate(new Date(`${ymd}T12:00:00+03:00`));
function SignedMoney({ minor, className }: { minor: number; className?: string }) {
return (
0 && "text-income",
minor < 0 && "text-expense",
className,
)}
>
{minor > 0 ? "+" : minor < 0 ? "−" : ""}
{formatMoney(Math.abs(minor))}
);
}
export function StatementView({
statement: s,
canWrite,
}: {
statement: AccountStatement;
canWrite: boolean;
}) {
const { openDetail, element: detailElement } = useTransactionDetail(canWrite);
const [reconcileOpen, setReconcileOpen] = useState(false);
const base = `/hesaplar/${s.account.id}`;
return (
{canWrite && s.canReconcile ? (
) : null}
>
}
/>
{monthLabel(s.month)}
{s.nextMonth ? (
) : (
)}
{(
[
[t.opening, s.openingMinor, false],
[t.inflow, s.inflowMinor, true],
[t.outflow, -s.outflowMinor, true],
[t.closing, s.closingMinor, false],
] as const
).map(([label, value, colored]) => (
{label}
{colored ? (
) : (
{formatMoney(value)}
)}
))}
{s.lastReconciliation
? t.lastReconciliation(
dateLabel(s.lastReconciliation.date),
formatMoney(s.lastReconciliation.statementBalanceMinor),
)
: t.noReconciliation}
{s.rows.length === 0 ? (
{t.empty}
) : (
{s.rows.map((row) => (
-
openDetail(row.item.id)} />
))}
)}
{s.truncated ? {t.truncated}
: null}
{detailElement}
);
}
function StatementRowView({ row, onOpen }: { row: StatementRow; onOpen: () => void }) {
const content = (
<>
{rowTitle(row.item)}
{row.reconciled ? (
) : null}
{dateLabel(row.item.date)} · {rowSubtitle(row.item)}
{formatMoney(row.balanceMinor)}
>
);
const cls = "flex w-full items-center gap-3 p-3 text-left";
// Gizli karşı hesaba dokunan işlemin ayrıntısı açılmaz.
return row.restricted ? (
{content}
) : (
);
}
function ReconcileDialog({
open,
onOpenChange,
accountId,
isLiability,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
accountId: string;
isLiability: boolean;
}) {
const [date, setDate] = useState(todayIstanbul);
const [balance, setBalance] = useState("");
const [preview, setPreview] = useState(null);
const [accept, setAccept] = useState(false);
const [errors, setErrors] = useState>({});
const [pending, setPending] = useState(false);
function reset(next: boolean) {
if (!next) {
setPreview(null);
setAccept(false);
setErrors({});
setBalance("");
setDate(todayIstanbul());
}
onOpenChange(next);
}
const input = () => ({
statementDate: date,
statementBalance: balance,
acceptAdjustment: accept,
});
async function check() {
setPending(true);
const res = await previewReconciliationAction(accountId, input());
setPending(false);
if (!res.ok) {
setErrors({ ...res.fieldErrors, root: res.fieldErrors ? undefined : res.error });
return;
}
setErrors({});
setAccept(false);
setPreview(res.data);
}
async function confirm() {
setPending(true);
const res = await reconcileAccountAction(accountId, input());
setPending(false);
if (!res.ok) {
setErrors({ root: res.error });
return;
}
toast.success(
res.data.adjustmentId
? r.doneWithAdjustment(res.data.markedCount)
: r.done(res.data.markedCount),
);
reset(false);
}
const diff = preview?.differenceMinor ?? 0;
const display = (minor: number) => formatMoney(isLiability ? -minor : minor);
return (
{preview ? (
) : (
)}
>
}
>
{r.date}
{
setDate(e.target.value);
setPreview(null);
}}
/>
{errors.statementDate ? {errors.statementDate} : null}
{isLiability ? r.debt : r.balance}
{
setBalance(v);
setPreview(null);
}}
/>
{isLiability ? r.debtHint : r.balanceHint}
{errors.statementBalance ? {errors.statementBalance} : null}
{preview ? (
- {r.statementBalance}
- {display(preview.statementBalanceMinor)}
- {r.appBalance}
- {display(preview.appBalanceMinor)}
- {r.difference}
-
{diff === 0 ? (
{r.matches}
) : (
<>
{r.mismatch}
>
)}
{r.unreconciled(preview.unreconciledCount)}
) : null}
{errors.root ? {errors.root} : null}
);
}