Files
kakeibo/frontend/src/lib/periods.ts
T
suphicakir 311cfc8c60 feat(değerlendirme): ay sonu değerlendirmesi sayfası
- 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>
2026-11-12 12:31:35 +03:00

177 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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),
]),
})
}