Skip to content

Commit

Permalink
Add option for averaging category spending report since first transac…
Browse files Browse the repository at this point in the history
…tion

Signed-off-by: Johannes Löthberg <[email protected]>
  • Loading branch information
kyrias committed Jul 21, 2023
1 parent f306787 commit 8a38c64
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ function CategoryAverage({ getCategories }) {
return null;
}

const numberOfMonthsOptions = [
{ value: 1, description: 'No averaging' },
{ value: 3, description: '3 months' },
{ value: 6, description: '6 months' },
{ value: 12, description: '12 months' },
{ value: -1, description: 'All time' },
];
const numberOfMonthsLine = numberOfMonthsOptions.length - 1;

return (
<View style={[styles.page, { minWidth: 650, overflow: 'hidden' }]}>
<Header
Expand All @@ -98,7 +107,8 @@ function CategoryAverage({ getCategories }) {
categoryGroups={categoryGroups}
onChangeCategory={setSelectedCategoryId}
numberOfMonths={numberOfMonthsAverage}
numberOfMonthsOptions={[1, 3, 6, 12]}
numberOfMonthsOptions={numberOfMonthsOptions}
numberOfMonthsLine={numberOfMonthsLine}
onChangeNumberOfMonths={setNumberOfMonthsAverage}
/>

Expand Down
6 changes: 4 additions & 2 deletions packages/desktop-client/src/components/reports/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function Header({
onChangeCategory,
numberOfMonths,
numberOfMonthsOptions,
numberOfMonthsLine,
onChangeNumberOfMonths,
}) {
return (
Expand Down Expand Up @@ -123,9 +124,10 @@ function Header({
onChange={onChangeNumberOfMonths}
value={numberOfMonths}
options={numberOfMonthsOptions.map(number => [
number,
number === 1 ? 'No averaging' : `${number} months`,
number.value,
number.description,
])}
line={numberOfMonthsLine}
/>
</View>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import * as d from 'date-fns';

import { rolloverBudget } from 'loot-core/src/client/queries';
import q, { runQuery } from 'loot-core/src/client/query-helpers';
import * as monthUtils from 'loot-core/src/shared/months';
import { integerToAmount, integerToCurrency } from 'loot-core/src/shared/util';

Expand Down Expand Up @@ -41,10 +42,32 @@ export default function createSpreadsheet(
return;
}

const months = monthUtils.rangeInclusive(
monthUtils.subMonths(start, numberOfMonthsAverage),
end,
);
let months;

// `numberOfMonthsAverage` is set to -1 to mean "all time."
if (numberOfMonthsAverage === -1) {
const firstTransaction = await runQuery(
q('transactions')
.filter({ category: categoryId })
.orderBy({ date: 'asc' })
.limit(1)
.select('date'),
);
if (firstTransaction.data.length === 0) {
setData([]);
return;
}

months = monthUtils.rangeInclusive(
monthUtils.monthFromDate(firstTransaction.data[0].date),
end,
);
} else {
months = monthUtils.rangeInclusive(
monthUtils.subMonths(start, numberOfMonthsAverage),
end,
);
}

const data: MonthlyBudget[] = await Promise.all(
months.map(async month => {
Expand Down Expand Up @@ -88,54 +111,65 @@ function recalculate(
);

const months = monthUtils.rangeInclusive(start, end);
return months.reduce((arr, month) => {
const thisMonth = budgetPerMonth[month];
const x = d.parseISO(`${month}-01`);

const sumAmounts = [];
for (let i = 0; i < numberOfMonthsAverage; i++) {
sumAmounts.push(budgetPerMonth[monthUtils.subMonths(month, i)].sumAmount);
}
const average = sumAmounts.reduce((a, b) => a + b) / sumAmounts.length;

const label = (
<div>
<div style={{ marginBottom: 10 }}>
<strong>{d.format(x, 'MMMM yyyy')}</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<AlignedText
left="Average:"
right={integerToCurrency(Math.round(average))}
/>
<AlignedText
left="Budgeted:"
right={integerToCurrency(thisMonth.budgeted)}
/>
<AlignedText
left="Spent:"
right={integerToCurrency(thisMonth.sumAmount)}
/>
<AlignedText
left="Balance:"
right={integerToCurrency(thisMonth.balance)}
/>
const [averagedData, _] = months.reduce(
([arr, idx], month) => {
const thisMonth = budgetPerMonth[month];
const x = d.parseISO(`${month}-01`);

const months = numberOfMonthsAverage === -1 ? idx : numberOfMonthsAverage;
const sumAmounts = [];
for (let i = 0; i < months; i++) {
sumAmounts.push(
budgetPerMonth[monthUtils.subMonths(month, i)].sumAmount,
);
}
const average = sumAmounts.reduce((a, b) => a + b) / sumAmounts.length;

const label = (
<div>
<div style={{ marginBottom: 10 }}>
<strong>{d.format(x, 'MMMM yyyy')}</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<AlignedText
left="Average:"
right={integerToCurrency(Math.round(average))}
/>
<AlignedText
left="Budgeted:"
right={integerToCurrency(thisMonth.budgeted)}
/>
<AlignedText
left="Spent:"
right={integerToCurrency(thisMonth.sumAmount)}
/>
<AlignedText
left="Balance:"
right={integerToCurrency(thisMonth.balance)}
/>
</div>
</div>
</div>
);
);

return [
[
...arr,
{
x,
y: integerToAmount(Math.round(average)),
premadeLabel: label,

month,
average,
budgeted: thisMonth.budgeted,
total: thisMonth.sumAmount,
},
],
idx + 1,
];
},
[[], 1],
);

return [
...arr,
{
x,
y: integerToAmount(Math.round(average)),
premadeLabel: label,

month,
average,
budgeted: thisMonth.budgeted,
total: thisMonth.sumAmount,
},
];
}, []);
return averagedData;
}

0 comments on commit 8a38c64

Please sign in to comment.