mirror of
https://codeberg.org/MarkusThielker/finances.git
synced 2025-04-12 05:08:43 +00:00
206 lines
6.2 KiB
TypeScript
206 lines
6.2 KiB
TypeScript
import React from 'react';
|
|
import { Category, Entity, EntityType } from '@prisma/client';
|
|
import { Scope, ScopeType } from '@/lib/types/scope';
|
|
import { prismaClient } from '@/prisma';
|
|
import { getUser } from '@/auth';
|
|
import DashboardPageClient from '@/components/dashboardPageClientComponents';
|
|
|
|
export type CategoryNumber = {
|
|
category: Category,
|
|
value: number,
|
|
}
|
|
|
|
export type EntityNumber = {
|
|
entity: Entity,
|
|
value: number,
|
|
}
|
|
|
|
export default async function DashboardPage(props: { searchParams?: { scope: ScopeType } }) {
|
|
|
|
const user = await getUser();
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
const scope = Scope.of(props.searchParams?.scope || ScopeType.ThisMonth);
|
|
|
|
// get all payments in the current scope
|
|
const payments = await prismaClient.payment.findMany({
|
|
where: {
|
|
userId: user?.id,
|
|
date: {
|
|
gte: scope.start,
|
|
lte: scope.end,
|
|
},
|
|
},
|
|
include: {
|
|
payor: true,
|
|
payee: true,
|
|
category: true,
|
|
},
|
|
});
|
|
|
|
let income = 0;
|
|
let expenses = 0;
|
|
|
|
// sum up income
|
|
payments.filter(payment =>
|
|
payment.payor.type === EntityType.Entity &&
|
|
payment.payee.type === EntityType.Account,
|
|
).forEach(payment => income += payment.amount);
|
|
|
|
// sum up expenses
|
|
payments.filter(payment =>
|
|
payment.payor.type === EntityType.Account &&
|
|
payment.payee.type === EntityType.Entity,
|
|
).forEach(payment => expenses += payment.amount);
|
|
|
|
// ############################
|
|
// Expenses by category
|
|
// ############################
|
|
|
|
// init helper variables (category)
|
|
const categoryExpenses: CategoryNumber[] = [];
|
|
const otherCategory: CategoryNumber = {
|
|
category: {
|
|
id: 0,
|
|
userId: '',
|
|
name: 'Other',
|
|
color: '#888888',
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
},
|
|
value: 0,
|
|
};
|
|
|
|
// sum up expenses per category
|
|
payments.filter(payment =>
|
|
payment.payor.type === EntityType.Account &&
|
|
payment.payee.type === EntityType.Entity,
|
|
).forEach(payment => {
|
|
|
|
if (!payment.category) {
|
|
otherCategory.value += payment.amount;
|
|
return;
|
|
}
|
|
|
|
const categoryNumber = categoryExpenses.find(categoryNumber => categoryNumber.category.id === payment.category?.id);
|
|
if (categoryNumber) {
|
|
categoryNumber.value += payment.amount;
|
|
} else {
|
|
categoryExpenses.push({category: payment.category, value: payment.amount});
|
|
}
|
|
});
|
|
categoryExpenses.sort((a, b) => Number(b.value - a.value));
|
|
if (otherCategory.value > 0) {
|
|
categoryExpenses.push(otherCategory);
|
|
}
|
|
|
|
// ############################
|
|
// Expenses by entity
|
|
// ############################
|
|
|
|
// init helper variables (entity)
|
|
const entityExpenses: EntityNumber[] = [];
|
|
const otherEntity: EntityNumber = {
|
|
entity: {
|
|
id: 0,
|
|
userId: '',
|
|
name: 'Other',
|
|
type: EntityType.Entity,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
},
|
|
value: 0,
|
|
};
|
|
|
|
// sum up expenses per category
|
|
payments.filter(payment =>
|
|
payment.payor.type === EntityType.Account &&
|
|
payment.payee.type === EntityType.Entity,
|
|
).forEach(payment => {
|
|
|
|
// if (!payment.payee) {
|
|
// other.value += payment.amount
|
|
// return
|
|
// }
|
|
|
|
const entityNumber = entityExpenses.find(entityNumber => entityNumber.entity.id === payment.payee?.id);
|
|
if (entityNumber) {
|
|
entityNumber.value += payment.amount;
|
|
} else {
|
|
entityExpenses.push({entity: payment.payee, value: payment.amount});
|
|
}
|
|
});
|
|
entityExpenses.sort((a, b) => Number(b.value - a.value));
|
|
if (otherEntity.value > 0) {
|
|
entityExpenses.push(otherEntity);
|
|
}
|
|
|
|
// ############################
|
|
// Format data
|
|
// ############################
|
|
|
|
const balanceDevelopment = income - expenses;
|
|
const scopes = Object.values(ScopeType).map(scopeType => scopeType.toString());
|
|
|
|
const incomeFormat = new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
}).format(Number(income) / 100);
|
|
|
|
const expensesFormat = new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
}).format(Number(expenses) / 100);
|
|
|
|
const balanceDevelopmentFormat = new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
}).format(Number(balanceDevelopment) / 100);
|
|
|
|
const categoryExpensesFormat = categoryExpenses.map(categoryNumber => ({
|
|
category: categoryNumber.category,
|
|
value: new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
}).format(Number(categoryNumber.value) / 100),
|
|
}));
|
|
|
|
const categoryPercentages = categoryExpenses.map(categoryNumber => ({
|
|
category: categoryNumber.category,
|
|
value: amountToPercent(categoryNumber.value, expenses),
|
|
}));
|
|
|
|
const entityExpensesFormat = entityExpenses.map(entityNumber => ({
|
|
entity: entityNumber.entity,
|
|
value: new Intl.NumberFormat('de-DE', {
|
|
style: 'currency',
|
|
currency: 'EUR',
|
|
}).format(Number(entityNumber.value) / 100),
|
|
}));
|
|
|
|
const entityPercentages = entityExpenses.map(entityNumber => ({
|
|
entity: entityNumber.entity,
|
|
value: amountToPercent(entityNumber.value, expenses),
|
|
}));
|
|
|
|
function amountToPercent(amount: number, total: number): string {
|
|
return (Number(amount) / Number(total) * 100).toFixed(2);
|
|
}
|
|
|
|
return (
|
|
<DashboardPageClient
|
|
scope={scope.type}
|
|
scopes={scopes}
|
|
income={incomeFormat}
|
|
expenses={expensesFormat}
|
|
balanceDevelopment={balanceDevelopmentFormat}
|
|
categoryExpenses={categoryExpensesFormat}
|
|
categoryPercentages={categoryPercentages}
|
|
entityExpenses={entityExpensesFormat}
|
|
entityPercentages={entityPercentages}
|
|
className="flex flex-col justify-center space-y-4 p-10"
|
|
/>
|
|
);
|
|
}
|