Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking β€œSign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Bank Statements #256

Merged
merged 4 commits into from
Jul 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
working import
Codehagen committed Jul 23, 2024
commit 9bff3db365df13e24fdf15c703bee11bff9b0490
56 changes: 56 additions & 0 deletions apps/www/src/actions/import-transactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use server";

import { prisma } from "@/lib/db";
import { getCurrentUser } from "@/lib/session";

interface Transaction {
date: string;
description: string;
amount: number;
category: string;
}

export async function importTransactions(
bankAccountId: string,
transactions: Transaction[],
) {
const user = await getCurrentUser();
if (!user) {
throw new Error("User not authenticated");
}

const currencyIso = "USD"; // Set the currency ISO code according to your requirements

// Fetch existing categories for the user
const categories = await prisma.category.findMany({
where: { userId: user.id },
});

const categoryMap = categories.reduce((acc, category) => {
acc[category.name] = category.id;
return acc;
}, {});

// Ensure the "Other" category is available
const otherCategoryId = categoryMap["Other"];

// Prepare transactions for insertion
const transactionsData = transactions.map((transaction) => ({
amount: transaction.amount,
date: new Date(transaction.date),
description: transaction.description,
categoryId: categoryMap[transaction.category] || otherCategoryId,
accountId: bankAccountId,
currencyIso: currencyIso,
}));

try {
await prisma.transaction.createMany({
data: transactionsData,
});
return { success: true };
} catch (error) {
console.error(error);
return { success: false, error: error.message };
}
}
6 changes: 4 additions & 2 deletions apps/www/src/app/(dashboard)/dashboard/banking/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import BankingDashboardDetails from "@/components/banking/BankingDashboardDetail
import { AddTransactionsButton } from "@/components/buttons/AddTransactionsButton";
import { DashboardHeader } from "@/components/dashboard/header";
import { DashboardShell } from "@/components/dashboard/shell";
import { CSVUploader } from "@/components/import/CsvImporter";
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";

export default async function BankAccountPage({
@@ -30,7 +31,6 @@ export default async function BankAccountPage({
const bankAccountDetails = await getBankAccountDetails(bankAccountId);
const transactions = await getBankAccountTransactions(bankAccountId);
console.log(transactions);
console.log(bankAccountDetails);

if (!bankAccountDetails) {
return (
@@ -48,7 +48,9 @@ export default async function BankAccountPage({
<DashboardHeader
heading={bankAccountDetails.name}
text="Here are your recent transactions"
/>
>
<CSVUploader bankAccountId={bankAccountId} />
</DashboardHeader>
<div>
{transactions.length === 0 ? (
<EmptyPlaceholder>
1 change: 0 additions & 1 deletion apps/www/src/app/(dashboard)/dashboard/banking/page.tsx
Original file line number Diff line number Diff line change
@@ -32,7 +32,6 @@ export default async function BankingPage() {
<DashboardShell>
<DashboardHeader heading="Banking" text="Overview off all your accounts ">
<AddAccountSheet currentPath="/banking" />
<CSVUploader />
</DashboardHeader>
<div>
{bankAccounts.length === 0 ? (
23 changes: 19 additions & 4 deletions apps/www/src/components/import/CsvImporter.tsx
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
@@ -16,9 +17,14 @@ import { cn } from "@/lib/utils";

import CSVParser from "./generic-csv-parser";

export function CSVUploader() {
export function CSVUploader({ bankAccountId }: { bankAccountId: string }) {
const [open, setOpen] = React.useState(false);

const handleTestFileImport = () => {
// Logic for importing a test file can be placed here
console.log("Test file import initiated");
};

return (
<Dialog>
<DialogTrigger asChild>
@@ -31,16 +37,25 @@ export function CSVUploader() {
Export transactions data as a file from bank's website.
</DialogDescription>
</DialogHeader>
<UploadForm />
<UploadForm bankAccountId={bankAccountId} />
<DialogFooter>
<Button onClick={handleTestFileImport}>Import Test File</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

function UploadForm({ className }: { className?: string }) {
function UploadForm({
className,
bankAccountId,
}: {
className?: string;
bankAccountId: string;
}) {
return (
<div className={cn("items-center py-4", className)}>
<CSVParser />
<CSVParser bankAccountId={bankAccountId} />
</div>
);
}
31 changes: 24 additions & 7 deletions apps/www/src/components/import/generic-csv-parser.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
"use client";

// theme CSS for React CSV Importer
import "react-csv-importer/dist/index.css";

import React, { use, useCallback } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { importTransactions } from "@/actions/import-transactions"; // Import the server action
import { Importer, ImporterField } from "react-csv-importer";
import { toast } from "sonner";

export default function CSVParser() {
export default function CSVParser({
bankAccountId,
}: {
bankAccountId: string;
}) {
const router = useRouter();
const searchParams = useSearchParams();

@@ -22,21 +26,34 @@ export default function CSVParser() {
[searchParams],
);

const handleData = async (rows) => {
const formattedRows = rows.map((row) => ({
date: row.date,
description: row.description,
amount: parseFloat(row.amount),
}));
const result = await importTransactions(bankAccountId, formattedRows);
if (result.success) {
toast.success("Transactions imported successfully!");
router.push(
"/dashboard/banking/" + "?" + createQueryString("step", "done"),
);
} else {
toast.error("Failed to import transactions: " + result.error);
}
};

return (
<div>
<Importer
dataHandler={async (rows) => {
// mock timeout to simulate processing
await new Promise((resolve) => setTimeout(resolve, 500));
}}
dataHandler={handleData}
chunkSize={10000}
defaultNoHeader={false}
restartable={true}
onStart={({ file, fields }) => {
console.log("starting import of file", file, "with fields", fields);
}}
onComplete={({ file, fields }) => {
// optional, invoked right after import is done (but user did not dismiss/reset the widget yet)
console.log("finished import of file", file, "with fields", fields);
toast.success("File imported with success!");
}}
11 changes: 11 additions & 0 deletions apps/www/src/components/import/testing.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"Date","Name","Amount"
"2016-01-01","Food","10.00"
"2016-01-01","Transport","5.00"
"2016-01-02","Food","20.00"
"2016-01-02","Transport","10.00"
"2016-01-03","Food","30.00"
"2016-01-03","Transport","15.00"
"2016-01-04","Food","40.00"
"2016-01-04","Transport","20.00"
"2016-01-05","Food","50.00"
"2016-01-05","Transport","25.00"