feat(ui): kart özeti, KMH paneli, taksit seçimi ve kalan taksit iptali
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,9 +42,22 @@ import type { PayeeSuggestion, TransactionDetail, TransactionFormOptions } from
|
||||
|
||||
const t = tr.transactions;
|
||||
const f = t.fields;
|
||||
const INSTALLMENT_OPTIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 18, 24, 36];
|
||||
|
||||
export type TransactionFormMode =
|
||||
| { kind: "create"; type: TransactionType }
|
||||
| {
|
||||
kind: "create";
|
||||
type: TransactionType;
|
||||
/** Ön doldurma (ör. kart borcu ödemesi, KMH faiz kaydı). */
|
||||
prefill?: {
|
||||
accountId?: string;
|
||||
toAccountId?: string;
|
||||
amountMinor?: number;
|
||||
categoryId?: string;
|
||||
date?: string;
|
||||
description?: string;
|
||||
};
|
||||
}
|
||||
| { kind: "edit"; transaction: TransactionDetail }
|
||||
| { kind: "copy"; transaction: TransactionDetail };
|
||||
|
||||
@@ -62,22 +75,26 @@ type FormValues = {
|
||||
tags: string;
|
||||
/** Önizleme bilgisiyle ekler; sunucuya yalnızca kimlikleri gider. */
|
||||
attachments: AttachmentInfo[];
|
||||
/** Taksit sayısı (yalnızca yeni kredi kartı gideri). */
|
||||
installments: string;
|
||||
};
|
||||
|
||||
function toValues(mode: TransactionFormMode, options: TransactionFormOptions | null): FormValues {
|
||||
if (mode.kind === "create") {
|
||||
const p = mode.prefill ?? {};
|
||||
return {
|
||||
type: mode.type,
|
||||
amount: "",
|
||||
date: todayIstanbul(),
|
||||
accountId: options?.accounts[0]?.id ?? "",
|
||||
toAccountId: "",
|
||||
categoryId: "",
|
||||
amount: p.amountMinor ? formatMoneyInput(p.amountMinor) : "",
|
||||
date: p.date ?? todayIstanbul(),
|
||||
accountId: p.accountId ?? options?.accounts[0]?.id ?? "",
|
||||
toAccountId: p.toAccountId ?? "",
|
||||
categoryId: p.categoryId ?? "",
|
||||
splits: [],
|
||||
payee: "",
|
||||
description: "",
|
||||
description: p.description ?? "",
|
||||
tags: "",
|
||||
attachments: [],
|
||||
installments: "1",
|
||||
};
|
||||
}
|
||||
const x = mode.transaction;
|
||||
@@ -98,6 +115,7 @@ function toValues(mode: TransactionFormMode, options: TransactionFormOptions | n
|
||||
tags: x.tags.join(", "),
|
||||
// Kopyada ekler taşınmaz (fiş o işleme aittir).
|
||||
attachments: mode.kind === "edit" ? x.attachments : [],
|
||||
installments: "1",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -374,6 +392,13 @@ export function TransactionFormDialog({
|
||||
}, [options, type, mode]);
|
||||
|
||||
const totalMinor = parseMoney(amount ?? "") ?? 0;
|
||||
const selectedAccountType = options?.accounts.find((a) => a.id === accountId)?.type;
|
||||
// Taksit: yeni (veya kopya) kredi kartı gideri, bölünmemiş.
|
||||
const showInstallments =
|
||||
mode?.kind !== "edit" &&
|
||||
type === "expense" &&
|
||||
selectedAccountType === "credit_card" &&
|
||||
!splitMode;
|
||||
const splitSum = (splitValues ?? []).reduce((a, s) => a + (parseMoney(s.amount ?? "") ?? 0), 0);
|
||||
const remaining = totalMinor - splitSum;
|
||||
|
||||
@@ -448,6 +473,7 @@ export function TransactionFormDialog({
|
||||
const { attachments, ...values } = form.getValues();
|
||||
const payload = {
|
||||
...values,
|
||||
installments: showInstallments ? values.installments : "1",
|
||||
attachmentIds: attachments.map((a) => a.id),
|
||||
toAccountId: values.type === "transfer" ? values.toAccountId : "",
|
||||
categoryId: values.type === "transfer" || values.splits.length ? "" : values.categoryId,
|
||||
@@ -595,6 +621,42 @@ export function TransactionFormDialog({
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
{showInstallments ? (
|
||||
<Controller
|
||||
name="installments"
|
||||
control={form.control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<FieldLabel htmlFor="tx-installments">{f.installments}</FieldLabel>
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger id="tx-installments" className="w-full">
|
||||
<SelectValue>
|
||||
{field.value === "1"
|
||||
? f.singlePayment
|
||||
: f.installmentCount(Number(field.value))}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="max-h-72">
|
||||
{INSTALLMENT_OPTIONS.map((n) => (
|
||||
<SelectItem key={n} value={String(n)}>
|
||||
{n === 1 ? f.singlePayment : f.installmentCount(n)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{Number(field.value) > 1 && totalMinor > 0 ? (
|
||||
<FieldDescription>
|
||||
{f.installmentHint(
|
||||
Number(field.value),
|
||||
formatMoney(Math.ceil(totalMinor / Number(field.value))),
|
||||
)}
|
||||
</FieldDescription>
|
||||
) : null}
|
||||
{fieldState.invalid ? <FieldError errors={[fieldState.error]} /> : null}
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
{type === "transfer" ? (
|
||||
<Controller
|
||||
name="toAccountId"
|
||||
|
||||
Reference in New Issue
Block a user