From 311cfc8c60282cb2f8d1fbb89511b43d70d30827 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 12 Nov 2026 12:31:35 +0300 Subject: [PATCH] =?UTF-8?q?feat(de=C4=9Ferlendirme):=20ay=20sonu=20de?= =?UTF-8?q?=C4=9Ferlendirmesi=20sayfas=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docs/MEMORY.md | 3 +- docs/PHASES.md | 2 +- frontend/src/components/markdown-tabs.tsx | 67 ++++++++ frontend/src/features/notes/note-editor.tsx | 53 +----- frontend/src/features/notes/notes-section.tsx | 48 +++++- frontend/src/features/review/breakdowns.tsx | 153 ++++++++++++++++++ frontend/src/features/review/page.tsx | 59 ++++++- frontend/src/features/review/questions.tsx | 72 +++++++++ frontend/src/features/review/reflection.tsx | 99 ++++++++++++ frontend/src/features/review/schema.test.ts | 12 ++ frontend/src/features/review/schema.ts | 12 ++ frontend/src/lib/periods.ts | 9 ++ frontend/src/lib/query.ts | 1 + frontend/src/lib/review.test.ts | 31 ++++ frontend/src/lib/review.ts | 26 +++ frontend/src/lib/strings.ts | 36 +++++ 16 files changed, 627 insertions(+), 56 deletions(-) create mode 100644 frontend/src/components/markdown-tabs.tsx create mode 100644 frontend/src/features/review/breakdowns.tsx create mode 100644 frontend/src/features/review/questions.tsx create mode 100644 frontend/src/features/review/reflection.tsx create mode 100644 frontend/src/features/review/schema.test.ts create mode 100644 frontend/src/features/review/schema.ts create mode 100644 frontend/src/lib/review.test.ts create mode 100644 frontend/src/lib/review.ts diff --git a/docs/MEMORY.md b/docs/MEMORY.md index 2453034..7074dac 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -1,7 +1,7 @@ # 🧠 Proje Hafızası ## ▶️ Sıradaki Adım -**Faz 6 – Adım 3:** Frontend Değerlendirme sayfası (`/degerlendirme`, açık dönem; ileride `?donem=`): 4 soru kartı (`PeriodReview`), grup planlanan/gerçekleşen çubukları, en çok harcanan kategoriler (`useCategories(true)` ile ad), haftalık dağılım, "Nasıl daha iyi yapabilirim?" (Markdown editör → `PATCH reflection_md`), notlar `NotesSection context="review"` (yalnızca başlık listesi, tıklayınca açılır), "🔒 Dönemi Kapat" düğmesi (sihirbaz Adım 4). +**Faz 6 – Adım 4:** Kapatma sihirbazı (`/degerlendirme/kapat`, Değerlendirme'de "🔒 Dönemi Kapat"): 1) özet + planlanan/gerçekleşen (review verisi), 2) gerçekleşen birikim → artıdaysa "birikime aktar" tutarı (MoneyInput, varsayılan tamamı; kalan artı devreder), açıktaysa borç bilgisi, 3) onay (`POST /close`, `version`), 4) sonuç + "Yeni dönemi aç" (sihirbaz `/plan/donem-ac`, önceki aydan kopyala adımları zaten var). Kapanınca `currentPeriod` null → sorgular yenilenir. **Bekleyen (kullanıcıda):** Faz 5 – Adım 5 canlı yedek kurulumu (`docs/DEPLOY.md` §6.1–6.3) → çıktılar gelince Faz 5 ✅. ## 📌 Alınan Kararlar @@ -109,3 +109,4 @@ ### 2026-11-12 — Faz 6 - **Adım 1:** Değerlendirme backend: `services/review.rs` (`budget::summary` + `budget::view` + kategori aggregation), `routes/review.rs` → `GET /periods/{id}/review` (`PeriodReview`: `available` = gelir − sabit − borç + artı, `savings_goal`, `spent_total`, `realized_savings` = available − harcanan, `savings_gap`; `groups` planned/spent, `categories` çoktan aza (+count), `weeks` 4 grubun toplamı). Kapalı dönemde okunur. `periods.reflection_md` (PATCH, ≤ 20.000 karakter, `""` → null, yalnızca açık dönem). `tests/review.rs` (3). Mutasyon: 4. soruya hedef düşmek / silinenleri saymak → yakalandı. Not: `TestApp::register` adı e-postaya çevirir → Türkçe karakterli ad kullanma. - **Adım 2:** Kapatma backend: `services/closing.rs` — `settle(realized, to_savings)` (< 0 → borç, birikim istenemez; ≥ 0 → varsayılan tamamı birikime, kalan artı), `close` (tek transaction: dönem `closed` + `closed_at/by` + `to_savings`, bu dönemden eski devirler soft delete, yeni devir; `to_period_id` = sonraki dönem ya da null), `reopen` (sahip; `closed` → **`correcting`**; ardından kapalı/düzeltmede dönem varsa 409). `PeriodStatus::Correcting` + `is_writable()` → `get_open`/`update` buna bakar; `current()` yalnız `open`. `period::create` artık transaction: önceki dönemin bağsız devrini bağlar. `POST /periods/{id}/close` (`PeriodCloseRequest`/`PeriodCloseResult`/`Carryover`), `POST /periods/{id}/reopen`. Period DTO: `to_savings`, `closed_at`. `tests/closing.rs` (4) + `settle` birim testi. Mutasyon 3/3 yakalandı; hata zorlanınca transaction geri aldı (dönem açık, sürüm aynı). Backend 113 test. Karar: yeniden açma = düzeltme modu (kullanıcı "a"). +- **Adım 3:** Değerlendirme sayfası (`features/review/`): `Questions` (4 kart; 1. kart hesap açıklaması, 3. bütçe yüzdesi, 4. hedefe göre durum `lib/review.ts` → `savingsStatus`: above/below/deficit), `breakdowns.tsx` (`GroupComparison`, `TopCategories` ilk 5 + "Tümünü göster", `WeeklyDistribution`; ortak `Bar`, `ratio`), `Reflection` (görüntüle/düzenle, `useUpdatePeriod` + `version`, 409 formda; şema `features/review/schema.ts`), `NotesSection collapsed` (yalnız başlık, `aria-expanded` ile açılır). Ortak `components/markdown-tabs.tsx` (Yaz/Önizle, `useId` kimlikler; `NoteEditor` da kullanıyor). `useReview` + `queryKeys.review` (dönem önekinde → kayıt değişince yenilenir). Dönem sürüyorsa bilgi satırı. Frontend 84 test; e2e 24 kontrol (Ekim kapatılıp Kasım'a artı devri dahil). Mobil haftalık satır sıkıştırıldı. diff --git a/docs/PHASES.md b/docs/PHASES.md index eb8dc15..eab82ca 100644 --- a/docs/PHASES.md +++ b/docs/PHASES.md @@ -63,6 +63,6 @@ ## Faz 6 Adımları (taslak) - ✅ 1. Backend: değerlendirme (`GET /periods/{id}/review`: 4 soru, grup/kategori/hafta) + `reflection_md` - ✅ 2. Backend: dönem kapatma (transaction: durum, devir, birikime aktarılan tutar) + yeni dönemde devrin bağlanması + yeniden açma (sahip) -- ⏳ 3. Frontend: Değerlendirme sayfası (4 soru, planlanan/gerçekleşen, en çok harcanan kategoriler, haftalık dağılım, "Nasıl daha iyi yapabilirim?", notlar) +- ✅ 3. Frontend: Değerlendirme sayfası (4 soru, planlanan/gerçekleşen, en çok harcanan kategoriler, haftalık dağılım, "Nasıl daha iyi yapabilirim?", notlar) - ⏳ 4. Frontend: dönem kapatma sihirbazı (+ yeni dönemi kopyalayarak açma) - ⏳ 5. Frontend: kapalı dönemler listesi + yeniden açma (sahip) diff --git a/frontend/src/components/markdown-tabs.tsx b/frontend/src/components/markdown-tabs.tsx new file mode 100644 index 0000000..5172570 --- /dev/null +++ b/frontend/src/components/markdown-tabs.tsx @@ -0,0 +1,67 @@ +import { useId, useState, type ReactNode } from 'react' +import { Markdown } from '@/components/markdown' +import { t } from '@/lib/strings' +import { cn } from '@/lib/utils' + +/** + * Markdown alanı için "Yaz / Önizle" sekmeleri. `children` = yazma paneli (genelde `Textarea`). + * Kimlikler `useId` ile üretilir → aynı sayfada birden çok editör olabilir. + */ +export function MarkdownTabs({ + label, + value, + children, +}: { + /** Sekme listesinin erişilebilir adı */ + label: string + /** Önizlenecek metin */ + value: string + children: ReactNode +}) { + const id = useId() + const [tab, setTab] = useState<'write' | 'preview'>('write') + + const tabButton = (v: typeof tab, text: string) => ( + + ) + + return ( +
+
+ {tabButton('write', t.notes.write)} + {tabButton('preview', t.notes.preview)} +
+ {tab === 'write' ? ( +
+ {children} +
+ ) : ( +
+ {value.trim() ? ( + {value} + ) : ( +

{t.notes.previewEmpty}

+ )} +
+ )} +
+ ) +} diff --git a/frontend/src/features/notes/note-editor.tsx b/frontend/src/features/notes/note-editor.tsx index 7c936ee..e5ac4f0 100644 --- a/frontend/src/features/notes/note-editor.tsx +++ b/frontend/src/features/notes/note-editor.tsx @@ -1,14 +1,12 @@ -import { useState } from 'react' import { zodResolver } from '@hookform/resolvers/zod' import { Loader2 } from 'lucide-react' import { useForm, useWatch } from 'react-hook-form' import { FormError, FormField } from '@/components/form-field' -import { Markdown } from '@/components/markdown' +import { MarkdownTabs } from '@/components/markdown-tabs' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { t } from '@/lib/strings' -import { cn } from '@/lib/utils' import { noteSchema, type NoteForm } from './schema' const s = t.notes @@ -26,7 +24,6 @@ export function NoteEditor({ onSave: (v: NoteForm) => Promise onCancel: () => void }) { - const [tab, setTab] = useState<'write' | 'preview'>('write') const { register, control, @@ -48,23 +45,6 @@ export function NoteEditor({ } }) - const tabButton = (value: typeof tab, label: string) => ( - - ) - return (
@@ -79,32 +59,11 @@ export function NoteEditor({ )} -
-
- {tabButton('write', s.write)} - {tabButton('preview', s.preview)} -
- {tab === 'write' ? ( -
- - {(f) =>