All checks were successful
CI / build-and-deploy (push) Successful in 1m22s
Den Zeitraum-Bereich habe ich dabei gleich mit aufgeräumt: die Auswahl des aktuellen Haushalts ist breiter und sauberer angeordnet, und die Desktop-Nutzerverwaltung ist jetzt wirklich links Anlegen + Schwelle und rechts die Nutzerliste. Seed und Backup/Restore kennen die neuen Felder ebenfalls in seed.ts, route.ts und route.ts.
253 lines
6.6 KiB
TypeScript
253 lines
6.6 KiB
TypeScript
import { redirect } from "next/navigation";
|
|
|
|
import { DashboardShell } from "@/components/dashboard/dashboard-shell";
|
|
import { getCurrentAccountingPeriod } from "@/lib/accounting-periods";
|
|
import { getAppSettings, toApprovalThresholdNumber } from "@/lib/app-settings";
|
|
import { getRollbackMetadata } from "@/lib/audit-log";
|
|
import type {
|
|
DashboardAccountingPeriod,
|
|
DashboardAuditLog,
|
|
DashboardManagedUser,
|
|
DashboardViewer,
|
|
DashboardWorkingGroup
|
|
} from "@/lib/dashboard-types";
|
|
import { canManageUsers, normalizeApprovalPermissions } from "@/lib/domain";
|
|
import prisma from "@/lib/prisma";
|
|
import { getCurrentViewer } from "@/lib/session";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function DashboardPage() {
|
|
const viewer = await getCurrentViewer();
|
|
|
|
if (!viewer) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const [currentPeriod, appSettings] = await Promise.all([
|
|
getCurrentAccountingPeriod(),
|
|
getAppSettings()
|
|
]);
|
|
|
|
if (!currentPeriod) {
|
|
throw new Error("Kein Abrechnungszeitraum gefunden.");
|
|
}
|
|
|
|
const accountingPeriods = await prisma.accountingPeriod.findMany({
|
|
orderBy: {
|
|
startsAt: "desc"
|
|
}
|
|
});
|
|
|
|
const workingGroups = await prisma.workingGroup.findMany({
|
|
include: {
|
|
members: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
username: true,
|
|
role: true
|
|
}
|
|
},
|
|
budgets: {
|
|
where: {
|
|
periodId: currentPeriod.id
|
|
},
|
|
orderBy: {
|
|
name: "asc"
|
|
},
|
|
include: {
|
|
expenses: {
|
|
orderBy: {
|
|
createdAt: "desc"
|
|
},
|
|
include: {
|
|
creator: {
|
|
select: {
|
|
id: true,
|
|
username: true
|
|
}
|
|
},
|
|
approvals: {
|
|
orderBy: {
|
|
timestamp: "asc"
|
|
},
|
|
include: {
|
|
user: {
|
|
select: {
|
|
id: true,
|
|
username: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
orderBy: {
|
|
name: "asc"
|
|
}
|
|
});
|
|
|
|
const managedUsers = canManageUsers(viewer.role)
|
|
? await prisma.user.findMany({
|
|
include: {
|
|
workingGroup: {
|
|
select: {
|
|
name: true
|
|
}
|
|
},
|
|
_count: {
|
|
select: {
|
|
approvals: true,
|
|
createdExpenses: true
|
|
}
|
|
}
|
|
},
|
|
orderBy: [
|
|
{
|
|
role: "asc"
|
|
},
|
|
{
|
|
username: "asc"
|
|
}
|
|
]
|
|
})
|
|
: [];
|
|
|
|
const auditLogs = canManageUsers(viewer.role)
|
|
? await prisma.auditLog.findMany({
|
|
orderBy: {
|
|
createdAt: "desc"
|
|
},
|
|
take: 120,
|
|
include: {
|
|
actor: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
username: true,
|
|
role: true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
: [];
|
|
|
|
const serializedViewer: DashboardViewer = {
|
|
id: viewer.id,
|
|
name: viewer.username,
|
|
username: viewer.username,
|
|
role: viewer.role,
|
|
workingGroupId: viewer.workingGroupId,
|
|
approvalPermissions: normalizeApprovalPermissions(
|
|
viewer.role,
|
|
viewer.approvalPermissions,
|
|
viewer.approvalPreference
|
|
)
|
|
};
|
|
|
|
const serializedGroups: DashboardWorkingGroup[] = workingGroups.map((workingGroup) => ({
|
|
id: workingGroup.id,
|
|
name: workingGroup.name,
|
|
totalBudget: workingGroup.budgets.reduce((sum, budget) => sum + Number(budget.totalBudget), 0),
|
|
members: workingGroup.members.map((member) => ({
|
|
id: member.id,
|
|
name: member.username,
|
|
username: member.username,
|
|
role: member.role
|
|
})),
|
|
budgets: workingGroup.budgets.map((budget) => ({
|
|
id: budget.id,
|
|
name: budget.name,
|
|
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
|
|
}
|
|
}))
|
|
}))
|
|
}))
|
|
}));
|
|
|
|
const serializedUsers: DashboardManagedUser[] = managedUsers.map((user) => ({
|
|
id: user.id,
|
|
name: user.username,
|
|
username: user.username,
|
|
role: user.role,
|
|
workingGroupId: user.workingGroupId,
|
|
workingGroupName: user.workingGroup?.name ?? null,
|
|
approvalPermissions: normalizeApprovalPermissions(
|
|
user.role,
|
|
user.approvalPermissions,
|
|
user.approvalPreference
|
|
),
|
|
createdExpensesCount: user._count.createdExpenses,
|
|
approvalsCount: user._count.approvals
|
|
}));
|
|
|
|
const serializedPeriods: DashboardAccountingPeriod[] = accountingPeriods.map((period) => ({
|
|
id: period.id,
|
|
name: period.name,
|
|
startsAt: period.startsAt.toISOString(),
|
|
endsAt: period.endsAt.toISOString(),
|
|
isCurrent: period.isCurrent
|
|
}));
|
|
|
|
const serializedAuditLogs: DashboardAuditLog[] = auditLogs.map((entry) => ({
|
|
id: entry.id,
|
|
action: entry.action,
|
|
entityType: entry.entityType,
|
|
entityId: entry.entityId,
|
|
entityLabel: entry.entityLabel,
|
|
summary: entry.summary,
|
|
canRestore: Boolean(getRollbackMetadata(entry.metadata)),
|
|
createdAt: entry.createdAt.toISOString(),
|
|
actor: entry.actor
|
|
? {
|
|
id: entry.actor.id,
|
|
name: entry.actor.username,
|
|
username: entry.actor.username,
|
|
role: entry.actor.role
|
|
}
|
|
: null
|
|
}));
|
|
|
|
return (
|
|
<DashboardShell
|
|
viewer={serializedViewer}
|
|
workingGroups={serializedGroups}
|
|
managedUsers={serializedUsers}
|
|
auditLogs={serializedAuditLogs}
|
|
accountingPeriods={serializedPeriods}
|
|
currentPeriodId={currentPeriod.id}
|
|
approvalThreshold={toApprovalThresholdNumber(appSettings.approvalThreshold)}
|
|
/>
|
|
);
|
|
}
|