feat(ui): değer grafiği, net varlık kartı, dashboard kartları ve ödeme diyaloğunda varlık bağı
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,36 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { AssetDetailView } from "@/features/assets/components/asset-detail-view";
|
||||
import { getAsset } from "@/features/assets/service";
|
||||
import { PeriodReportView } from "@/features/assets/components/period-report";
|
||||
import {
|
||||
getAsset,
|
||||
getPeriodReport,
|
||||
REPORT_RANGES,
|
||||
type ReportRange,
|
||||
} from "@/features/assets/service";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { requireActiveHousehold } from "@/lib/dal";
|
||||
|
||||
export const metadata: Metadata = { title: tr.assets.history };
|
||||
|
||||
/** /birikim/:id — varlık hareketleri (gizli varlık yalnızca sahibine; aksi 404). */
|
||||
export default async function AssetPage({ params }: PageProps<"/birikim/[id]">) {
|
||||
/** /birikim/:id — varlık hareketleri ve dönemsel rapor (gizli varlık yalnızca sahibine; aksi 404). */
|
||||
export default async function AssetPage({ params, searchParams }: PageProps<"/birikim/[id]">) {
|
||||
const { user, household } = await requireActiveHousehold("viewer");
|
||||
const { id } = await params;
|
||||
const data = await getAsset(user.id, household.id, id);
|
||||
if (!data) notFound();
|
||||
return <AssetDetailView key={id} data={data} />;
|
||||
const donem = (await searchParams).donem;
|
||||
const range: ReportRange = REPORT_RANGES.includes(donem as ReportRange)
|
||||
? (donem as ReportRange)
|
||||
: "6";
|
||||
const [data, report] = await Promise.all([
|
||||
getAsset(user.id, household.id, id),
|
||||
getPeriodReport(user.id, household.id, range, id),
|
||||
]);
|
||||
if (!data || !report) notFound();
|
||||
return (
|
||||
<AssetDetailView
|
||||
key={id}
|
||||
data={data}
|
||||
report={<PeriodReportView report={report} range={range} basePath={`/birikim/${id}`} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,21 +2,45 @@ import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { AssetsView } from "@/features/assets/components/assets-view";
|
||||
import { listAssets } from "@/features/assets/service";
|
||||
import { PeriodReportView } from "@/features/assets/components/period-report";
|
||||
import {
|
||||
getPeriodReport,
|
||||
listAssets,
|
||||
REPORT_RANGES,
|
||||
type ReportRange,
|
||||
} from "@/features/assets/service";
|
||||
import { NetWorthCard } from "@/features/net-worth/components/net-worth-card";
|
||||
import { getNetWorth } from "@/features/net-worth/service";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { requireActiveHousehold } from "@/lib/dal";
|
||||
|
||||
export const metadata: Metadata = { title: tr.assets.title };
|
||||
|
||||
/** /birikim — altın, döviz ve ziynet varlıkları; ortalama maliyet ve kâr/zarar. */
|
||||
export default async function AssetsPage() {
|
||||
function parseRange(value: string | string[] | undefined): ReportRange {
|
||||
return REPORT_RANGES.includes(value as ReportRange) ? (value as ReportRange) : "6";
|
||||
}
|
||||
|
||||
/** /birikim — net varlık, varlıklar (ortalama maliyet, K/Z) ve dönemsel rapor. */
|
||||
export default async function AssetsPage({ searchParams }: PageProps<"/birikim">) {
|
||||
const { user, household } = await requireActiveHousehold("viewer");
|
||||
const data = await listAssets(user.id, household.id);
|
||||
if (!data) notFound();
|
||||
const range = parseRange((await searchParams).donem);
|
||||
const [data, report, netWorth] = await Promise.all([
|
||||
listAssets(user.id, household.id),
|
||||
getPeriodReport(user.id, household.id, range),
|
||||
getNetWorth(user.id, household.id),
|
||||
]);
|
||||
if (!data || !report || !netWorth) notFound();
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={tr.assets.title} description={tr.assets.description} />
|
||||
<section aria-labelledby="net-worth-title" className="flex flex-col gap-2">
|
||||
<h2 id="net-worth-title" className="text-base font-medium">
|
||||
{tr.assets.netWorth.title}
|
||||
</h2>
|
||||
<NetWorthCard data={netWorth} />
|
||||
</section>
|
||||
<AssetsView items={data.items} totals={data.totals} canWrite={data.canWrite} />
|
||||
<PeriodReportView report={report} range={range} basePath="/birikim" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
import { PageHeader } from "@/components/page-header";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { SummaryCardsSkeleton } from "@/features/dashboard/components/summary-cards-skeleton";
|
||||
import { ContactsDueBanner } from "@/features/contacts/components/due-banner";
|
||||
import { getNetWorth } from "@/features/net-worth/service";
|
||||
import { OverdueBanner } from "@/features/recurring/components/overdue-banner";
|
||||
import { tr } from "@/i18n/tr";
|
||||
import { requireActiveHousehold } from "@/lib/dal";
|
||||
@@ -16,22 +18,27 @@ type SummaryCard = {
|
||||
key: keyof typeof tr.dashboard.cards;
|
||||
tone?: string;
|
||||
amountMinor: number | null;
|
||||
href?: string;
|
||||
};
|
||||
|
||||
/** FAZ 14: gerçek özet verisi (gelir, gider, bakiye, kart borcu, net varlık). */
|
||||
async function getSummary(): Promise<SummaryCard[]> {
|
||||
/**
|
||||
* Özet kartları. Bakiye, kart borcu ve net varlık gerçek (Faz 9); bu ayın gelir/gider/kalan
|
||||
* kartları Faz 14'te dolar.
|
||||
*/
|
||||
async function getSummary(userId: string, householdId: string): Promise<SummaryCard[]> {
|
||||
const nw = await getNetWorth(userId, householdId);
|
||||
return [
|
||||
{ key: "monthIncome", tone: "text-income", amountMinor: null },
|
||||
{ key: "monthExpense", tone: "text-expense", amountMinor: null },
|
||||
{ key: "monthRemaining", tone: "text-budget", amountMinor: null },
|
||||
{ key: "totalBalance", amountMinor: null },
|
||||
{ key: "cardDebt", tone: "text-expense", amountMinor: null },
|
||||
{ key: "netWorth", amountMinor: null },
|
||||
{ key: "totalBalance", amountMinor: nw?.accountsMinor ?? null, href: "/hesaplar" },
|
||||
{ key: "cardDebt", tone: "text-expense", amountMinor: nw?.cardDebtMinor ?? null },
|
||||
{ key: "netWorth", amountMinor: nw?.totalMinor ?? null, href: "/birikim" },
|
||||
];
|
||||
}
|
||||
|
||||
async function SummaryCards() {
|
||||
const cards = await getSummary();
|
||||
async function SummaryCards({ userId, householdId }: { userId: string; householdId: string }) {
|
||||
const cards = await getSummary(userId, householdId);
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3 xl:grid-cols-6">
|
||||
{cards.map((c) => (
|
||||
@@ -39,7 +46,15 @@ async function SummaryCards() {
|
||||
<CardHeader>
|
||||
<CardDescription>{tr.dashboard.cards[c.key]}</CardDescription>
|
||||
<CardTitle className={cn("text-lg tabular-nums", c.tone)}>
|
||||
{c.amountMinor === null ? "—" : formatMoney(c.amountMinor)}
|
||||
{c.amountMinor === null ? (
|
||||
"—"
|
||||
) : c.href ? (
|
||||
<Link href={c.href} className="hover:underline">
|
||||
{formatMoney(c.amountMinor)}
|
||||
</Link>
|
||||
) : (
|
||||
formatMoney(c.amountMinor)
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
{c.amountMinor === null ? (
|
||||
@@ -68,7 +83,7 @@ export default async function DashboardPage() {
|
||||
<ContactsDueBanner userId={user.id} householdId={household.id} />
|
||||
</Suspense>
|
||||
<Suspense fallback={<SummaryCardsSkeleton />}>
|
||||
<SummaryCards />
|
||||
<SummaryCards userId={user.id} householdId={household.id} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user