"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2 } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { Controller, useForm, useWatch, type Resolver } from "react-hook-form"; import { toast } from "sonner"; import { MoneyInput } from "@/components/money-input"; import { ResponsiveDialog } from "@/components/responsive-dialog"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { getTransactionFormOptionsAction } from "@/features/transactions/actions"; import { AccountSelect, CategorySelect, PayeeInput, } from "@/features/transactions/components/transaction-form"; import type { TransactionFormOptions } from "@/features/transactions/service"; import { tr } from "@/i18n/tr"; import { todayIstanbul } from "@/shared/dates"; import { formatDate } from "@/shared/format"; import { formatMoneyInput } from "@/shared/money"; import { describeRecurrence, FREQUENCIES, listOccurrences, MONTHLY_MODES, type Frequency, type MonthlyMode, } from "@/shared/recurrence"; import { END_KINDS, recurringRuleFormSchema, type RecurringRuleForm, } from "@/shared/schemas/recurring"; import { TRANSACTION_TYPES, type TransactionType } from "@/shared/transactions"; import { createRuleAction, updateRuleAction } from "../actions"; import type { RuleFormValues } from "../service"; const t = tr.recurring; const tf = tr.transactions.fields; export type RuleFormMode = | { kind: "create" } | { kind: "edit"; rule: RuleFormValues; scope: "all" | "following"; /** "Bu ve sonrakiler" için başlangıç tekrarı ve onun tarihi / kalan tekrar sayısı. */ fromTransactionId?: string; fromDate?: string; remainingCount?: number | null; }; type FormValues = { type: TransactionType; amount: string; isEstimate: boolean; accountId: string; toAccountId: string; categoryId: string; payee: string; description: string; tags: string; startDate: string; frequency: Frequency; interval: string; monthlyMode: MonthlyMode; endKind: (typeof END_KINDS)[number]; count: string; endDate: string; }; function toValues(mode: RuleFormMode, options: TransactionFormOptions | null): FormValues { if (mode.kind === "create") { return { type: "expense", amount: "", isEstimate: false, accountId: options?.accounts[0]?.id ?? "", toAccountId: "", categoryId: "", payee: "", description: "", tags: "", startDate: todayIstanbul(), frequency: "monthly", interval: "1", monthlyMode: "day", endKind: "never", count: "", endDate: "", }; } const r = mode.rule; const following = mode.scope === "following" && mode.fromDate; const count = following ? (mode.remainingCount ?? null) : r.count; return { type: r.type, amount: formatMoneyInput(r.amountMinor), isEstimate: r.isEstimate, accountId: r.accountId, toAccountId: r.toAccountId ?? "", categoryId: r.categoryId ?? "", payee: r.payee, description: r.description, tags: r.tags.join(", "), startDate: following ? mode.fromDate! : r.startDate, frequency: r.frequency, interval: String(r.interval), monthlyMode: r.monthlyMode, endKind: count ? "count" : r.endDate ? "date" : "never", count: count ? String(count) : "", endDate: r.endDate ?? "", }; } /** Düzenli işlem kuralı formu (oluştur / tümünü düzenle / bu ve sonrakileri düzenle). */ export function RuleFormDialog({ mode, onOpenChange, }: { mode: RuleFormMode | null; onOpenChange: (open: boolean) => void; }) { const [options, setOptions] = useState(null); const [loadError, setLoadError] = useState(null); const form = useForm({ resolver: zodResolver(recurringRuleFormSchema) as unknown as Resolver< FormValues, unknown, RecurringRuleForm >, defaultValues: toValues({ kind: "create" }, null), }); useEffect(() => { if (!mode) return; let cancelled = false; getTransactionFormOptionsAction().then((res) => { if (cancelled) return; if (!res.ok) return setLoadError(res.error); setLoadError(null); setOptions(res.data); form.reset(toValues(mode, res.data)); }); return () => { cancelled = true; }; }, [mode, form]); const w = useWatch({ control: form.control }); const type = w.type ?? "expense"; const categories = useMemo(() => { const kind = type === "income" ? "income" : "expense"; return (options?.categories ?? []).filter((c) => c.kind === kind); }, [options, type]); // Önizleme: açıklama ve ilk 3 tarih (istemcide aynı saf fonksiyonla). const preview = useMemo(() => { const interval = Math.min(12, Math.max(1, Number(w.interval) || 1)); const r = { frequency: w.frequency ?? "monthly", interval, monthlyMode: w.monthlyMode ?? "day", startDate: w.startDate ?? todayIstanbul(), endDate: w.endKind === "date" && w.endDate ? w.endDate : null, count: w.endKind === "count" && Number(w.count) > 0 ? Number(w.count) : null, } as const; if (!/^\d{4}-\d{2}-\d{2}$/.test(r.startDate)) return null; return { text: describeRecurrence(r), dates: listOccurrences(r, "9999-12-31") .slice(0, 3) .map((o) => o.date), }; }, [w]); function changeType(next: TransactionType) { form.setValue("type", next); form.setValue("categoryId", ""); if (next !== "transfer") form.setValue("toAccountId", ""); form.clearErrors(); } async function onSubmit() { if (!mode) return; const values = form.getValues(); const payload = { ...values, toAccountId: values.type === "transfer" ? values.toAccountId : "", categoryId: values.type === "transfer" ? "" : values.categoryId, count: values.endKind === "count" ? values.count : null, endDate: values.endKind === "date" ? values.endDate : null, }; const result = mode.kind === "edit" ? await updateRuleAction(mode.rule.id, payload, mode.scope, mode.fromTransactionId) : await createRuleAction(payload); if (result.ok) { toast.success(mode.kind === "edit" ? t.toast.updated : t.toast.created); onOpenChange(false); return; } for (const [name, message] of Object.entries(result.fieldErrors ?? {})) { if (message) form.setError(name as keyof FormValues, { message }); } if (!result.fieldErrors) form.setError("root", { message: result.error }); } const loading = Boolean(mode) && !options && !loadError; const errors = form.formState.errors; const title = mode?.kind === "edit" ? `${t.editTitle} · ${t.scopes[mode.scope]}` : t.createTitle; return ( ) } > {loadError ? (

{loadError}

) : loading || !options ? (
) : (
changeType(v as TransactionType)}> {TRANSACTION_TYPES.map((x) => ( {tr.transactions.types[x]} ))} ( {tf.amount} {fieldState.invalid ? : null} )} /> ( )} /> ( {type === "transfer" ? tf.fromAccount : tf.account} {fieldState.invalid ? : null} )} /> {type === "transfer" ? ( ( {tf.toAccount} {fieldState.invalid ? : null} )} /> ) : ( ( {tf.category} {fieldState.invalid ? : null} )} /> )} ( {tf.payee} {}} /> )} /> ( {tf.description} )} />
( {t.fields.startDate} {fieldState.invalid ? : null} )} /> ( {t.fields.frequency} )} />
( {t.fields.interval} ({t.units[w.frequency ?? "monthly"]}) {fieldState.invalid ? : null} )} /> {w.frequency === "monthly" ? ( ( {t.fields.monthlyMode} )} /> ) : null}
( {t.fields.end} )} /> {w.endKind === "count" ? ( ( {t.fields.count} {fieldState.invalid ? : null} )} /> ) : w.endKind === "date" ? ( ( {t.fields.endDate} {fieldState.invalid ? : null} )} /> ) : null}
{preview ? ( {preview.text} · {t.fields.preview}:{" "} {preview.dates.map((d) => formatDate(`${d}T12:00:00+03:00`)).join(", ")} ) : null} {errors.root ? {errors.root.message} : null}
)}
); }