feat(plan): ilk dönem sihirbazı (dönem, gelir, gider, birikim, bütçe)
- /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>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Dönem, plan kalemleri, özet ve bütçe: sunucu verisi kancaları (TanStack Query).
|
||||
*
|
||||
* Kural: bir dönemde bir şey değişince o döneme bağlı tüm sorgular
|
||||
* (`queryKeys.period(id)` önekiyle) yenilenir → özet ve öneri hep güncel kalır.
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query'
|
||||
import type { BudgetSuggestion } from '@/types/BudgetSuggestion'
|
||||
import type { CopyPreviousResult } from '@/types/CopyPreviousResult'
|
||||
import type { GroupAmounts } from '@/types/GroupAmounts'
|
||||
import type { GroupBudget } from '@/types/GroupBudget'
|
||||
import type { Period } from '@/types/Period'
|
||||
import type { PeriodCreateRequest } from '@/types/PeriodCreateRequest'
|
||||
import type { PeriodSummary } from '@/types/PeriodSummary'
|
||||
import type { PeriodUpdateRequest } from '@/types/PeriodUpdateRequest'
|
||||
import type { PlanItem } from '@/types/PlanItem'
|
||||
import type { PlanItemCreateRequest } from '@/types/PlanItemCreateRequest'
|
||||
import type { SuggestMethod } from '@/types/SuggestMethod'
|
||||
import { api } from './api'
|
||||
import { queryKeys } from './query'
|
||||
|
||||
function refreshPeriod(qc: QueryClient, id: string) {
|
||||
return qc.invalidateQueries({ queryKey: queryKeys.period(id) })
|
||||
}
|
||||
|
||||
// ---------- Dönem ----------
|
||||
|
||||
/** Açık dönem: `undefined` yükleniyor, `null` açık dönem yok */
|
||||
export function useCurrentPeriod() {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.currentPeriod,
|
||||
queryFn: () => api.get<Period | null>('/periods/current'),
|
||||
})
|
||||
}
|
||||
|
||||
export function usePeriods() {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.periods,
|
||||
queryFn: () => api.get<Period[]>('/periods'),
|
||||
})
|
||||
}
|
||||
|
||||
export function useCreatePeriod() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (body: PeriodCreateRequest) => api.post<Period>('/periods', body),
|
||||
onSuccess: (p) => {
|
||||
qc.setQueryData(queryKeys.currentPeriod, p)
|
||||
void qc.invalidateQueries({ queryKey: queryKeys.periods, exact: true })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdatePeriod() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, ...body }: PeriodUpdateRequest & { id: string }) =>
|
||||
api.patch<Period>(`/periods/${id}`, body),
|
||||
onSuccess: (p) => {
|
||||
if (p.status === 'open') qc.setQueryData(queryKeys.currentPeriod, p)
|
||||
void qc.invalidateQueries({ queryKey: queryKeys.periods, exact: true })
|
||||
void refreshPeriod(qc, p.id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- Plan kalemleri ----------
|
||||
|
||||
export function usePlanItems(periodId: string) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.planItems(periodId),
|
||||
queryFn: () => api.get<PlanItem[]>(`/periods/${periodId}/plan-items`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useCreatePlanItem(periodId: string) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (body: PlanItemCreateRequest) =>
|
||||
api.post<PlanItem>(`/periods/${periodId}/plan-items`, body),
|
||||
onSuccess: () => refreshPeriod(qc, periodId),
|
||||
})
|
||||
}
|
||||
|
||||
export function useDeletePlanItem(periodId: string) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (itemId: string) => api.delete(`/plan-items/${itemId}`),
|
||||
onSuccess: () => refreshPeriod(qc, periodId),
|
||||
})
|
||||
}
|
||||
|
||||
export function useCopyPrevious(periodId: string) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: () => api.post<CopyPreviousResult>(`/periods/${periodId}/plan-items/copy-previous`),
|
||||
onSuccess: () => refreshPeriod(qc, periodId),
|
||||
})
|
||||
}
|
||||
|
||||
// ---------- Özet ve bütçe ----------
|
||||
|
||||
export function useSummary(periodId: string) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.summary(periodId),
|
||||
queryFn: () => api.get<PeriodSummary>(`/periods/${periodId}/summary`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useBudget(periodId: string) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.budget(periodId),
|
||||
queryFn: () => api.get<GroupBudget[]>(`/periods/${periodId}/budget`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useSuggestion(periodId: string, method: SuggestMethod) {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.suggestion(periodId, method),
|
||||
queryFn: () =>
|
||||
api.get<BudgetSuggestion>(`/periods/${periodId}/budget/suggestion?method=${method}`),
|
||||
})
|
||||
}
|
||||
|
||||
export function useSetBudget(periodId: string) {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (body: GroupAmounts) => api.put<GroupBudget[]>(`/periods/${periodId}/budget`, body),
|
||||
onSuccess: (groups) => {
|
||||
qc.setQueryData(queryKeys.budget(periodId), groups)
|
||||
void qc.invalidateQueries({ queryKey: queryKeys.summary(periodId) })
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user