import { useEffect } from 'react' import { zodResolver } from '@hookform/resolvers/zod' import { CalendarCog, Loader2, Plus, RotateCcw, Save, Trash2, X } from 'lucide-react' import { Controller, useFieldArray, useForm, useWatch, type Control, type FieldErrors, type UseFormRegister, type UseFormSetValue, } from 'react-hook-form' import { Link, useBlocker } from 'react-router' import { toast } from 'sonner' import { FormField } from '@/components/form-field' import { paths } from '@/components/layout/nav-items' import { MoneyInput } from '@/components/money-input' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { previewWeeks } from '@/lib/budget-preview' import { formatDayMonth, formatMoney, formatNumber, parseMoney } from '@/lib/format' import { group, GROUPS } from '@/lib/groups' import { useSavePlan } from '@/lib/periods' import { diffPlan, GROUP_CODES, hasChanges, moneyToText, toDraft } from '@/lib/plan-draft' import { t } from '@/lib/strings' import { cn } from '@/lib/utils' import type { GroupBudget } from '@/types/GroupBudget' import type { Period } from '@/types/Period' import type { PeriodSummary } from '@/types/PeriodSummary' import type { PlanItem } from '@/types/PlanItem' import type { PlanItemKind } from '@/types/PlanItemKind' import { DistributionBar } from './distribution-bar' import { PeriodHeading, Section } from './plan-read' import { planSchema, type PlanForm } from './schema' import { SuggestionPicker } from './suggestion-picker' import { SummaryTiles } from './summary-tiles' const s = t.plan const itemText = t.wizard.items /** Metin → kuruş (geçersiz / boş / eksi → 0): önizleme hesapları için */ const k = (text: string | undefined) => Math.max(parseMoney(text ?? '') ?? 0, 0) /** * Düzenleme modu. Hiçbir şey anında kaydedilmez: "Kaydet" yalnızca değişenleri gönderir, * "Vazgeç" taslağı atar. Kaydedilmemiş değişiklik varken sayfadan çıkılırsa onay istenir. */ export function PlanEdit({ period, items, budget, summary, onClose, }: { period: Period items: PlanItem[] budget: GroupBudget[] summary: PeriodSummary onClose: () => void }) { const save = useSavePlan(period) const { control, register, handleSubmit, setValue, formState: { errors, isDirty, isSubmitting }, } = useForm({ resolver: zodResolver(planSchema(period.start_date, period.last_date)), mode: 'onTouched', defaultValues: toDraft(period, items, budget), }) const v = useWatch({ control }) // ---- Canlı önizleme: kaydedilmiş özet + taslaktaki farklar (formül backend'de kalır) const incomeTotal = (v.incomes ?? []).reduce((a, r) => a + k(r?.amount), 0) const fixedTotal = (v.expenses ?? []).reduce((a, r) => a + k(r?.amount), 0) const savings = k(v.savings) const spendable = summary.spendable + (incomeTotal - summary.income_total) - (fixedTotal - summary.fixed_expense_total) - (savings - summary.savings_goal) const budgetTotal = GROUP_CODES.reduce((a, g) => a + k(v.budget?.[g]), 0) const preview: PeriodSummary = { ...summary, income_total: incomeTotal, fixed_expense_total: fixedTotal, savings_goal: savings, spendable, budget_total: budgetTotal, unallocated: spendable - budgetTotal, } // ---- Kaydedilmemiş değişiklik koruması const blocker = useBlocker( ({ currentLocation, nextLocation }) => isDirty && !isSubmitting && currentLocation.pathname !== nextLocation.pathname, ) useEffect(() => { if (blocker.state !== 'blocked') return if (window.confirm(s.leaveConfirm)) blocker.proceed() else blocker.reset() }, [blocker]) useEffect(() => { if (!isDirty) return const warn = (e: BeforeUnloadEvent) => e.preventDefault() window.addEventListener('beforeunload', warn) return () => window.removeEventListener('beforeunload', warn) }, [isDirty]) const onSubmit = handleSubmit(async (draft) => { const ops = diffPlan(period, items, budget, draft) if (!hasChanges(ops)) { toast(s.noChanges) return onClose() } try { await save.mutateAsync(ops) toast.success(s.saved) } catch (e) { toast.error(s.saveFailed(e instanceof Error ? e.message : String(e))) } onClose() }) const actions = ( <> ) return (
✏️ {s.editing} · {s.editingHint}
{s.changeDates} } > {(f) => ( } /> )}
{actions}
) } // ---------- Gelir / gider satırları ---------- function ItemsEdit({ kind, period, control, register, errors, }: { kind: PlanItemKind period: Period control: Control register: UseFormRegister errors: FieldErrors }) { const name = kind === 'income' ? 'incomes' : 'expenses' // keyName: kalemlerin kendi `id` alanı var, RHF'nin iç anahtarı onu ezmesin const { fields, append, remove } = useFieldArray({ control, name, keyName: 'key' }) const rows = useWatch({ control, name }) const total = (rows ?? []).reduce((a, r) => a + k(r?.amount), 0) const text = itemText[kind] return (
{fields.length === 0 &&

{text.empty}

}
    {fields.map((field, i) => { const e = errors[name]?.[i] const messages = [e?.name?.message, e?.amount?.message, e?.date?.message].filter(Boolean) const label = rows?.[i]?.name || text.placeholder return (
  • {/* Mobilde ad + sil aynı satırda; geniş ekranda `contents` → ızgaraya katılır */}
    ( )} /> {messages.length > 0 && (

    {messages.join(' · ')}

    )}
  • ) })}
{text.total} {formatMoney(total)}
) } // ---------- Bütçe: aylık + haftalık ---------- function BudgetEdit({ period, control, setValue, errors, values, spendable, budgetTotal, }: { period: Period control: Control setValue: UseFormSetValue errors: FieldErrors values: ReturnType> spendable: number budgetTotal: number }) { return (
GROUP_CODES.forEach((g) => setValue(`budget.${g}`, moneyToText(a[g]), { shouldDirty: true, shouldValidate: true, }), ) } />
{GROUPS.map((g) => { const weekTexts = values.weeks?.[g.code] ?? [] const pv = previewWeeks( k(values.budget?.[g.code]), period.weeks.map((w, i) => { const txt = weekTexts[i] ?? '' return { days: w.days, manual: txt.trim() === '' ? null : k(txt) } }), ) return (
{(f) => ( ( )} /> )}
w.isManual)} className="group/weeks"> {s.weekly}

{s.weekHint}

    {period.weeks.map((w, i) => ( ))}
{pv.weeklyOver && (

{s.weeklyOver}

)}
) })}
) } function WeekRow({ control, setValue, groupCode, index, label, range, days, auto, manual, error, }: { control: Control setValue: UseFormSetValue groupCode: (typeof GROUP_CODES)[number] index: number label: string range: string days: number auto: number manual: boolean error?: string }) { const fieldName = `weeks.${groupCode}.${index}` as const return (
  • {label} {range} · {s.weekDays(days)} {manual && ` · ${s.manual}`}
    ( )} />
    {manual ? ( ) : ( )} {error &&

    {error}

    }
  • ) }