feat(ui): günlük plan kartı, gelir planı bölümü ve dashboard özet kartları

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-06 01:22:38 +03:00
parent 39aa19d168
commit d8f442c1aa
6 changed files with 564 additions and 7 deletions
+19
View File
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { PageHeader } from "@/components/page-header";
import { BudgetView } from "@/features/budgets/components/budget-view";
import { IncomePlanSection } from "@/features/budgets/components/income-plan";
import { getIncomePlan } from "@/features/budgets/plan";
import { getBudget } from "@/features/budgets/service";
import { tr } from "@/i18n/tr";
import { requireActiveHousehold } from "@/lib/dal";
@@ -15,10 +17,27 @@ export default async function BudgetPage({ searchParams }: PageProps<"/butce">)
const ay = budgetMonthSchema.safeParse((await searchParams).ay);
const data = await getBudget(user.id, household.id, ay.success ? ay.data : undefined);
if (!data) notFound();
const income = await getIncomePlan(user.id, household.id, data.month);
if (!income) notFound();
const monthLabel = new Intl.DateTimeFormat("tr-TR", {
month: "long",
year: "numeric",
timeZone: "Europe/Istanbul",
}).format(new Date(`${data.month}-15T12:00:00+03:00`));
return (
<>
<PageHeader title={tr.budget.title} description={tr.budget.description} />
<BudgetView key={data.month} data={data} />
<IncomePlanSection
key={`income-${data.month}`}
month={data.month}
monthLabel={monthLabel}
income={income}
limitMinor={data.totals.limitMinor}
forecastMinor={data.totals.forecastMinor}
hasBudget={data.hasBudget}
canWrite={data.canWrite}
/>
</>
);
}
+22 -6
View File
@@ -4,7 +4,10 @@ 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 { DailyPlanCard } from "@/features/budgets/components/daily-plan-card";
import { BudgetWarningBanner } from "@/features/budgets/components/warning-banner";
import { getIncomePlan } from "@/features/budgets/plan";
import { getBudget } from "@/features/budgets/service";
import { ContactsDueBanner } from "@/features/contacts/components/due-banner";
import { getNetWorth } from "@/features/net-worth/service";
import { OverdueBanner } from "@/features/recurring/components/overdue-banner";
@@ -23,15 +26,25 @@ type 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.
* Özet kartları (içinde bulunulan bütçe ayı): gerçekleşen gelir, gider ve farkı; hesap bakiyesi,
* kart borcu ve net varlık.
*/
async function getSummary(userId: string, householdId: string): Promise<SummaryCard[]> {
const nw = await getNetWorth(userId, householdId);
const [nw, budget] = await Promise.all([
getNetWorth(userId, householdId),
getBudget(userId, householdId),
]);
const income = budget ? await getIncomePlan(userId, householdId, budget.month) : null;
const inc = income?.actualMinor ?? null;
const exp = budget?.totals.actualMinor ?? null;
return [
{ key: "monthIncome", tone: "text-income", amountMinor: null },
{ key: "monthExpense", tone: "text-expense", amountMinor: null },
{ key: "monthRemaining", tone: "text-budget", amountMinor: null },
{ key: "monthIncome", tone: "text-income", amountMinor: inc, href: "/butce" },
{ key: "monthExpense", tone: "text-expense", amountMinor: exp, href: "/butce" },
{
key: "monthRemaining",
tone: "text-budget",
amountMinor: inc !== null && exp !== null ? inc - exp : 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" },
@@ -89,6 +102,9 @@ export default async function DashboardPage() {
<Suspense fallback={<SummaryCardsSkeleton />}>
<SummaryCards userId={user.id} householdId={household.id} />
</Suspense>
<Suspense fallback={null}>
<DailyPlanCard userId={user.id} householdId={household.id} />
</Suspense>
</>
);
}