mirror of
https://codeberg.org/MarkusThielker/finances.git
synced 2025-04-12 05:08:43 +00:00
N-FIN-12: add sample data generation for development (#14)
Resolves #12
This commit is contained in:
commit
c1359dbcf7
9 changed files with 323 additions and 4 deletions
24
package-lock.json
generated
24
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@tanstack/react-table": "^8.13.2",
|
"@tanstack/react-table": "^8.13.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
@ -1304,6 +1305,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-separator": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-slot": {
|
"node_modules/@radix-ui/react-slot": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@tanstack/react-table": "^8.13.2",
|
"@tanstack/react-table": "^8.13.2",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|
|
@ -7,6 +7,9 @@ import { Label } from '@/components/ui/label';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import SignOutForm from '@/components/form/signOutForm';
|
import SignOutForm from '@/components/form/signOutForm';
|
||||||
import { URL_SIGN_IN } from '@/lib/constants';
|
import { URL_SIGN_IN } from '@/lib/constants';
|
||||||
|
import GenerateSampleDataForm from '@/components/form/generateSampleDataForm';
|
||||||
|
import generateSampleData from '@/lib/actions/generateSampleData';
|
||||||
|
import { prismaClient } from '@/prisma';
|
||||||
|
|
||||||
export default async function AccountPage() {
|
export default async function AccountPage() {
|
||||||
|
|
||||||
|
@ -16,6 +19,28 @@ export default async function AccountPage() {
|
||||||
redirect(URL_SIGN_IN);
|
redirect(URL_SIGN_IN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let paymentCount = 0;
|
||||||
|
let entityCount = 0;
|
||||||
|
let categoryCount = 0;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
paymentCount = await prismaClient.payment.count({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
entityCount = await prismaClient.entity.count({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
categoryCount = await prismaClient.category.count({
|
||||||
|
where: {
|
||||||
|
userId: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<Card className="w-full max-w-md mt-12">
|
<Card className="w-full max-w-md mt-12">
|
||||||
|
@ -36,8 +61,33 @@ export default async function AccountPage() {
|
||||||
disabled
|
disabled
|
||||||
value={user?.username}/>
|
value={user?.username}/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-row items-center space-x-4">
|
||||||
|
<div>
|
||||||
|
<Label>Payments</Label>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
value={paymentCount}/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Entities</Label>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
value={entityCount}/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Categories</Label>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
value={categoryCount}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter className="space-x-4">
|
||||||
|
{
|
||||||
|
process.env.NODE_ENV === 'development' && (
|
||||||
|
<GenerateSampleDataForm onSubmit={generateSampleData}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
<SignOutForm onSubmit={signOut}/>
|
<SignOutForm onSubmit={signOut}/>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default function AuthLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center">
|
<div className="flex justify-center">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { URL_SIGN_UP } from '@/lib/constants';
|
||||||
|
|
||||||
export default async function SignInPage() {
|
export default async function SignInPage() {
|
||||||
return (
|
return (
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md mt-12">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Sign in</CardTitle>
|
<CardTitle>Sign in</CardTitle>
|
||||||
<CardDescription>Sign into your existing account</CardDescription>
|
<CardDescription>Sign into your existing account</CardDescription>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { URL_SIGN_IN } from '@/lib/constants';
|
||||||
|
|
||||||
export default async function SignUpPage() {
|
export default async function SignUpPage() {
|
||||||
return (
|
return (
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md mt-12">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Sign up</CardTitle>
|
<CardTitle>Sign up</CardTitle>
|
||||||
<CardDescription>Create a new account.</CardDescription>
|
<CardDescription>Create a new account.</CardDescription>
|
||||||
|
|
23
src/components/form/generateSampleDataForm.tsx
Normal file
23
src/components/form/generateSampleDataForm.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import React from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { sonnerContent } from '@/components/ui/sonner';
|
||||||
|
import { ActionResponse } from '@/lib/types/ActionResponse';
|
||||||
|
|
||||||
|
export default function GenerateSampleDataForm({onSubmit}: { onSubmit: () => Promise<ActionResponse> }) {
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const response = await onSubmit();
|
||||||
|
toast(sonnerContent(response));
|
||||||
|
router.refresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button className="w-full" variant="outline" onClick={handleSubmit}>Generate sample data</Button>
|
||||||
|
);
|
||||||
|
}
|
31
src/components/ui/separator.tsx
Normal file
31
src/components/ui/separator.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{className, orientation = 'horizontal', decorative = true, ...props},
|
||||||
|
ref,
|
||||||
|
) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
'shrink-0 bg-border',
|
||||||
|
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Separator };
|
190
src/lib/actions/generateSampleData.ts
Normal file
190
src/lib/actions/generateSampleData.ts
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
import { prismaClient } from '@/prisma';
|
||||||
|
import type { Category, Entity } from '@prisma/client';
|
||||||
|
import { EntityType } from '@prisma/client';
|
||||||
|
import { getUser } from '@/auth';
|
||||||
|
import { URL_SIGN_IN } from '@/lib/constants';
|
||||||
|
import { ActionResponse } from '@/lib/types/ActionResponse';
|
||||||
|
|
||||||
|
export default async function generateSampleData(): Promise<ActionResponse> {
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
const user = await getUser();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
type: 'error',
|
||||||
|
message: 'You must be logged in to create/update an category.',
|
||||||
|
redirect: URL_SIGN_IN,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories: create sample data
|
||||||
|
const categories: Category[] = await prismaClient.category.findMany({where: {userId: user.id}});
|
||||||
|
if (await prismaClient.category.count({where: {userId: user.id}}) == 0) {
|
||||||
|
|
||||||
|
console.log('Creating sample categories...');
|
||||||
|
|
||||||
|
categories.push(await prismaClient.category.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Groceries',
|
||||||
|
color: '#FFBEAC',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
categories.push(await prismaClient.category.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Drugstore items',
|
||||||
|
color: '#9CBCFF',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
categories.push(await prismaClient.category.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Going out',
|
||||||
|
color: '#F1ADFF',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
categories.push(await prismaClient.category.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Random stuff',
|
||||||
|
color: '#C1FFA9',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
categories.push(await prismaClient.category.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Salary',
|
||||||
|
color: '#FFF787',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Sample categories created.');
|
||||||
|
}
|
||||||
|
console.log(categories);
|
||||||
|
|
||||||
|
// Entities: create sample data
|
||||||
|
const entities: Entity[] = await prismaClient.entity.findMany({where: {userId: user.id}});
|
||||||
|
if (await prismaClient.entity.count({where: {userId: user.id}}) == 0) {
|
||||||
|
|
||||||
|
console.log('Creating sample entities...');
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Main Account',
|
||||||
|
type: EntityType.Account,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Company',
|
||||||
|
type: EntityType.Entity,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Supermarket 1',
|
||||||
|
type: EntityType.Entity,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Supermarket 2',
|
||||||
|
type: EntityType.Entity,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Supermarket 3',
|
||||||
|
type: EntityType.Entity,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
entities.push(await prismaClient.entity.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
name: 'Supermarket 4',
|
||||||
|
type: EntityType.Entity,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('Sample entities created.');
|
||||||
|
}
|
||||||
|
console.log(entities);
|
||||||
|
|
||||||
|
// Payments: create sample data
|
||||||
|
console.log('Creating sample payments...');
|
||||||
|
|
||||||
|
if (await prismaClient.payment.count({where: {userId: user.id}}) == 0) {
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(1);
|
||||||
|
date.setMonth(date.getMonth() - i);
|
||||||
|
|
||||||
|
await prismaClient.payment.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
amount: 200000,
|
||||||
|
date: date,
|
||||||
|
payorId: entities[1].id,
|
||||||
|
payeeId: entities[0].id,
|
||||||
|
categoryId: 5,
|
||||||
|
createdAt: date,
|
||||||
|
updatedAt: date,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let minAmount = 200; // 2€
|
||||||
|
let maxAmount = 3000; // 30€
|
||||||
|
let minPayee = entities[2].id;
|
||||||
|
let maxPayee = entities[entities.length - 1].id;
|
||||||
|
let minCategory = categories[0].id;
|
||||||
|
let maxCategory = categories[categories.length - 1].id;
|
||||||
|
let payments = 196;
|
||||||
|
|
||||||
|
for (let i = 0; i < payments; i++) {
|
||||||
|
|
||||||
|
const date = new Date(
|
||||||
|
new Date().getTime() - Math.floor(Math.random() * 10000000000));
|
||||||
|
|
||||||
|
await prismaClient.payment.create({
|
||||||
|
data: {
|
||||||
|
userId: user.id,
|
||||||
|
amount: Math.floor(
|
||||||
|
Math.random() * (maxAmount - minAmount) + minAmount),
|
||||||
|
date: date,
|
||||||
|
payorId: 1,
|
||||||
|
payeeId: Math.floor(
|
||||||
|
Math.random() * (maxPayee - minPayee) + minPayee),
|
||||||
|
categoryId: Math.floor(
|
||||||
|
Math.random() * (maxCategory - minCategory) + minCategory),
|
||||||
|
createdAt: date,
|
||||||
|
updatedAt: date,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Sample payments created.');
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'success',
|
||||||
|
message: 'Sample data created',
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue