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>
This commit is contained in:
2026-11-12 12:31:35 +03:00
parent 62e92f552a
commit 311cfc8c60
16 changed files with 627 additions and 56 deletions
+57 -2
View File
@@ -1,11 +1,66 @@
import { ComingSoon, PageHeader } from '@/components/page'
import { useState } from 'react'
import { Info } from 'lucide-react'
import { PageError, PageHeader, PageLoading } from '@/components/page'
import { NotesSection } from '@/features/notes/notes-section'
import { NoPeriodCard } from '@/features/period-wizard/no-period-card'
import { PeriodHeading } from '@/features/planning/plan-read'
import { useCategories } from '@/lib/catalog'
import { formatDate, toIsoDate } from '@/lib/format'
import { useCurrentPeriod, useReview } from '@/lib/periods'
import { t } from '@/lib/strings'
import type { Period } from '@/types/Period'
import { GroupComparison, TopCategories, WeeklyDistribution } from './breakdowns'
import { Questions } from './questions'
import { Reflection } from './reflection'
export function ReviewPage() {
const current = useCurrentPeriod()
return (
<>
<PageHeader title={t.pages.review.title} description={t.pages.review.description} />
<ComingSoon when="Faz 6'da" />
{current.isPending ? (
<PageLoading />
) : current.isError ? (
<PageError message={current.error.message} onRetry={current.refetch} />
) : current.data ? (
<ReviewView period={current.data} />
) : (
<NoPeriodCard />
)}
</>
)
}
/**
* Hesaplar sorgu anında backend'de yapılır (`GET /periods/{id}/review`); defterde
* bir kayıt değişince bu sayfa da yenilenir. Kapalı dönem: salt okunur (Adım 5'te seçilebilir).
*/
function ReviewView({ period }: { period: Period }) {
const review = useReview(period.id)
const categories = useCategories(true)
const [today] = useState(() => toIsoDate(new Date()))
const readOnly = period.status === 'closed'
if (review.isError) return <PageError message={review.error.message} onRetry={review.refetch} />
if (!review.data) return <PageLoading />
return (
<div className="space-y-6">
<PeriodHeading period={period} />
{!readOnly && today <= period.last_date && (
<p className="flex items-start gap-2 rounded-xl bg-muted px-3 py-2 text-sm text-muted-foreground">
<Info className="mt-0.5 size-4 shrink-0" aria-hidden />
{t.review.ongoing(formatDate(period.last_date))}
</p>
)}
<Questions review={review.data} />
<div className="grid items-start gap-4 lg:grid-cols-2">
<GroupComparison review={review.data} />
<TopCategories review={review.data} categories={categories.data ?? []} />
</div>
<WeeklyDistribution review={review.data} />
<Reflection period={period} readOnly={readOnly} />
<NotesSection periodId={period.id} context="review" readOnly={readOnly} collapsed />
</div>
)
}