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>
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 79 KiB |
@@ -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',
|
||||
|
||||
@@ -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={() =>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -8,6 +8,7 @@ export type TimeFrame = {
|
||||
| 'sliding-window'
|
||||
| 'static'
|
||||
| 'full'
|
||||
| 'lastMonth'
|
||||
| 'lastYear'
|
||||
| 'yearToDate'
|
||||
| 'priorYearToDate';
|
||||
|
||||
6
upcoming-release-notes/6222.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [espege]
|
||||
---
|
||||
|
||||
Adds date filter "Last Month" on reports
|
||||