fix(defter): haftalar ayın günlerine göre (1–8, 9–15, 16–22, 23–son), defter bugünün haftasıyla açılır
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useRef, useState } from 'react'
|
||||
import { useLocation, useSearchParams } from 'react-router'
|
||||
import { useLocation } from 'react-router'
|
||||
import { Info } from 'lucide-react'
|
||||
import { PageError, PageHeader, PageLoading } from '@/components/page'
|
||||
import { PeriodPicker } from '@/features/periods/period-picker'
|
||||
@@ -8,7 +8,7 @@ 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 { weekIndexFor } from '@/lib/ledger'
|
||||
import { isWritable } from '@/lib/period-select'
|
||||
import { useBudget } from '@/lib/periods'
|
||||
import { t } from '@/lib/strings'
|
||||
@@ -33,11 +33,11 @@ export function LedgerPage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hafta adreste tutulur (`?hafta=2`): yenileyince / geri gelince aynı hafta açılır.
|
||||
* Defter her açılışta **bugünün haftasıyla** başlar (dönem geçmişteyse son, gelecekteyse ilk
|
||||
* hafta); oklarla gezilen hafta yalnızca bu ekranda tutulur, adrese yazılmaz (kullanıcı kararı).
|
||||
* 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)
|
||||
// Panel'deki "Harcama Ekle" → tutar alanına odaklan
|
||||
@@ -45,9 +45,9 @@ function LedgerView({ period }: { period: Period }) {
|
||||
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 =
|
||||
parseWeekParam(params.get('hafta'), period.weeks.length) ?? weekIndexFor(period.weeks, today)
|
||||
const week = period.weeks[index - 1]
|
||||
const [index, setIndex] = useState(() => weekIndexFor(period.weeks, today))
|
||||
// Dönem tarihleri değişip hafta sayısı azalırsa son haftaya düş
|
||||
const week = period.weeks[Math.min(index, period.weeks.length) - 1]
|
||||
const readOnly = !isWritable(period)
|
||||
|
||||
const budget = useBudget(period.id)
|
||||
@@ -57,15 +57,7 @@ function LedgerView({ period }: { period: Period }) {
|
||||
|
||||
const goTo = (n: number) => {
|
||||
setEditing(null)
|
||||
setParams(
|
||||
(prev) => {
|
||||
const next = new URLSearchParams(prev) // seçili dönem (`donem`) korunur
|
||||
if (n === todayIndex) next.delete('hafta')
|
||||
else next.set('hafta', String(n))
|
||||
return next
|
||||
},
|
||||
{ replace: true },
|
||||
)
|
||||
setIndex(n)
|
||||
}
|
||||
|
||||
function edit(e: Entry) {
|
||||
@@ -81,7 +73,7 @@ function LedgerView({ period }: { period: Period }) {
|
||||
<div className="mx-auto max-w-3xl lg:max-w-none">
|
||||
<WeekPicker
|
||||
weeks={period.weeks}
|
||||
index={index}
|
||||
index={week.index}
|
||||
todayIndex={todayIndex}
|
||||
budget={budget.data}
|
||||
onChange={goTo}
|
||||
@@ -123,7 +115,7 @@ function LedgerView({ period }: { period: Period }) {
|
||||
{budget.isError ? (
|
||||
<PageError message={budget.error.message} onRetry={budget.refetch} />
|
||||
) : budget.data ? (
|
||||
<WeekSummary budget={budget.data} index={index} />
|
||||
<WeekSummary budget={budget.data} index={week.index} />
|
||||
) : (
|
||||
<PageLoading />
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { PeriodWeek } from '@/types/PeriodWeek'
|
||||
|
||||
const s = t.ledger
|
||||
|
||||
/** ◀ önceki | 2. hafta / 5 · 07.12 – 13.12 | sonraki ▶ (+ "Bu hafta", haftanın toplamı) */
|
||||
/** ◀ önceki | 2. hafta / 4 · 09.12 – 15.12 | sonraki ▶ (+ "Bu hafta", haftanın toplamı) */
|
||||
export function WeekPicker({
|
||||
weeks,
|
||||
index,
|
||||
|
||||
@@ -28,14 +28,15 @@ describe('splitProportional (backend ile aynı)', () => {
|
||||
})
|
||||
|
||||
describe('previewWeeks', () => {
|
||||
const dec = [6, 7, 7, 7, 4]
|
||||
// Aralık: 1–8, 9–15, 16–22, 23–31 (backend tests/budget.rs ile aynı)
|
||||
const dec = [8, 7, 7, 9]
|
||||
|
||||
it('all automatic', () => {
|
||||
const r = previewWeeks(
|
||||
100_000,
|
||||
dec.map((days) => ({ days, manual: null })),
|
||||
)
|
||||
expect(r.weeks.map((w) => w.amount)).toEqual([19_355, 22_581, 22_581, 22_580, 12_903])
|
||||
expect(r.weeks.map((w) => w.amount)).toEqual([25_806, 22_581, 22_581, 29_032])
|
||||
expect(r.weeklyOver).toBe(false)
|
||||
})
|
||||
|
||||
@@ -45,9 +46,9 @@ describe('previewWeeks', () => {
|
||||
dec.map((days, i) => ({ days, manual: i === 1 ? 40_000 : null })),
|
||||
)
|
||||
expect(r.weeks[1]).toEqual({ amount: 40_000, isManual: true })
|
||||
// kalan 60.000 → 6,7,7,4 gün
|
||||
// kalan 60.000 → 8,7,9 gün
|
||||
expect(r.weeks.filter((w) => !w.isManual).map((w) => w.amount)).toEqual([
|
||||
15_000, 17_500, 17_500, 10_000,
|
||||
20_000, 17_500, 22_500,
|
||||
])
|
||||
expect(r.weeks.reduce((a, w) => a + w.amount, 0)).toBe(100_000)
|
||||
})
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { PeriodWeek } from '@/types/PeriodWeek'
|
||||
import type { Entry } from '@/types/Entry'
|
||||
import { defaultEntryDate, groupByDay, parseWeekParam, weekIndexFor } from './ledger'
|
||||
import { defaultEntryDate, groupByDay, weekIndexFor } from './ledger'
|
||||
|
||||
// Aralık 2026: 1–6 · 7–13 · 14–20 · 21–27 · 28–31 (backend testleriyle aynı)
|
||||
// Aralık 2026: 1–8 · 9–15 · 16–22 · 23–31 (backend `calendar::weeks` ile aynı)
|
||||
const weeks: PeriodWeek[] = [
|
||||
['2026-12-01', '2026-12-06'],
|
||||
['2026-12-07', '2026-12-13'],
|
||||
['2026-12-14', '2026-12-20'],
|
||||
['2026-12-21', '2026-12-27'],
|
||||
['2026-12-28', '2026-12-31'],
|
||||
['2026-12-01', '2026-12-08'],
|
||||
['2026-12-09', '2026-12-15'],
|
||||
['2026-12-16', '2026-12-22'],
|
||||
['2026-12-23', '2026-12-31'],
|
||||
].map(([start_date, last_date], i) => ({ index: i + 1, start_date, last_date, days: 0 }))
|
||||
|
||||
describe('ledger', () => {
|
||||
it('finds the week of a date, clamping outside the period', () => {
|
||||
expect(weekIndexFor(weeks, '2026-12-06')).toBe(1)
|
||||
expect(weekIndexFor(weeks, '2026-12-07')).toBe(2)
|
||||
expect(weekIndexFor(weeks, '2026-12-31')).toBe(5)
|
||||
expect(weekIndexFor(weeks, '2026-12-08')).toBe(1)
|
||||
expect(weekIndexFor(weeks, '2026-12-09')).toBe(2)
|
||||
expect(weekIndexFor(weeks, '2026-12-23')).toBe(4)
|
||||
expect(weekIndexFor(weeks, '2026-12-31')).toBe(4)
|
||||
expect(weekIndexFor(weeks, '2026-11-12')).toBe(1)
|
||||
expect(weekIndexFor(weeks, '2027-01-03')).toBe(5)
|
||||
expect(weekIndexFor(weeks, '2027-01-03')).toBe(4)
|
||||
})
|
||||
|
||||
it('defaults the entry date to today, or the nearest day of the week', () => {
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-09')).toBe('2026-12-09')
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-20')).toBe('2026-12-13') // geçmiş hafta → son gün
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-02')).toBe('2026-12-07') // gelecek hafta → ilk gün
|
||||
})
|
||||
|
||||
it('accepts only valid week numbers from the address', () => {
|
||||
expect(parseWeekParam('3', 5)).toBe(3)
|
||||
expect(parseWeekParam('0', 5)).toBeNull()
|
||||
expect(parseWeekParam('6', 5)).toBeNull()
|
||||
expect(parseWeekParam('2a', 5)).toBeNull()
|
||||
expect(parseWeekParam(null, 5)).toBeNull()
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-13')).toBe('2026-12-13')
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-20')).toBe('2026-12-15') // geçmiş hafta → son gün
|
||||
expect(defaultEntryDate(weeks[1], '2026-12-02')).toBe('2026-12-09') // gelecek hafta → ilk gün
|
||||
})
|
||||
|
||||
it('groups entries by day, newest day first', () => {
|
||||
|
||||
@@ -21,13 +21,6 @@ export function defaultEntryDate(week: PeriodWeek, today: string): string {
|
||||
return today
|
||||
}
|
||||
|
||||
/** Adresteki `?hafta=` değeri → geçerli hafta numarası ya da `null` */
|
||||
export function parseWeekParam(value: string | null, count: number): number | null {
|
||||
if (!value || !/^\d+$/.test(value)) return null
|
||||
const n = Number(value)
|
||||
return n >= 1 && n <= count ? n : null
|
||||
}
|
||||
|
||||
export type EntryDay = { date: string; total: number; entries: Entry[] }
|
||||
|
||||
/** Kayıtları gün gün toplar; en yeni gün başta, gün içinde sunucu sırası korunur */
|
||||
|
||||
@@ -126,7 +126,7 @@ export const t = {
|
||||
last: 'Son gün',
|
||||
name: 'Dönem adı',
|
||||
nameHint: 'Boş bırakırsan tarihlere göre otomatik verilir (ör. "Aralık 2026").',
|
||||
days: (n: number) => `${n} gün · haftalar Pazartesi başlar`,
|
||||
days: (n: number) => `${n} gün · haftalar dönemin 1., 9., 16. ve 23. gününde başlar`,
|
||||
created: 'Dönem açıldı',
|
||||
startRequired: 'Başlangıç tarihini seçin',
|
||||
lastRequired: 'Son günü seçin',
|
||||
|
||||
Reference in New Issue
Block a user