-
Neil authored
* Reorganize and add graphs * Create Customizable Chart * Notes * Hide Menu update Donut * lint fixes * Organize Menus * Change Title * UI changes * UI updates * Add Data Table * Functionality additions and Privacy Filters * Date filters working and formatting changes * Fix default spreadsheet and add tableGraph * Integrate Summary Data and Split Legend * started adding functionality on charts * list fix * Enabling more graphs, fixing errors * Legend, interactions, Empty Rows Filter * fixes for EmptyRows/interactions/legends * formatting UI and filtering data * format date * fix errors * Fix Legend Order * lint fixes * Add tooltips * Feature Flag * fix overview card, fix offbudget checkbox * Revamped dataType, added scrollBars * data display adjustments * data spreadsheet updates/groups added to matrix * Add Category Selector * Add Labels Button * formatting fixes * Add Averages to dataTable * data bug fix * Added all type back in with exceptions * formatting * split assets/debts, add Uncategorized * bug fixes and UI updates * add scrollbars to table * formatting dataTable * tooltips, navigation and graph labels * Code clean-up and re-org * revert color change * Change labels name * organize files * code cleanup * Tooltip Colors * Descoping legend for future PR * descope legend & rename split * rename type variable to be more descriptive * adjustments for sankey and eslint merges * notes update * code review fixes * code fixes * fix date selections
Neil authored* Reorganize and add graphs * Create Customizable Chart * Notes * Hide Menu update Donut * lint fixes * Organize Menus * Change Title * UI changes * UI updates * Add Data Table * Functionality additions and Privacy Filters * Date filters working and formatting changes * Fix default spreadsheet and add tableGraph * Integrate Summary Data and Split Legend * started adding functionality on charts * list fix * Enabling more graphs, fixing errors * Legend, interactions, Empty Rows Filter * fixes for EmptyRows/interactions/legends * formatting UI and filtering data * format date * fix errors * Fix Legend Order * lint fixes * Add tooltips * Feature Flag * fix overview card, fix offbudget checkbox * Revamped dataType, added scrollBars * data display adjustments * data spreadsheet updates/groups added to matrix * Add Category Selector * Add Labels Button * formatting fixes * Add Averages to dataTable * data bug fix * Added all type back in with exceptions * formatting * split assets/debts, add Uncategorized * bug fixes and UI updates * add scrollbars to table * formatting dataTable * tooltips, navigation and graph labels * Code clean-up and re-org * revert color change * Change labels name * organize files * code cleanup * Tooltip Colors * Descoping legend for future PR * descope legend & rename split * rename type variable to be more descriptive * adjustments for sankey and eslint merges * notes update * code review fixes * code fixes * fix date selections
cash-flow-spreadsheet.tsx 5.74 KiB
import React from 'react';
import * as d from 'date-fns';
import q from 'loot-core/src/client/query-helpers';
import { send } from 'loot-core/src/platform/client/fetch';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToCurrency, integerToAmount } from 'loot-core/src/shared/util';
import AlignedText from '../../common/AlignedText';
import { runAll, indexCashFlow } from '../util';
export function simpleCashFlow(start, end) {
return async (spreadsheet, setData) => {
function makeQuery() {
return q('transactions')
.filter({
$and: [{ date: { $gte: start } }, { date: { $lte: end } }],
'account.offbudget': false,
$or: [
{
'payee.transfer_acct.offbudget': true,
'payee.transfer_acct': null,
},
],
})
.calculate({ $sum: '$amount' });
}
return runAll(
[
makeQuery().filter({ amount: { $gt: 0 } }),
makeQuery().filter({ amount: { $lt: 0 } }),
],
data => {
setData({
graphData: {
income: data[0],
expense: data[1],
},
});
},
);
};
}
export function cashFlowByDate(
start,
end,
isConcise,
conditions = [],
conditionsOp,
) {
return async (spreadsheet, setData) => {
let { filters } = await send('make-filters-from-conditions', {
conditions: conditions.filter(cond => !cond.customName),
});
const conditionsOpKey = conditionsOp === 'or' ? '$or' : '$and';
function makeQuery(where) {
let query = q('transactions')
.filter({
[conditionsOpKey]: [...filters],
})
.filter({
$and: [
{ date: { $transform: '$month', $gte: start } },
{ date: { $transform: '$month', $lte: end } },
],
'account.offbudget': false,
});
if (isConcise) {
return query
.groupBy([{ $month: '$date' }, 'payee.transfer_acct'])
.select([
{ date: { $month: '$date' } },
{ isTransfer: 'payee.transfer_acct' },
{ amount: { $sum: '$amount' } },
]);
}
return query
.groupBy(['date', 'payee.transfer_acct'])
.select([
'date',
{ isTransfer: 'payee.transfer_acct' },
{ amount: { $sum: '$amount' } },
]);
}
return runAll(
[
q('transactions')
.filter({
[conditionsOpKey]: filters,
date: { $transform: '$month', $lt: start },
'account.offbudget': false,
})
.calculate({ $sum: '$amount' }),
makeQuery('amount > 0').filter({ amount: { $gt: 0 } }),
makeQuery('amount < 0').filter({ amount: { $lt: 0 } }),
],
data => {
setData(recalculate(data, start, end, isConcise));
},
);
};
}
function recalculate(data, start, end, isConcise) {
let [startingBalance, income, expense] = data;
let convIncome = income.map(t => {
return { ...t, isTransfer: t.isTransfer !== null };
});
let convExpense = expense.map(t => {
return { ...t, isTransfer: t.isTransfer !== null };
});
const dates = isConcise
? monthUtils.rangeInclusive(
monthUtils.getMonth(start),
monthUtils.getMonth(end),
)
: monthUtils.dayRangeInclusive(start, end);
const incomes = indexCashFlow(convIncome, 'date', 'isTransfer');
const expenses = indexCashFlow(convExpense, 'date', 'isTransfer');
let balance = startingBalance;
let totalExpenses = 0;
let totalIncome = 0;
let totalTransfers = 0;
const graphData = dates.reduce(
(res, date) => {
let income = 0;
let expense = 0;
let creditTransfers = 0;
let debitTransfers = 0;
if (incomes[date]) {
income = !incomes[date].false ? 0 : incomes[date].false;
creditTransfers = !incomes[date].true ? 0 : incomes[date].true;
}
if (expenses[date]) {
expense = !expenses[date].false ? 0 : expenses[date].false;
debitTransfers = !expenses[date].true ? 0 : expenses[date].true;
}
totalExpenses += expense;
totalIncome += income;
balance += income + expense + creditTransfers + debitTransfers;
totalTransfers += creditTransfers + debitTransfers;
const x = d.parseISO(date);
const label = (
<div>
<div style={{ marginBottom: 10 }}>
<strong>
{d.format(x, isConcise ? 'MMMM yyyy' : 'MMMM d, yyyy')}
</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<AlignedText left="Income:" right={integerToCurrency(income)} />
<AlignedText left="Expenses:" right={integerToCurrency(expense)} />
<AlignedText
left="Change:"
right={<strong>{integerToCurrency(income + expense)}</strong>}
/>
{creditTransfers + debitTransfers !== 0 && (
<AlignedText
left="Transfers:"
right={integerToCurrency(creditTransfers + debitTransfers)}
/>
)}
<AlignedText left="Balance:" right={integerToCurrency(balance)} />
</div>
</div>
);
res.income.push({ x, y: integerToAmount(income) });
res.expenses.push({ x, y: integerToAmount(expense) });
res.balances.push({
x,
y: integerToAmount(balance),
premadeLabel: label,
amount: balance,
});
return res;
},
{ expenses: [], income: [], balances: [] },
);
const { balances } = graphData;
return {
graphData,
balance: balances[balances.length - 1].amount,
totalExpenses,
totalIncome,
totalTransfers,
totalChange: balances[balances.length - 1].amount - balances[0].amount,
};
}