add "last month" mode date filter to reports (#6222)

* feat: add "last month" mode to reports

* [autofix.ci] apply automated fixes

* chore: fix release note

* Update VRT screenshots

Auto-generated by VRT workflow

PR: #6222

* chore: UseCallBack instead of useEffect

* chore: respect call convention

* chore: simplified case and leverage useCallback

* chore: fix linter warning

* [autofix.ci] apply automated fixes

* chore: adding brace wrapping in case

* chore: clearer comment

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
This commit is contained in:
espege
2025-12-04 14:56:59 -05:00
committed by GitHub
parent 879fd1b054
commit 4bd0303b1a
14 changed files with 95 additions and 9 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -670,6 +670,16 @@ function QueryItem({
: 'sliding-window';
switch (item) {
case 'last-month': {
const prevMonth = monthUtils.subMonths(
monthUtils.currentMonth(),
1,
);
start = monthUtils.firstDayOfMonth(prevMonth);
end = monthUtils.lastDayOfMonth(prevMonth);
mode = quickSelectMode;
break;
}
case '1-month': {
const [startMonth, endMonth] = getLatestRange(0);
start = monthUtils.firstDayOfMonth(startMonth);
@@ -760,6 +770,7 @@ function QueryItem({
{ name: '1-year', text: t('1 year') },
Menu.line,
{ name: 'year-to-date', text: t('Year to date') },
{ name: 'last-month', text: t('Last month') },
{ name: 'last-year', text: t('Last year') },
{
name: 'prior-year-to-date',

View File

@@ -213,6 +213,25 @@ export function Header({
>
<Trans>Year to date</Trans>
</Button>
<Button
variant="bare"
onPress={() =>
onChangeDates(
...convertToMonth(
...getLiveRange(
'Last month',
earliestTransaction,
latestTransaction,
false,
firstDayOfWeekIdx,
),
'lastMonth',
),
)
}
>
<Trans>Last month</Trans>
</Button>
<Button
variant="bare"
onPress={() =>

View File

@@ -38,6 +38,10 @@ const currentIntervalOptions = [
description: t('Year to date'),
disableInclude: true,
},
{
description: t('Last month'),
disableInclude: true,
},
{
description: t('Last year'),
disableInclude: true,

View File

@@ -16,7 +16,7 @@ export function getLiveRange(
let dateEnd = latestTransaction;
const rangeName = ReportOptions.dateRangeMap.get(cond);
switch (rangeName) {
case 'yearToDate':
case 'yearToDate': {
[dateStart, dateEnd] = validateRange(
earliestTransaction,
latestTransaction,
@@ -24,7 +24,18 @@ export function getLiveRange(
monthUtils.currentDay(),
);
break;
case 'lastYear':
}
case 'lastMonth': {
const prevMonth = monthUtils.subMonths(monthUtils.currentMonth(), 1);
[dateStart, dateEnd] = validateRange(
earliestTransaction,
latestTransaction,
monthUtils.firstDayOfMonth(prevMonth),
monthUtils.lastDayOfMonth(prevMonth),
);
break;
}
case 'lastYear': {
[dateStart, dateEnd] = validateRange(
earliestTransaction,
latestTransaction,
@@ -35,7 +46,8 @@ export function getLiveRange(
'-31',
);
break;
case 'priorYearToDate':
}
case 'priorYearToDate': {
[dateStart, dateEnd] = validateRange(
earliestTransaction,
latestTransaction,
@@ -45,10 +57,12 @@ export function getLiveRange(
monthUtils.prevYear(monthUtils.currentDate(), 'yyyy-MM-dd'),
);
break;
case 'allTime':
}
case 'allTime': {
dateStart = earliestTransaction;
dateEnd = latestTransaction;
break;
}
default:
if (typeof rangeName === 'number') {
[dateStart, dateEnd] = getSpecificRange(

View File

@@ -1,4 +1,10 @@
import React, { useState, useEffect, useMemo, useRef } from 'react';
import React, {
useState,
useEffect,
useMemo,
useRef,
useCallback,
} from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
@@ -68,6 +74,16 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
const { t } = useTranslation();
const format = useFormat();
const getDefaultIntervalForMode = useCallback(
(mode: TimeFrame['mode']): 'Daily' | 'Weekly' | 'Monthly' | 'Yearly' => {
if (mode === 'lastMonth') {
return 'Weekly'; // For a single month, weekly interval provides better granularity than a single monthly data point
}
return 'Monthly';
},
[],
);
const accounts = useAccounts();
const {
conditions,
@@ -89,7 +105,20 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
const [start, setStart] = useState(monthUtils.currentMonth());
const [end, setEnd] = useState(monthUtils.currentMonth());
const [mode, setMode] = useState<TimeFrame['mode']>('sliding-window');
const [interval, setInterval] = useState(widget?.meta?.interval || 'Monthly');
const [interval, setInterval] = useState(
widget?.meta?.interval || getDefaultIntervalForMode(mode),
);
// Combined setter: set mode and update interval (unless interval was set in widget meta)
const setModeAndInterval = useCallback(
(newMode: TimeFrame['mode']) => {
setMode(newMode);
if (!widget?.meta?.interval) {
setInterval(getDefaultIntervalForMode(newMode));
}
},
[widget?.meta?.interval, getDefaultIntervalForMode],
);
const [latestTransaction, setLatestTransaction] = useState('');
const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx');
@@ -182,14 +211,14 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
);
setStart(initialStart);
setEnd(initialEnd);
setMode(initialMode);
setModeAndInterval(initialMode);
}
}, [latestTransaction, widget?.meta?.timeFrame]);
}, [latestTransaction, widget?.meta?.timeFrame, setModeAndInterval]);
function onChangeDates(start: string, end: string, mode: TimeFrame['mode']) {
setStart(start);
setEnd(end);
setMode(mode);
setModeAndInterval(mode);
}
async function onSaveWidget() {

View File

@@ -170,6 +170,8 @@ function timeFrameModeToCondition(mode: TimeFrame['mode']): string | null {
switch (mode) {
case 'full':
return 'All time';
case 'lastMonth':
return 'Last month';
case 'lastYear':
return 'Last year';
case 'yearToDate':

View File

@@ -8,6 +8,7 @@ export type TimeFrame = {
| 'sliding-window'
| 'static'
| 'full'
| 'lastMonth'
| 'lastYear'
| 'yearToDate'
| 'priorYearToDate';

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [espege]
---
Adds date filter "Last Month" on reports