diff --git a/src/app/page.tsx b/src/app/page.tsx
index c527955..94151cd 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,9 +1,205 @@
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);
+ }
-export default async function Home() {
return (
-
- Next Finances
-
+
);
}
diff --git a/src/components/categoryPageClientComponents.tsx b/src/components/categoryPageClientComponents.tsx
index 6de7ec5..ac8b908 100644
--- a/src/components/categoryPageClientComponents.tsx
+++ b/src/components/categoryPageClientComponents.tsx
@@ -9,7 +9,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from
import { DataTable } from '@/components/ui/data-table';
import { columns } from '@/app/categories/columns';
import { z } from 'zod';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/dashboardPageClientComponents.tsx b/src/components/dashboardPageClientComponents.tsx
new file mode 100644
index 0000000..e45aaff
--- /dev/null
+++ b/src/components/dashboardPageClientComponents.tsx
@@ -0,0 +1,212 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { Category, Entity } from '@prisma/client';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import React from 'react';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
+
+export default function DashboardPageClientContent(
+ {
+ scope,
+ scopes,
+ income,
+ expenses,
+ balanceDevelopment,
+ categoryExpenses,
+ categoryPercentages,
+ entityExpenses,
+ entityPercentages,
+ }: {
+ scope: string,
+ scopes: string[],
+ income: string,
+ expenses: string,
+ balanceDevelopment: string,
+ categoryExpenses: {
+ category: Category,
+ value: string,
+ }[],
+ categoryPercentages: {
+ category: Category,
+ value: string,
+ }[],
+ entityExpenses: {
+ entity: Entity,
+ value: string,
+ }[],
+ entityPercentages: {
+ entity: Entity,
+ value: string,
+ }[],
+
+ },
+) {
+
+ const router = useRouter();
+
+ return (
+
+
+
+
+
Dashboard
+
+
+
+
+
+
+
+
+
+
+ Income
+
+
+
+ {income}
+
+
+
+
+
+
+ Expanses
+
+
+
+ {expenses}
+
+
+
+
+
+
+ Development
+
+
+
+ {balanceDevelopment}
+
+
+
+
+
+
+
+
+
+
+
+
+ Expenses
+ by category (%)
+
+
+ {
+ categoryPercentages.map(item => (
+
+
+
+
{item.category.name}
+
+
{item.value}%
+
+ ))
+ }
+
+
+
+
+
+ Expenses
+ by category (€)
+
+
+ {
+ categoryExpenses.map((item) => (
+
+
+
+
{item.category.name}
+
+
{item.value}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+
+ Expenses
+ by entity (%)
+
+
+
+ {
+ entityPercentages.map(item => (
+
+
+ {item.entity.name}
+
+
{item.value}%
+
+ ))
+ }
+
+
+
+
+
+ Expenses
+ by entity (€)
+
+
+ {
+ entityExpenses.map(item => (
+
+
+ {item.entity.name}
+
+
{item.value}
+
+ ))
+ }
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/entityPageClientComponents.tsx b/src/components/entityPageClientComponents.tsx
index 282dcc4..4f4a8bd 100644
--- a/src/components/entityPageClientComponents.tsx
+++ b/src/components/entityPageClientComponents.tsx
@@ -11,7 +11,7 @@ import { DataTable } from '@/components/ui/data-table';
import { columns } from '@/app/entities/columns';
import { z } from 'zod';
import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { Input } from '@/components/ui/input';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
diff --git a/src/components/form/categoryForm.tsx b/src/components/form/categoryForm.tsx
index 89c4bd2..2c2af97 100644
--- a/src/components/form/categoryForm.tsx
+++ b/src/components/form/categoryForm.tsx
@@ -7,7 +7,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import { Input } from '@/components/ui/input';
import React from 'react';
import { Button } from '@/components/ui/button';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/form/entityForm.tsx b/src/components/form/entityForm.tsx
index 85dbd8a..274c290 100644
--- a/src/components/form/entityForm.tsx
+++ b/src/components/form/entityForm.tsx
@@ -7,7 +7,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import { Input } from '@/components/ui/input';
import React from 'react';
import { Button } from '@/components/ui/button';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/form/paymentForm.tsx b/src/components/form/paymentForm.tsx
index ddf66dc..d6323dd 100644
--- a/src/components/form/paymentForm.tsx
+++ b/src/components/form/paymentForm.tsx
@@ -7,7 +7,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '
import { Input } from '@/components/ui/input';
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/form/signInForm.tsx b/src/components/form/signInForm.tsx
index 7d95e82..e4e96fa 100644
--- a/src/components/form/signInForm.tsx
+++ b/src/components/form/signInForm.tsx
@@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input';
import React from 'react';
import { Button } from '@/components/ui/button';
import { signInFormSchema } from '@/lib/form-schemas/signInFormSchema';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/form/signOutForm.tsx b/src/components/form/signOutForm.tsx
index b47649f..9dd51fe 100644
--- a/src/components/form/signOutForm.tsx
+++ b/src/components/form/signOutForm.tsx
@@ -1,6 +1,6 @@
'use client';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { Button } from '@/components/ui/button';
import React from 'react';
import { useRouter } from 'next/navigation';
diff --git a/src/components/form/signUpForm.tsx b/src/components/form/signUpForm.tsx
index ed53c50..2898733 100644
--- a/src/components/form/signUpForm.tsx
+++ b/src/components/form/signUpForm.tsx
@@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input';
import React from 'react';
import { Button } from '@/components/ui/button';
import { signUpFormSchema } from '@/lib/form-schemas/signUpFormSchema';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/paymentPageClientComponents.tsx b/src/components/paymentPageClientComponents.tsx
index 2308ce2..22fa031 100644
--- a/src/components/paymentPageClientComponents.tsx
+++ b/src/components/paymentPageClientComponents.tsx
@@ -7,7 +7,7 @@ import { Edit, Trash } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { DataTable } from '@/components/ui/data-table';
import { z } from 'zod';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { sonnerContent } from '@/components/ui/sonner';
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
index cd1cc44..6a9d8be 100644
--- a/src/components/ui/sonner.tsx
+++ b/src/components/ui/sonner.tsx
@@ -4,7 +4,7 @@ import { useTheme } from 'next-themes';
import { Toaster as Sonner } from 'sonner';
import { AlertCircle, CheckCircle, HelpCircle, XCircle } from 'lucide-react';
import React, { JSX } from 'react';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
type ToasterProps = React.ComponentProps
diff --git a/src/lib/actions/categoryCreateUpdate.ts b/src/lib/actions/categoryCreateUpdate.ts
index 4248bd3..a7796d5 100644
--- a/src/lib/actions/categoryCreateUpdate.ts
+++ b/src/lib/actions/categoryCreateUpdate.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
import { URL_SIGN_IN } from '@/lib/constants';
diff --git a/src/lib/actions/categoryDelete.ts b/src/lib/actions/categoryDelete.ts
index 1a7f55b..11cc496 100644
--- a/src/lib/actions/categoryDelete.ts
+++ b/src/lib/actions/categoryDelete.ts
@@ -1,4 +1,4 @@
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
import { URL_SIGN_IN } from '@/lib/constants';
diff --git a/src/lib/actions/entityCreateUpdate.ts b/src/lib/actions/entityCreateUpdate.ts
index 37027b6..3d3ca38 100644
--- a/src/lib/actions/entityCreateUpdate.ts
+++ b/src/lib/actions/entityCreateUpdate.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
diff --git a/src/lib/actions/entityDelete.ts b/src/lib/actions/entityDelete.ts
index 37ea1bd..cb2dd45 100644
--- a/src/lib/actions/entityDelete.ts
+++ b/src/lib/actions/entityDelete.ts
@@ -1,4 +1,4 @@
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
import { URL_SIGN_IN } from '@/lib/constants';
diff --git a/src/lib/actions/paymentCreateUpdate.ts b/src/lib/actions/paymentCreateUpdate.ts
index d49646d..6f889ef 100644
--- a/src/lib/actions/paymentCreateUpdate.ts
+++ b/src/lib/actions/paymentCreateUpdate.ts
@@ -1,5 +1,5 @@
import { z } from 'zod';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
import { URL_SIGN_IN } from '@/lib/constants';
diff --git a/src/lib/actions/paymentDelete.ts b/src/lib/actions/paymentDelete.ts
index 46ee086..3401e92 100644
--- a/src/lib/actions/paymentDelete.ts
+++ b/src/lib/actions/paymentDelete.ts
@@ -1,4 +1,4 @@
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { prismaClient } from '@/prisma';
import { getUser } from '@/auth';
import { URL_SIGN_IN } from '@/lib/constants';
diff --git a/src/lib/actions/signIn.ts b/src/lib/actions/signIn.ts
index 4027f75..5168179 100644
--- a/src/lib/actions/signIn.ts
+++ b/src/lib/actions/signIn.ts
@@ -3,7 +3,7 @@ import { Argon2id } from 'oslo/password';
import { lucia } from '@/auth';
import { cookies } from 'next/headers';
import { signInFormSchema } from '@/lib/form-schemas/signInFormSchema';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { URL_HOME } from '@/lib/constants';
import { prismaClient } from '@/prisma';
diff --git a/src/lib/actions/signOut.ts b/src/lib/actions/signOut.ts
index 4525a8e..de58559 100644
--- a/src/lib/actions/signOut.ts
+++ b/src/lib/actions/signOut.ts
@@ -1,6 +1,6 @@
import { getSession, lucia } from '@/auth';
import { cookies } from 'next/headers';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { URL_SIGN_IN } from '@/lib/constants';
export default async function signOut(): Promise {
diff --git a/src/lib/actions/signUp.ts b/src/lib/actions/signUp.ts
index 4f731b1..1b40aff 100644
--- a/src/lib/actions/signUp.ts
+++ b/src/lib/actions/signUp.ts
@@ -4,7 +4,7 @@ import { generateId } from 'lucia';
import { lucia } from '@/auth';
import { cookies } from 'next/headers';
import { signUpFormSchema } from '@/lib/form-schemas/signUpFormSchema';
-import { ActionResponse } from '@/lib/types/ActionResponse';
+import { ActionResponse } from '@/lib/types/actionResponse';
import { URL_HOME } from '@/lib/constants';
import { prismaClient } from '@/prisma';
diff --git a/src/lib/types/ActionResponse.ts b/src/lib/types/actionResponse.ts
similarity index 100%
rename from src/lib/types/ActionResponse.ts
rename to src/lib/types/actionResponse.ts
diff --git a/src/lib/types/scope.ts b/src/lib/types/scope.ts
new file mode 100644
index 0000000..a5f589a
--- /dev/null
+++ b/src/lib/types/scope.ts
@@ -0,0 +1,48 @@
+export enum ScopeType {
+ ThisMonth = 'This month',
+ LastMonth = 'Last month',
+ ThisYear = 'This year',
+ LastYear = 'Last year',
+}
+
+export class Scope {
+
+ public type: ScopeType;
+ public start: Date;
+ public end: Date;
+
+ private constructor(type: ScopeType, start: Date, end: Date) {
+ this.type = type;
+ this.start = start;
+ this.end = end;
+ }
+
+ static of(type: ScopeType): Scope {
+
+ let start: Date;
+ let end: Date;
+
+ const today = new Date();
+
+ switch (type) {
+ case ScopeType.ThisMonth:
+ start = new Date(today.getFullYear(), today.getMonth(), 1, 0, 0, 0, 0);
+ end = new Date(today.getFullYear(), today.getMonth() + 1, 0, 24, 0, 0, -1);
+ break;
+ case ScopeType.LastMonth:
+ start = new Date(today.getFullYear(), today.getMonth() - 1, 1, 0, 0, 0, 0);
+ end = new Date(today.getFullYear(), today.getMonth(), 0, 24, 0, 0, -1);
+ break;
+ case ScopeType.ThisYear:
+ start = new Date(today.getFullYear(), 0, 1, 0, 0, 0, 0);
+ end = new Date(today.getFullYear(), 11, 31, 24, 0, 0, -1);
+ break;
+ case ScopeType.LastYear:
+ start = new Date(today.getFullYear() - 1, 0, 1, 0, 0, 0, 0);
+ end = new Date(today.getFullYear() - 1, 11, 31, 24, 0, 0, -1);
+ break;
+ }
+
+ return new Scope(type, start, end);
+ }
+}