feat(06-02): replace BudgetSetup with template-based month picker

- Rewrite BudgetSetup to use month picker + currency + Generate button
- Remove manual form fields (name, dates, carryover, copy-from select)
- Handle 409 conflict gracefully by calling onCreated() to refresh list
- Remove copyFrom method from budgets API (TMPL-06)
- Update BudgetSetup test to reflect new month-picker UI
- Remove copyFrom from DashboardPage test mock
- Add budget.generate, budget.month, budget.generating i18n keys (EN/DE)
- Remove budget.copyFrom and budget.setup i18n keys
This commit is contained in:
2026-03-12 13:08:46 +01:00
parent 14075850c3
commit 7dfd04f31b
6 changed files with 60 additions and 75 deletions

View File

@@ -3,9 +3,8 @@ import { useTranslation } from 'react-i18next'
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Spinner } from '@/components/ui/spinner'
import { budgets as budgetsApi, type Budget } from '@/lib/api'
import { budgets as budgetsApi, type Budget, ApiError } from '@/lib/api'
interface Props {
existingBudgets: Budget[]
@@ -13,30 +12,24 @@ interface Props {
onCancel: () => void
}
export function BudgetSetup({ existingBudgets, onCreated, onCancel }: Props) {
export function BudgetSetup({ existingBudgets: _existingBudgets, onCreated, onCancel }: Props) {
const { t } = useTranslation()
const [name, setName] = useState('')
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [month, setMonth] = useState('')
const [currency, setCurrency] = useState('EUR')
const [carryover, setCarryover] = useState('0')
const [copyFromId, setCopyFromId] = useState('')
const [saving, setSaving] = useState(false)
const handleCreate = async () => {
const handleGenerate = async () => {
setSaving(true)
try {
const budget = await budgetsApi.create({
name,
start_date: startDate,
end_date: endDate,
currency,
carryover_amount: parseFloat(carryover) || 0,
})
if (copyFromId) {
await budgetsApi.copyFrom(budget.id, copyFromId)
}
await budgetsApi.generate({ month, currency })
onCreated()
} catch (err) {
if (err instanceof ApiError && err.status === 409) {
// Budget already exists for this month — navigate to it by refreshing the list
onCreated()
} else {
throw err
}
} finally {
setSaving(false)
}
@@ -45,53 +38,22 @@ export function BudgetSetup({ existingBudgets, onCreated, onCancel }: Props) {
return (
<Card>
<CardHeader className="bg-gradient-to-r from-violet-50 to-purple-50">
<CardTitle>{t('budget.setup')}</CardTitle>
<CardTitle>{t('budget.generate')}</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4 pt-4">
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.name')}</label>
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Oktober 2025" />
<label className="text-sm font-medium">{t('budget.month')}</label>
<Input type="month" value={month} onChange={(e) => setMonth(e.target.value)} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.startDate')}</label>
<Input type="date" value={startDate} onChange={(e) => setStartDate(e.target.value)} />
</div>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.endDate')}</label>
<Input type="date" value={endDate} onChange={(e) => setEndDate(e.target.value)} />
</div>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.currency')}</label>
<Input value={currency} onChange={(e) => setCurrency(e.target.value)} />
</div>
<div className="grid grid-cols-2 gap-4">
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.currency')}</label>
<Input value={currency} onChange={(e) => setCurrency(e.target.value)} />
</div>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.carryover')}</label>
<Input type="number" step="0.01" value={carryover} onChange={(e) => setCarryover(e.target.value)} />
</div>
</div>
{existingBudgets.length > 0 && (
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('budget.copyFrom')}</label>
<Select value={copyFromId} onValueChange={setCopyFromId}>
<SelectTrigger>
<SelectValue placeholder="—" />
</SelectTrigger>
<SelectContent>
{existingBudgets.map((b) => (
<SelectItem key={b.id} value={b.id}>{b.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</CardContent>
<CardFooter className="flex gap-2 justify-end">
<Button variant="outline" onClick={onCancel}>{t('common.cancel')}</Button>
<Button onClick={handleCreate} disabled={saving || !name || !startDate || !endDate} className="min-w-[120px]">
{saving ? <Spinner /> : t('common.create')}
<Button onClick={handleGenerate} disabled={saving || !month} className="min-w-[160px]">
{saving ? <Spinner /> : t('budget.generate')}
</Button>
</CardFooter>
</Card>