Files
kakeibo/frontend/src/features/debt/targets-editor.tsx
T

107 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { Loader2, Plus, X } from 'lucide-react'
import { toast } from 'sonner'
import { FormError } from '@/components/form-field'
import { MoneyInput } from '@/components/money-input'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { monthIndex, monthName, useSetTargets } from '@/lib/debt'
import { formatNumber, parseMoney } from '@/lib/format'
import { formatMonth } from '@/lib/reports'
import { t } from '@/lib/strings'
import type { DebtTarget } from '@/types/DebtTarget'
const s = t.debt
type Row = { key: number; month: string; amount: string }
/** Yol haritası: ay → ay sonu toplam borç hedefi (liste olduğu gibi kaydedilir) */
export function TargetsEditor({
targets,
thisMonth,
}: {
targets: DebtTarget[]
thisMonth: string
}) {
const save = useSetTargets()
const [rows, setRows] = useState<Row[]>(() =>
targets.map((tg, i) => ({
key: i,
month: tg.month,
amount: formatNumber(tg.closing_target / 100),
})),
)
const [error, setError] = useState<string>()
function add() {
const last = rows.length ? monthIndex(rows[rows.length - 1].month) : monthIndex(thisMonth)
setRows([
...rows,
{ key: Date.now(), month: monthName((last ?? 0) + (rows.length ? 1 : 0)), amount: '' },
])
}
async function submit() {
setError(undefined)
const out: DebtTarget[] = []
for (const r of rows) {
const k = parseMoney(r.amount)
if (monthIndex(r.month) === null || k === null || k < 0)
return setError(`${r.month || '?'}: ay ya da tutar geçersiz`)
out.push({ month: r.month, closing_target: k })
}
try {
await save.mutateAsync(out.sort((a, b) => a.month.localeCompare(b.month)))
toast.success(s.targetsSaved)
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
}
}
return (
<div className="space-y-3">
<p className="text-sm text-muted-foreground">{s.targetsHint}</p>
<ul className="space-y-2">
{rows.map((r, i) => (
<li key={r.key} className="flex items-center gap-2">
<Input
type="month"
aria-label={s.month}
value={r.month}
onChange={(e) =>
setRows(rows.map((x, j) => (j === i ? { ...x, month: e.target.value } : x)))
}
className="w-40 shrink-0"
/>
<div className="min-w-0 flex-1">
<MoneyInput
aria-label={`${s.targetAmount} ${r.month ? formatMonth(r.month) : ''}`}
value={r.amount}
onChange={(v) => setRows(rows.map((x, j) => (j === i ? { ...x, amount: v } : x)))}
/>
</div>
<Button
variant="ghost"
size="icon"
aria-label={s.removeTarget(r.month)}
onClick={() => setRows(rows.filter((_, j) => j !== i))}
>
<X aria-hidden />
</Button>
</li>
))}
</ul>
<FormError message={error} />
<div className="flex flex-wrap gap-2">
<Button variant="outline" onClick={add}>
<Plus aria-hidden />
{s.addTarget}
</Button>
<Button onClick={() => void submit()} disabled={save.isPending}>
{save.isPending && <Loader2 className="animate-spin" aria-hidden />}
{s.saveTargets}
</Button>
</div>
</div>
)
}