/** * 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('/periods/current'), }) } export function usePeriods() { return useQuery({ queryKey: queryKeys.periods, queryFn: () => api.get('/periods'), }) } export function useCreatePeriod() { const qc = useQueryClient() return useMutation({ mutationFn: (body: PeriodCreateRequest) => api.post('/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(`/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(`/periods/${periodId}/plan-items`), }) } export function useCreatePlanItem(periodId: string) { const qc = useQueryClient() return useMutation({ mutationFn: (body: PlanItemCreateRequest) => api.post(`/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(`/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(`/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(`/periods/${periodId}/review`), }) } export function useBudget(periodId: string) { return useQuery({ queryKey: queryKeys.budget(periodId), queryFn: () => api.get(`/periods/${periodId}/budget`), }) } export function useSuggestion(periodId: string, method: SuggestMethod) { return useQuery({ queryKey: queryKeys.suggestion(periodId, method), queryFn: () => api.get(`/periods/${periodId}/budget/suggestion?method=${method}`), }) } export function useSetBudget(periodId: string) { const qc = useQueryClient() return useMutation({ mutationFn: (body: GroupAmounts) => api.put(`/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(`/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), ]), }) }