feat(borç): borç takibi ekranları (Panel kartı, /borc, /taksitler)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-14 04:40:51 +03:00
parent 11402ba660
commit c063186e80
18 changed files with 1490 additions and 6 deletions
+107
View File
@@ -0,0 +1,107 @@
import { lazy, Suspense, useState } from 'react'
import { CreditCard, Landmark } from 'lucide-react'
import { Link } from 'react-router'
import { paths } from '@/components/layout/nav-items'
import { PageError, PageHeader, PageLoading } from '@/components/page'
import { Skeleton } from '@/components/skeleton'
import { Button } from '@/components/ui/button'
import { Section } from '@/features/planning/plan-read'
import { useDebt } from '@/lib/debt'
import { t } from '@/lib/strings'
import type { DebtSnapshot } from '@/types/DebtSnapshot'
import { DebtCard } from './debt-card'
import { History } from './history'
import { ImportSection } from './import-section'
import { SnapshotForm } from './snapshot-form'
import { TargetsEditor } from './targets-editor'
const DebtChart = lazy(() => import('./debt-chart'))
const s = t.debt
/** /borc: toplam borç, gerçek + hedef grafiği, ölçümler, yeni ölçüm, yol haritası, içe aktarma */
export function DebtPage() {
const debt = useDebt()
const [editing, setEditing] = useState<DebtSnapshot | null>(null)
const d = debt.data
return (
<>
<PageHeader
title={s.title}
description={s.description}
actions={
<Button asChild variant="outline">
<Link to={paths.installments}>
<CreditCard aria-hidden />
{s.toInstallments}
</Link>
</Button>
}
/>
{debt.isError ? (
<PageError message={debt.error.message} onRetry={debt.refetch} />
) : !d ? (
<PageLoading />
) : (
<div className="space-y-4">
{d.latest ? (
<DebtCard debt={d} />
) : (
<section className="flex flex-col items-center gap-3 rounded-2xl border bg-card p-8 text-center">
<div className="grid size-14 place-items-center rounded-full bg-primary/10 text-primary">
<Landmark className="size-7" aria-hidden />
</div>
<h2 className="text-lg font-semibold">{s.emptyTitle}</h2>
<p className="max-w-md text-muted-foreground">{s.emptyText}</p>
</section>
)}
{d.snapshots.length > 0 && (
<Section title={s.chartTitle}>
<Suspense fallback={<Skeleton className="h-72" />}>
<DebtChart debt={d} />
</Suspense>
</Section>
)}
{d.snapshots.length > 0 && (
<Section title={s.historyTitle}>
<History
snapshots={d.snapshots}
onEdit={(sn) => {
setEditing(sn)
document
.getElementById('debt-form')
?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}}
/>
</Section>
)}
<div className="grid items-start gap-4 lg:grid-cols-2">
<div id="debt-form" className="scroll-mt-4">
<Section title={editing ? s.formEditTitle : s.formTitle}>
<SnapshotForm
// Taksit planları değişince (ör. içe aktarma) kalan taksit önerisi yeniden dolsun
key={
editing?.id ?? `new-${d.installments.map((p) => `${p.id}:${p.version}`).join()}`
}
plans={d.installments}
editing={editing ?? undefined}
onDone={() => setEditing(null)}
/>
</Section>
</div>
<Section title={s.targetsTitle}>
<TargetsEditor
key={d.targets.map((x) => `${x.month}:${x.closing_target}`).join()}
targets={d.targets}
thisMonth={d.this_month}
/>
</Section>
<Section title={s.importTitle}>
<ImportSection />
</Section>
</div>
</div>
)}
</>
)
}