Skip to content

Commit

Permalink
Export transactions as CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
ymeiron committed Jan 10, 2024
1 parent 2263bb9 commit ffded9c
Showing 1 changed file with 85 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
selectCurrentUserPermissions,
selectGroupById,
selectSortedTransactions,
selectAccountIdToAccountMap,
} from "@abrechnung/redux";
import { Add, Clear } from "@mui/icons-material";
import SearchIcon from "@mui/icons-material/Search";
Expand All @@ -29,13 +30,14 @@ import {
useMediaQuery,
} from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { SaveAlt } from "@mui/icons-material";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { TagSelector } from "@/components/TagSelector";
import { PurchaseIcon, TransferIcon } from "@/components/style/AbrechnungIcons";
import { MobilePaper } from "@/components/style/mobile";
import { useTitle } from "@/core/utils";
import { selectGroupSlice, useAppDispatch, useAppSelector } from "@/store";
import { selectGroupSlice, useAppDispatch, useAppSelector, selectAccountSlice } from "@/store";
import { TransactionListItem } from "./TransactionListItem";
import { useTranslation } from "react-i18next";

Expand All @@ -46,6 +48,79 @@ interface Props {
const emptyList = [];
const MAX_ITEMS_PER_PAGE = 40;

function exportCsv(groupId) {
const accounts = useAppSelector((state) =>

Check failure on line 52 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

React Hook "useAppSelector" is called in function "exportCsv" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
selectAccountIdToAccountMap({ state: selectAccountSlice(state), groupId })
);

let transactionsSorted = useAppSelector((state) =>

Check failure on line 56 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

React Hook "useAppSelector" is called in function "exportCsv" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"
selectSortedTransactions({ state, groupId, searchTerm: "", sortMode: "billed_at", tags: [] })
);
transactionsSorted = [...transactionsSorted].reverse();

const accountIds = Object.keys(accounts).filter(id => !accounts[id].deleted);

Check failure on line 61 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

Replace `id` with `(id)`
const accountNames = accountIds.map(id => accounts[id].name);

Check failure on line 62 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

Replace `id` with `(id)`
const accountIndexById = Object.fromEntries(accountIds.map((id, index) => [id, index]));

let exportedCsv = "ID,Date,Payer,Name,Tags,Value," + accountNames.join(",") + ",Description\n";
for (const transaction of transactionsSorted) {
if (transaction.is_wip) continue;

const creditorId = Object.entries(transaction.creditor_shares)[0][0];
const creditorName = accounts[creditorId].name;
let tags = "";
if (transaction.tags.length == 1) {
tags = transaction.tags[0];
} else if (transaction.tags.length > 1) {
tags = JSON.stringify(transaction.tags.join(","));
}

let value = transaction.value;
let total = accountIds.map(() => 0);

Check failure on line 79 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

'total' is never reassigned. Use 'const' instead

if (transaction.type == "transfer") {
total[accountIndexById[creditorId]] = transaction.value;
const debitorId = Object.entries(transaction.debitor_shares)[0][0];
total[accountIndexById[debitorId]] = -transaction.value;
value = 0

Check failure on line 85 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

Insert `;`
} else {
let extraFromPositions = 0;
let totalPositions = 0;
for (let position of Object.values(transaction.positions)) {

Check failure on line 89 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

'position' is never reassigned. Use 'const' instead
const totalShares = Object.values(position.usages).reduce((a, b) => a + b, 0) + position.communist_shares;

Check failure on line 90 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

Insert `⏎···················`
for (let [accountId, shares] of Object.entries(position.usages)) {

Check failure on line 91 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

'accountId' is never reassigned. Use 'const' instead

Check failure on line 91 in frontend/apps/web/src/pages/transactions/TransactionList/TransactionList.tsx

View workflow job for this annotation

GitHub Actions / build_and_test_frontend / lint

'shares' is never reassigned. Use 'const' instead
let value = position.price*shares/totalShares;
total[accountIndexById[accountId]] += value;
totalPositions += value;
}
extraFromPositions += position.price*position.communist_shares/totalShares;
}
totalPositions += extraFromPositions;
const valueMinusPositions = transaction.value - totalPositions;
const totalShares = Object.values(transaction.debitor_shares).reduce((a, b) => a + b, 0);
const numberOfDebitors = Object.values(transaction.debitor_shares).length;
for (let [accountId, debitorShares] of Object.entries(transaction.debitor_shares)) {
total[accountIndexById[accountId]] += valueMinusPositions*debitorShares/totalShares + extraFromPositions/numberOfDebitors;
}
}
exportedCsv += `${transaction.id},${transaction.billed_at},${creditorName},${JSON.stringify(transaction.name)},${tags},${value.toFixed(2)},`;
exportedCsv += total.map((value) => value.toFixed(2)).join(",");
exportedCsv += "," + JSON.stringify(transaction.description) + "\n";
}
return exportedCsv;
}

function downloadCsv(str, filename) {
let blob = new Blob([str], {type: "text/csv;charset=utf-8"});
let url = URL.createObjectURL(blob);
let link = document.createElement("a");
link.download = filename;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

export const TransactionList: React.FC<Props> = ({ groupId }) => {
const { t } = useTranslation();
const theme: Theme = useTheme();
Expand Down Expand Up @@ -98,6 +173,8 @@ export const TransactionList: React.FC<Props> = ({ groupId }) => {

const handleChangeTagFilter = (newTags: string[]) => setTagFilter(newTags);

const exportedCsv = exportCsv(groupId);

return (
<>
<MobilePaper>
Expand Down Expand Up @@ -162,6 +239,13 @@ export const TransactionList: React.FC<Props> = ({ groupId }) => {
/>
</FormControl>
</Box>
<Box sx={{ display: "flex-item" }}>
<div style={{ padding: "8px" }}>
<Tooltip title="Export CSV">
<IconButton size="small" color="primary" onClick={() => {downloadCsv(exportedCsv, "transactions.csv");}}><SaveAlt /></IconButton>
</Tooltip>
</div>
</Box>
{!isSmallScreen && permissions.canWrite && (
<Box sx={{ display: "flex-item" }}>
<div style={{ padding: "8px" }}>
Expand Down

0 comments on commit ffded9c

Please sign in to comment.