ca0d3a505c
- /plan/donem-ac?adim=1..5: her adım hemen kaydedilir, adım adreste - Panel ve Plan'da açık dönem yoksa "Dönemi başlat" kartı - MoneyInput (sayısal klavye, Türkçe biçim), moneyText/toKurus, period-dates - lib/periods.ts: dönem, plan kalemi, özet, bütçe ve öneri kancaları - SummaryTiles, PageLoading/PageError; 13 yeni vitest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
2.0 KiB
TypeScript
56 lines
2.0 KiB
TypeScript
import { z } from 'zod'
|
||
import { moneyText } from '@/lib/money-schema'
|
||
import { daysInclusive, isIsoDate, MAX_PERIOD_DAYS } from '@/lib/period-dates'
|
||
import { t } from '@/lib/strings'
|
||
|
||
const s = t.wizard
|
||
|
||
/** 1. adım: dönem tarihleri. `last` = son gün (dahil). */
|
||
export const datesSchema = z
|
||
.object({
|
||
start: z.string(),
|
||
last: z.string(),
|
||
name: z.string().trim().max(60, s.dates.nameTooLong),
|
||
})
|
||
.superRefine((v, ctx) => {
|
||
if (!isIsoDate(v.start)) {
|
||
ctx.addIssue({ code: 'custom', path: ['start'], message: s.dates.startRequired })
|
||
}
|
||
if (!isIsoDate(v.last)) {
|
||
ctx.addIssue({ code: 'custom', path: ['last'], message: s.dates.lastRequired })
|
||
return
|
||
}
|
||
if (!isIsoDate(v.start)) return
|
||
if (v.last < v.start) {
|
||
ctx.addIssue({ code: 'custom', path: ['last'], message: s.dates.lastBeforeStart })
|
||
} else if (daysInclusive(v.start, v.last) > MAX_PERIOD_DAYS) {
|
||
ctx.addIssue({ code: 'custom', path: ['last'], message: s.dates.tooLong(MAX_PERIOD_DAYS) })
|
||
}
|
||
})
|
||
export type DatesForm = z.infer<typeof datesSchema>
|
||
|
||
/** 2–3. adım: gelir / gider kalemi. Tarih boş olabilir; doluysa dönem içinde olmalı. */
|
||
export function itemSchema(start: string, last: string) {
|
||
return z.object({
|
||
name: z.string().trim().min(1, s.items.nameRequired).max(60, s.items.nameTooLong),
|
||
amount: moneyText('positive'),
|
||
date: z
|
||
.string()
|
||
.refine((d) => d === '' || (isIsoDate(d) && d >= start && d <= last), s.items.dateOutside),
|
||
})
|
||
}
|
||
export type ItemForm = z.infer<ReturnType<typeof itemSchema>>
|
||
|
||
/** 4. adım */
|
||
export const savingsSchema = z.object({ goal: moneyText('optional') })
|
||
export type SavingsForm = z.infer<typeof savingsSchema>
|
||
|
||
/** 5. adım: 4 grubun aylık bütçesi */
|
||
export const budgetSchema = z.object({
|
||
needs: moneyText('optional'),
|
||
wants: moneyText('optional'),
|
||
culture: moneyText('optional'),
|
||
unexpected: moneyText('optional'),
|
||
})
|
||
export type BudgetForm = z.infer<typeof budgetSchema>
|