feat(defter): haftanın girişleri, düzenle/sil + geri al, haftalık özet

- Girişler gün gün, gün toplamıyla; satırda kategori, grup, ödeme türü, açıklama
- Düzenleme aynı formda (sürüm kontrolü, pasif kategori korunur)
- Silmede "Geri al" bildirimi; grup bazlı bütçe / harcanan / kalan özeti

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 11:29:33 +03:00
parent f930f2fbcc
commit 16535ad7f3
13 changed files with 472 additions and 62 deletions
+86 -30
View File
@@ -1,16 +1,20 @@
import { useState } from 'react'
import { useRef, useState } from 'react'
import { useSearchParams } from 'react-router'
import { PageError, PageHeader, PageLoading } from '@/components/page'
import { NoPeriodCard } from '@/features/period-wizard/no-period-card'
import { Section } from '@/features/planning/plan-read'
import { useCategories, usePaymentMethods } from '@/lib/catalog'
import { useEntries } from '@/lib/entries'
import { toIsoDate } from '@/lib/format'
import { parseWeekParam, weekIndexFor } from '@/lib/ledger'
import { useBudget, useCurrentPeriod } from '@/lib/periods'
import { t } from '@/lib/strings'
import type { Entry } from '@/types/Entry'
import type { Period } from '@/types/Period'
import { EntryForm } from './entry-form'
import { EntryList } from './entry-list'
import { WeekPicker } from './week-picker'
import { WeekSummary } from './week-summary'
export function LedgerPage() {
const current = useCurrentPeriod()
@@ -30,10 +34,15 @@ export function LedgerPage() {
)
}
/** Hafta adreste tutulur (`?hafta=2`): yenileyince / geri gelince aynı hafta açılır. */
/**
* Hafta adreste tutulur (`?hafta=2`): yenileyince / geri gelince aynı hafta açılır.
* Düzen: hafta seçici → form → haftalık özet → girişler (masaüstünde form solda, diğerleri sağda).
*/
function LedgerView({ period }: { period: Period }) {
const [params, setParams] = useSearchParams()
const [today] = useState(() => toIsoDate(new Date()))
const [editing, setEditing] = useState<Entry | null>(null)
const formRef = useRef<HTMLDivElement>(null)
const inPeriod = today >= period.start_date && today <= period.last_date
const todayIndex = inPeriod ? weekIndexFor(period.weeks, today) : null
const index =
@@ -41,38 +50,85 @@ function LedgerView({ period }: { period: Period }) {
const week = period.weeks[index - 1]
const budget = useBudget(period.id)
const categories = useCategories()
const methods = usePaymentMethods()
const categories = useCategories(true)
const methods = usePaymentMethods(true)
const entries = useEntries(period.id, { from: week.start_date, to: week.last_date })
const goTo = (n: number) =>
const goTo = (n: number) => {
setEditing(null)
setParams(n === todayIndex ? {} : { hafta: String(n) }, { replace: true })
}
function edit(e: Entry) {
setEditing(e)
formRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
const catalogFailed = [categories, methods].find((q) => q.isError)
const catalog = categories.data && methods.data ? { c: categories.data, m: methods.data } : null
const failed = [categories, methods].find((q) => q.isError)
return (
<div className="mx-auto max-w-3xl space-y-4">
<WeekPicker
weeks={period.weeks}
index={index}
todayIndex={todayIndex}
budget={budget.data}
onChange={goTo}
/>
<Section title={t.ledger.formTitle}>
{failed?.error ? (
<PageError message={failed.error.message} onRetry={failed.refetch} />
) : !categories.data || !methods.data ? (
<PageLoading />
) : (
<EntryForm
period={period}
week={week}
today={today}
categories={categories.data}
methods={methods.data}
onSaved={(date) => goTo(weekIndexFor(period.weeks, date))}
/>
)}
</Section>
<div className="space-y-4">
<div className="mx-auto max-w-3xl lg:max-w-none">
<WeekPicker
weeks={period.weeks}
index={index}
todayIndex={todayIndex}
budget={budget.data}
onChange={goTo}
/>
</div>
<div className="grid items-start gap-4 lg:grid-cols-2">
<div ref={formRef} className="scroll-mt-4 lg:sticky lg:top-4">
<Section title={editing ? t.ledger.editTitle : t.ledger.formTitle}>
{catalogFailed?.error ? (
<PageError message={catalogFailed.error.message} onRetry={catalogFailed.refetch} />
) : !catalog ? (
<PageLoading />
) : (
<EntryForm
key={editing?.id ?? 'new'}
period={period}
week={week}
today={today}
categories={catalog.c}
methods={catalog.m}
entry={editing ?? undefined}
onSaved={(date) => goTo(weekIndexFor(period.weeks, date))}
onCancel={() => setEditing(null)}
/>
)}
</Section>
</div>
<div className="space-y-4">
<Section title={t.ledger.summaryTitle}>
{budget.isError ? (
<PageError message={budget.error.message} onRetry={budget.refetch} />
) : budget.data ? (
<WeekSummary budget={budget.data} index={index} />
) : (
<PageLoading />
)}
</Section>
<Section title={t.ledger.listTitle}>
{entries.isError ? (
<PageError message={entries.error.message} onRetry={entries.refetch} />
) : entries.data && catalog ? (
<EntryList
periodId={period.id}
entries={entries.data}
categories={catalog.c}
methods={catalog.m}
editingId={editing?.id}
onEdit={edit}
onDeleted={(id) => editing?.id === id && setEditing(null)}
/>
) : (
<PageLoading />
)}
</Section>
</div>
</div>
</div>
)
}