311cfc8c60
- Kakeibo'nun 4 sorusu, nasıl hesaplandığıyla birlikte - Grup bazında planlanan/gerçekleşen, en çok harcanan kategoriler, haftalık dağılım - "Nasıl daha iyi yapabilirim?" (Markdown) ve yalnızca başlıkları listelenen notlar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
177 lines
6.0 KiB
TypeScript
177 lines
6.0 KiB
TypeScript
/**
|
||
* 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 { PeriodReview } from '@/types/PeriodReview'
|
||
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 type { PlanOps } from './plan-draft'
|
||
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`),
|
||
})
|
||
}
|
||
|
||
/** Ay sonu değerlendirmesi: 4 soru + grup/kategori/hafta dökümü (kayıt değişince yenilenir) */
|
||
export function useReview(periodId: string) {
|
||
return useQuery({
|
||
queryKey: queryKeys.review(periodId),
|
||
queryFn: () => api.get<PeriodReview>(`/periods/${periodId}/review`),
|
||
})
|
||
}
|
||
|
||
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) })
|
||
},
|
||
})
|
||
}
|
||
|
||
// ---------- Plan ekranı: toplu kaydet ----------
|
||
|
||
/**
|
||
* Düzenleme modundaki değişiklikleri sırayla gönderir (`diffPlan` çıktısı).
|
||
* Tek transaction değildir: biri hata verirse kalanlar gönderilmez, hata fırlatılır;
|
||
* her durumda dönem verisi yeniden yüklenir → ekran sunucudaki gerçek durumu gösterir.
|
||
*/
|
||
export function useSavePlan(period: Period) {
|
||
const qc = useQueryClient()
|
||
return useMutation({
|
||
mutationFn: async (ops: PlanOps) => {
|
||
const pid = period.id
|
||
if (ops.savings !== null) {
|
||
await api.patch<Period>(`/periods/${pid}`, {
|
||
savings_goal: ops.savings,
|
||
version: period.version,
|
||
})
|
||
}
|
||
for (const id of ops.deletes) await api.delete(`/plan-items/${id}`)
|
||
for (const { id, ...body } of ops.updates) await api.patch(`/plan-items/${id}`, body)
|
||
for (const body of ops.creates) await api.post(`/periods/${pid}/plan-items`, body)
|
||
if (ops.monthly) await api.put(`/periods/${pid}/budget`, ops.monthly)
|
||
for (const body of ops.weeks) await api.put(`/periods/${pid}/budget/weeks`, body)
|
||
},
|
||
onSettled: () =>
|
||
Promise.all([
|
||
qc.invalidateQueries({ queryKey: queryKeys.periods }),
|
||
refreshPeriod(qc, period.id),
|
||
]),
|
||
})
|
||
}
|