Desktop ist wieder auf Horizontal-Scroll zurückgebaut, mobil bleibt die Dropdown-Auswahl. Dabei habe ich die Scroll-Container stabilisiert, damit die AG- und Budgetkarten sauber scrollen statt seitlich zu „wackeln“, in dashboard-shell.tsx und budget-column.tsx.
Die Abo-Logik ist jetzt deutlich sauberer: beim Anlegen gibt es ein Startdatum, der Server leitet daraus Monatsraten für den gewählten Zeitraum ab, Budgets rechnen mit dem periodischen Gesamtbetrag, und Abo-Ausgaben erscheinen als aufklappbare Gruppe statt als aufgeblähte Liste. Das steckt vor allem in page.tsx, recurring-expenses.ts, route.ts, dashboard-types.ts und der Migration migration.sql. Backup/Import und Audit-Restore kennen das neue Feld ebenfalls.
This commit is contained in:
103
src/app/page.tsx
103
src/app/page.tsx
@@ -13,6 +13,7 @@ import type {
|
||||
} from "@/lib/dashboard-types";
|
||||
import { canManageUsers, normalizeApprovalPermissions } from "@/lib/domain";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { buildRecurringOccurrences, getExpensePeriodAmount } from "@/lib/recurring-expenses";
|
||||
import { getCurrentViewer } from "@/lib/session";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -24,10 +25,7 @@ export default async function DashboardPage() {
|
||||
redirect("/login");
|
||||
}
|
||||
|
||||
const [currentPeriod, appSettings] = await Promise.all([
|
||||
getCurrentAccountingPeriod(),
|
||||
getAppSettings()
|
||||
]);
|
||||
const [currentPeriod, appSettings] = await Promise.all([getCurrentAccountingPeriod(), getAppSettings()]);
|
||||
|
||||
if (!currentPeriod) {
|
||||
throw new Error("Kein Abrechnungszeitraum gefunden.");
|
||||
@@ -106,14 +104,7 @@ export default async function DashboardPage() {
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: [
|
||||
{
|
||||
role: "asc"
|
||||
},
|
||||
{
|
||||
username: "asc"
|
||||
}
|
||||
]
|
||||
orderBy: [{ role: "asc" }, { username: "asc" }]
|
||||
})
|
||||
: [];
|
||||
|
||||
@@ -142,11 +133,7 @@ export default async function DashboardPage() {
|
||||
username: viewer.username,
|
||||
role: viewer.role,
|
||||
workingGroupId: viewer.workingGroupId,
|
||||
approvalPermissions: normalizeApprovalPermissions(
|
||||
viewer.role,
|
||||
viewer.approvalPermissions,
|
||||
viewer.approvalPreference
|
||||
)
|
||||
approvalPermissions: normalizeApprovalPermissions(viewer.role, viewer.approvalPermissions, viewer.approvalPreference)
|
||||
};
|
||||
|
||||
const serializedGroups: DashboardWorkingGroup[] = workingGroups.map((workingGroup) => ({
|
||||
@@ -165,33 +152,55 @@ export default async function DashboardPage() {
|
||||
totalBudget: Number(budget.totalBudget),
|
||||
colorCode: budget.colorCode,
|
||||
periodId: budget.periodId,
|
||||
expenses: budget.expenses.map((expense) => ({
|
||||
id: expense.id,
|
||||
title: expense.title,
|
||||
description: expense.description,
|
||||
amount: Number(expense.amount),
|
||||
budgetId: expense.budgetId,
|
||||
periodId: expense.periodId,
|
||||
approvalStatus: expense.approvalStatus,
|
||||
recurrence: expense.recurrence,
|
||||
paidAt: expense.paidAt?.toISOString() ?? null,
|
||||
documentedAt: expense.documentedAt?.toISOString() ?? null,
|
||||
proofUrl: expense.proofUrl,
|
||||
createdAt: expense.createdAt.toISOString(),
|
||||
creator: {
|
||||
id: expense.creator.id,
|
||||
name: expense.creator.username
|
||||
},
|
||||
approvals: expense.approvals.map((approval) => ({
|
||||
id: approval.id,
|
||||
approvalType: approval.approvalType,
|
||||
timestamp: approval.timestamp.toISOString(),
|
||||
user: {
|
||||
id: approval.user.id,
|
||||
name: approval.user.username
|
||||
}
|
||||
}))
|
||||
}))
|
||||
expenses: budget.expenses.map((expense) => {
|
||||
const amount = Number(expense.amount);
|
||||
const recurrenceStartAt =
|
||||
expense.recurrence === "MONTHLY"
|
||||
? (expense.recurrenceStartAt ?? expense.createdAt).toISOString()
|
||||
: null;
|
||||
const occurrences =
|
||||
expense.recurrence === "MONTHLY" && recurrenceStartAt
|
||||
? buildRecurringOccurrences({
|
||||
expenseId: expense.id,
|
||||
amount,
|
||||
recurrenceStartAt,
|
||||
periodStartsAt: currentPeriod.startsAt,
|
||||
periodEndsAt: currentPeriod.endsAt
|
||||
})
|
||||
: [];
|
||||
|
||||
return {
|
||||
id: expense.id,
|
||||
title: expense.title,
|
||||
description: expense.description,
|
||||
amount,
|
||||
periodAmount: getExpensePeriodAmount(amount, expense.recurrence, occurrences.length),
|
||||
occurrenceCount: expense.recurrence === "MONTHLY" ? occurrences.length : 1,
|
||||
occurrences,
|
||||
budgetId: expense.budgetId,
|
||||
periodId: expense.periodId,
|
||||
approvalStatus: expense.approvalStatus,
|
||||
recurrence: expense.recurrence,
|
||||
recurrenceStartAt,
|
||||
paidAt: expense.paidAt?.toISOString() ?? null,
|
||||
documentedAt: expense.documentedAt?.toISOString() ?? null,
|
||||
proofUrl: expense.proofUrl,
|
||||
createdAt: expense.createdAt.toISOString(),
|
||||
creator: {
|
||||
id: expense.creator.id,
|
||||
name: expense.creator.username
|
||||
},
|
||||
approvals: expense.approvals.map((approval) => ({
|
||||
id: approval.id,
|
||||
approvalType: approval.approvalType,
|
||||
timestamp: approval.timestamp.toISOString(),
|
||||
user: {
|
||||
id: approval.user.id,
|
||||
name: approval.user.username
|
||||
}
|
||||
}))
|
||||
};
|
||||
})
|
||||
}))
|
||||
}));
|
||||
|
||||
@@ -202,11 +211,7 @@ export default async function DashboardPage() {
|
||||
role: user.role,
|
||||
workingGroupId: user.workingGroupId,
|
||||
workingGroupName: user.workingGroup?.name ?? null,
|
||||
approvalPermissions: normalizeApprovalPermissions(
|
||||
user.role,
|
||||
user.approvalPermissions,
|
||||
user.approvalPreference
|
||||
),
|
||||
approvalPermissions: normalizeApprovalPermissions(user.role, user.approvalPermissions, user.approvalPreference),
|
||||
createdExpensesCount: user._count.createdExpenses,
|
||||
approvalsCount: user._count.approvals
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user