mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-27 17:48:17 -05:00
feat(reports): extend report end date to the latest transaction date (#5753)
* add get-latest-transaction function to extend end dates beyond today * add release notes * yarn lint * fix dateEnd and lint warnings * change getFullRange to return all months * Update upcoming-release-notes/5753.md Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk> --------- Co-authored-by: Matt Fiddaman <github@m.fiddaman.uk>
This commit is contained in:
@@ -34,6 +34,7 @@ type HeaderProps = {
|
||||
show1Month?: boolean;
|
||||
allMonths: Array<{ name: string; pretty: string }>;
|
||||
earliestTransaction: string;
|
||||
latestTransaction: string;
|
||||
firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'];
|
||||
onChangeDates: (
|
||||
start: TimeFrame['start'],
|
||||
@@ -59,6 +60,7 @@ export function Header({
|
||||
show1Month,
|
||||
allMonths,
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
firstDayOfWeekIdx,
|
||||
onChangeDates,
|
||||
filters,
|
||||
@@ -129,6 +131,7 @@ export function Header({
|
||||
onChangeDates(
|
||||
...validateStart(
|
||||
allMonths[allMonths.length - 1].name,
|
||||
allMonths[0].name,
|
||||
newValue,
|
||||
end,
|
||||
),
|
||||
@@ -144,6 +147,7 @@ export function Header({
|
||||
onChangeDates(
|
||||
...validateEnd(
|
||||
allMonths[allMonths.length - 1].name,
|
||||
allMonths[0].name,
|
||||
start,
|
||||
newValue,
|
||||
),
|
||||
@@ -191,6 +195,7 @@ export function Header({
|
||||
...getLiveRange(
|
||||
'Year to date',
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
true,
|
||||
firstDayOfWeekIdx,
|
||||
),
|
||||
@@ -209,6 +214,7 @@ export function Header({
|
||||
...getLiveRange(
|
||||
'Last year',
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
false,
|
||||
firstDayOfWeekIdx,
|
||||
),
|
||||
@@ -227,6 +233,7 @@ export function Header({
|
||||
...getLiveRange(
|
||||
'Prior year to date',
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
false,
|
||||
firstDayOfWeekIdx,
|
||||
),
|
||||
@@ -241,7 +248,10 @@ export function Header({
|
||||
variant="bare"
|
||||
onPress={() =>
|
||||
onChangeDates(
|
||||
...getFullRange(allMonths[allMonths.length - 1].name),
|
||||
...getFullRange(
|
||||
allMonths[allMonths.length - 1].name,
|
||||
allMonths[0].name,
|
||||
),
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -63,6 +63,7 @@ type ReportSidebarProps = {
|
||||
defaultItems: (item: string) => void;
|
||||
defaultModeItems: (graph: string, item: string) => void;
|
||||
earliestTransaction: string;
|
||||
latestTransaction: string;
|
||||
firstDayOfWeekIdx: SyncedPrefs['firstDayOfWeekIdx'];
|
||||
isComplexCategoryCondition?: boolean;
|
||||
};
|
||||
@@ -93,6 +94,7 @@ export function ReportSidebar({
|
||||
defaultItems,
|
||||
defaultModeItems,
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
firstDayOfWeekIdx,
|
||||
isComplexCategoryCondition = false,
|
||||
}: ReportSidebarProps) {
|
||||
@@ -110,6 +112,7 @@ export function ReportSidebar({
|
||||
...getLiveRange(
|
||||
cond,
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
customReportItems.includeCurrentInterval,
|
||||
firstDayOfWeekIdx,
|
||||
),
|
||||
@@ -545,6 +548,7 @@ export function ReportSidebar({
|
||||
onChangeDates(
|
||||
...validateStart(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
newValue,
|
||||
customReportItems.endDate,
|
||||
customReportItems.interval,
|
||||
@@ -578,6 +582,7 @@ export function ReportSidebar({
|
||||
onChangeDates(
|
||||
...validateEnd(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
customReportItems.startDate,
|
||||
newValue,
|
||||
customReportItems.interval,
|
||||
|
||||
@@ -8,16 +8,18 @@ import { getSpecificRange, validateRange } from './reportRanges';
|
||||
export function getLiveRange(
|
||||
cond: string,
|
||||
earliestTransaction: string,
|
||||
latestTransaction: string,
|
||||
includeCurrentInterval: boolean,
|
||||
firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
|
||||
): [string, string, TimeFrame['mode']] {
|
||||
let dateStart = earliestTransaction;
|
||||
let dateEnd = monthUtils.currentDay();
|
||||
let dateEnd = latestTransaction;
|
||||
const rangeName = ReportOptions.dateRangeMap.get(cond);
|
||||
switch (rangeName) {
|
||||
case 'yearToDate':
|
||||
[dateStart, dateEnd] = validateRange(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
monthUtils.getYearStart(monthUtils.currentMonth()) + '-01',
|
||||
monthUtils.currentDay(),
|
||||
);
|
||||
@@ -25,6 +27,7 @@ export function getLiveRange(
|
||||
case 'lastYear':
|
||||
[dateStart, dateEnd] = validateRange(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
monthUtils.getYearStart(
|
||||
monthUtils.prevYear(monthUtils.currentMonth()),
|
||||
) + '-01',
|
||||
@@ -35,6 +38,7 @@ export function getLiveRange(
|
||||
case 'priorYearToDate':
|
||||
[dateStart, dateEnd] = validateRange(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
monthUtils.getYearStart(
|
||||
monthUtils.prevYear(monthUtils.currentMonth()),
|
||||
) + '-01',
|
||||
@@ -43,7 +47,7 @@ export function getLiveRange(
|
||||
break;
|
||||
case 'allTime':
|
||||
dateStart = earliestTransaction;
|
||||
dateEnd = monthUtils.currentDay();
|
||||
dateEnd = latestTransaction;
|
||||
break;
|
||||
default:
|
||||
if (typeof rangeName === 'number') {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { type SyncedPrefs } from 'loot-core/types/prefs';
|
||||
|
||||
export function validateStart(
|
||||
earliest: string,
|
||||
latest: string,
|
||||
start: string,
|
||||
end: string,
|
||||
interval?: string,
|
||||
@@ -35,6 +36,7 @@ export function validateStart(
|
||||
}
|
||||
return boundedRange(
|
||||
earliest,
|
||||
latest,
|
||||
dateStart,
|
||||
interval ? end : monthUtils.monthFromDate(end),
|
||||
interval,
|
||||
@@ -44,6 +46,7 @@ export function validateStart(
|
||||
|
||||
export function validateEnd(
|
||||
earliest: string,
|
||||
latest: string,
|
||||
start: string,
|
||||
end: string,
|
||||
interval?: string,
|
||||
@@ -75,6 +78,7 @@ export function validateEnd(
|
||||
}
|
||||
return boundedRange(
|
||||
earliest,
|
||||
latest,
|
||||
interval ? start : monthUtils.monthFromDate(start),
|
||||
dateEnd,
|
||||
interval,
|
||||
@@ -82,8 +86,12 @@ export function validateEnd(
|
||||
);
|
||||
}
|
||||
|
||||
export function validateRange(earliest: string, start: string, end: string) {
|
||||
const latest = monthUtils.currentDay();
|
||||
export function validateRange(
|
||||
earliest: string,
|
||||
latest: string,
|
||||
start: string,
|
||||
end: string,
|
||||
) {
|
||||
if (end > latest) {
|
||||
end = latest;
|
||||
}
|
||||
@@ -95,12 +103,12 @@ export function validateRange(earliest: string, start: string, end: string) {
|
||||
|
||||
function boundedRange(
|
||||
earliest: string,
|
||||
latest: string,
|
||||
start: string,
|
||||
end: string,
|
||||
interval?: string,
|
||||
firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'],
|
||||
): [string, string, 'static'] {
|
||||
let latest: string;
|
||||
switch (interval) {
|
||||
case 'Daily':
|
||||
latest = monthUtils.currentDay();
|
||||
@@ -115,7 +123,6 @@ function boundedRange(
|
||||
latest = monthUtils.currentDay();
|
||||
break;
|
||||
default:
|
||||
latest = monthUtils.currentMonth();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -154,8 +161,7 @@ export function getSpecificRange(
|
||||
return [dateStart, dateEnd, 'static'];
|
||||
}
|
||||
|
||||
export function getFullRange(start: string) {
|
||||
const end = monthUtils.currentMonth();
|
||||
export function getFullRange(start: string, end: string) {
|
||||
return [start, end, 'full'] as const;
|
||||
}
|
||||
|
||||
@@ -179,7 +185,7 @@ export function calculateTimeRange(
|
||||
const mode = timeFrame?.mode ?? defaultTimeFrame?.mode ?? 'sliding-window';
|
||||
|
||||
if (mode === 'full') {
|
||||
return getFullRange(start);
|
||||
return getFullRange(start, end);
|
||||
}
|
||||
if (mode === 'sliding-window') {
|
||||
const offset = monthUtils.differenceInCalendarMonths(end, start);
|
||||
|
||||
@@ -260,33 +260,47 @@ function CalendarInner({ widget, parameters }: CalendarInnerProps) {
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
try {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
let earliestMonth = trans
|
||||
? monthUtils.monthFromDate(parseISO(fromDateRepr(trans.date)))
|
||||
: currentMonth;
|
||||
const earliestTransaction = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
// Make sure the month selects are at least populates with a
|
||||
// year's worth of months. We can undo this when we have fancier
|
||||
// date selects.
|
||||
const yearAgo = monthUtils.subMonths(monthUtils.currentMonth(), 12);
|
||||
if (earliestMonth > yearAgo) {
|
||||
earliestMonth = yearAgo;
|
||||
}
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransaction(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, monthUtils.currentMonth())
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
}))
|
||||
.reverse();
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
let earliestMonth = earliestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
parseISO(fromDateRepr(earliestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
const latestMonth = latestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
parseISO(fromDateRepr(latestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
|
||||
setAllMonths(allMonths);
|
||||
} catch (error) {
|
||||
console.error('Error fetching earliest transaction:', error);
|
||||
// Make sure the month selects are at least populates with a
|
||||
// year's worth of months. We can undo this when we have fancier
|
||||
// date selects.
|
||||
const yearAgo = monthUtils.subMonths(latestMonth, 12);
|
||||
if (earliestMonth > yearAgo) {
|
||||
earliestMonth = yearAgo;
|
||||
}
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, latestMonth)
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
}))
|
||||
.reverse();
|
||||
|
||||
setAllMonths(allMonths);
|
||||
}
|
||||
run();
|
||||
}, [locale]);
|
||||
@@ -477,7 +491,8 @@ function CalendarInner({ widget, parameters }: CalendarInnerProps) {
|
||||
},
|
||||
);
|
||||
|
||||
const [earliestTransaction, _] = useState('');
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -512,6 +527,7 @@ function CalendarInner({ widget, parameters }: CalendarInnerProps) {
|
||||
start={start}
|
||||
end={end}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
mode={mode}
|
||||
onChangeDates={onChangeDates}
|
||||
|
||||
@@ -127,13 +127,27 @@ function CashFlowInner({ widget }: CashFlowInnerProps) {
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
const earliestMonth = trans
|
||||
? monthUtils.monthFromDate(d.parseISO(trans.date))
|
||||
const earliestTransaction = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransaction(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const earliestMonth = earliestTransaction
|
||||
? monthUtils.monthFromDate(d.parseISO(earliestTransaction.date))
|
||||
: monthUtils.currentMonth();
|
||||
const latestMonth = latestTransaction
|
||||
? monthUtils.monthFromDate(d.parseISO(latestTransaction.date))
|
||||
: monthUtils.currentMonth();
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, monthUtils.currentMonth())
|
||||
.rangeInclusive(earliestMonth, latestMonth)
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
@@ -206,7 +220,8 @@ function CashFlowInner({ widget }: CashFlowInnerProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const [earliestTransaction, _] = useState('');
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx');
|
||||
const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0';
|
||||
|
||||
@@ -248,6 +263,7 @@ function CashFlowInner({ widget }: CashFlowInnerProps) {
|
||||
start={start}
|
||||
end={end}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
mode={mode}
|
||||
show1Month
|
||||
|
||||
@@ -270,6 +270,7 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
monthUtils.rangeInclusive(startDate, endDate),
|
||||
);
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
const [report, setReport] = useState(loadReport);
|
||||
const [savedStatus, setSavedStatus] = useState(
|
||||
session.savedStatus ?? (initialReport ? 'saved' : 'new'),
|
||||
@@ -296,26 +297,17 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransaction(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const fromDate =
|
||||
interval === 'Weekly'
|
||||
? 'dayFromDate'
|
||||
: (((ReportOptions.intervalMap.get(interval) || 'Day').toLowerCase() +
|
||||
'FromDate') as 'dayFromDate' | 'monthFromDate' | 'yearFromDate');
|
||||
|
||||
const currentDate =
|
||||
interval === 'Weekly'
|
||||
? 'currentDay'
|
||||
: (('current' +
|
||||
(ReportOptions.intervalMap.get(interval) || 'Day')) as
|
||||
| 'currentDay'
|
||||
| 'currentMonth'
|
||||
| 'currentYear');
|
||||
|
||||
const currentInterval =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.currentWeek(firstDayOfWeekIdx)
|
||||
: monthUtils[currentDate]();
|
||||
|
||||
const earliestInterval =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.weekFromDate(
|
||||
@@ -334,16 +326,30 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
),
|
||||
);
|
||||
|
||||
const latestInterval =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.weekFromDate(
|
||||
d.parseISO(
|
||||
fromDateRepr(latestTransaction.date || monthUtils.currentDay()),
|
||||
),
|
||||
firstDayOfWeekIdx,
|
||||
)
|
||||
: monthUtils[fromDate](
|
||||
d.parseISO(
|
||||
fromDateRepr(latestTransaction.date || monthUtils.currentDay()),
|
||||
),
|
||||
);
|
||||
|
||||
const allIntervals =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.weekRangeInclusive(
|
||||
earliestInterval,
|
||||
currentInterval,
|
||||
latestInterval,
|
||||
firstDayOfWeekIdx,
|
||||
)
|
||||
: monthUtils[
|
||||
ReportOptions.intervalRange.get(interval) || 'rangeInclusive'
|
||||
](earliestInterval, currentInterval);
|
||||
](earliestInterval, latestInterval);
|
||||
|
||||
const allIntervalsMap = allIntervals
|
||||
.map((inter: string) => ({
|
||||
@@ -364,6 +370,7 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
includeCurrentInterval,
|
||||
firstDayOfWeekIdx,
|
||||
);
|
||||
@@ -804,6 +811,7 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
defaultItems={defaultItems}
|
||||
defaultModeItems={defaultModeItems}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
isComplexCategoryCondition={isComplexCategoryCondition}
|
||||
/>
|
||||
|
||||
@@ -69,6 +69,7 @@ function CustomReportListCardsInner({
|
||||
|
||||
const [nameMenuOpen, setNameMenuOpen] = useState(false);
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
|
||||
const payees = usePayees();
|
||||
const accounts = useAccounts();
|
||||
@@ -85,8 +86,14 @@ function CustomReportListCardsInner({
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(trans ? trans.date : monthUtils.currentDay());
|
||||
const earliestTrans = await send('get-earliest-transaction');
|
||||
const latestTrans = await send('get-latest-transaction');
|
||||
setEarliestTransaction(
|
||||
earliestTrans ? earliestTrans.date : monthUtils.currentDay(),
|
||||
);
|
||||
setLatestTransaction(
|
||||
latestTrans ? latestTrans.date : monthUtils.currentDay(),
|
||||
);
|
||||
}
|
||||
run();
|
||||
}, []);
|
||||
@@ -172,6 +179,7 @@ function CustomReportListCardsInner({
|
||||
accounts={accounts}
|
||||
categories={categories}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
showTooltip={!isEditing}
|
||||
/>
|
||||
|
||||
@@ -70,6 +70,7 @@ export function GetCardData({
|
||||
accounts,
|
||||
categories,
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
firstDayOfWeekIdx,
|
||||
showTooltip,
|
||||
}: {
|
||||
@@ -78,6 +79,7 @@ export function GetCardData({
|
||||
accounts: AccountEntity[];
|
||||
categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] };
|
||||
earliestTransaction: string;
|
||||
latestTransaction: string;
|
||||
firstDayOfWeekIdx?: SyncedPrefs['firstDayOfWeekIdx'];
|
||||
showTooltip?: boolean;
|
||||
}) {
|
||||
@@ -90,6 +92,7 @@ export function GetCardData({
|
||||
const [dateStart, dateEnd] = getLiveRange(
|
||||
report.dateRange,
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
report.includeCurrentInterval,
|
||||
firstDayOfWeekIdx,
|
||||
);
|
||||
|
||||
@@ -125,22 +125,40 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
|
||||
const data = useReport('net_worth', reportParams);
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
const earliestTransaction = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransaction(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
let earliestMonth = trans
|
||||
? monthUtils.monthFromDate(d.parseISO(fromDateRepr(trans.date)))
|
||||
let earliestMonth = earliestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
d.parseISO(fromDateRepr(earliestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
const latestMonth = latestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
d.parseISO(fromDateRepr(latestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
|
||||
// Make sure the month selects are at least populates with a
|
||||
// year's worth of months. We can undo this when we have fancier
|
||||
// date selects.
|
||||
const yearAgo = monthUtils.subMonths(monthUtils.currentMonth(), 12);
|
||||
const yearAgo = monthUtils.subMonths(latestMonth, 12);
|
||||
if (earliestMonth > yearAgo) {
|
||||
earliestMonth = yearAgo;
|
||||
}
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, monthUtils.currentMonth())
|
||||
.rangeInclusive(earliestMonth, latestMonth)
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
@@ -206,7 +224,8 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
|
||||
});
|
||||
};
|
||||
|
||||
const [earliestTransaction, _] = useState('');
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
|
||||
if (!allMonths || !data) {
|
||||
return null;
|
||||
@@ -244,6 +263,7 @@ function NetWorthInner({ widget }: NetWorthInnerProps) {
|
||||
start={start}
|
||||
end={end}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
mode={mode}
|
||||
onChangeDates={onChangeDates}
|
||||
|
||||
@@ -100,22 +100,27 @@ function SpendingInternal({ widget }: SpendingInternalProps) {
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
const earliestTrans = await send('get-earliest-transaction');
|
||||
const latestTrans = await send('get-latest-transaction');
|
||||
|
||||
let earliestMonth = trans
|
||||
? monthUtils.monthFromDate(d.parseISO(fromDateRepr(trans.date)))
|
||||
: monthUtils.currentMonth();
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
let earliestMonth = earliestTrans
|
||||
? monthUtils.monthFromDate(d.parseISO(fromDateRepr(earliestTrans.date)))
|
||||
: currentMonth;
|
||||
const latestMonth = latestTrans
|
||||
? monthUtils.monthFromDate(d.parseISO(fromDateRepr(latestTrans.date)))
|
||||
: currentMonth;
|
||||
|
||||
// Make sure the month selects are at least populates with a
|
||||
// year's worth of months. We can undo this when we have fancier
|
||||
// date selects.
|
||||
const yearAgo = monthUtils.subMonths(monthUtils.currentMonth(), 12);
|
||||
const yearAgo = monthUtils.subMonths(latestMonth, 12);
|
||||
if (earliestMonth > yearAgo) {
|
||||
earliestMonth = yearAgo;
|
||||
}
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, monthUtils.currentMonth())
|
||||
.rangeInclusive(earliestMonth, latestMonth)
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
|
||||
@@ -160,28 +160,47 @@ function SummaryInner({ widget }: SummaryInnerProps) {
|
||||
}>
|
||||
>([]);
|
||||
|
||||
const [earliestTransaction, _] = useState('');
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
const [_firstDayOfWeekIdx] = useSyncedPref('firstDayOfWeekIdx');
|
||||
const firstDayOfWeekIdx = _firstDayOfWeekIdx || '0';
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const trans = await send('get-earliest-transaction');
|
||||
const earliestTransaction = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransaction(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const currentMonth = monthUtils.currentMonth();
|
||||
let earliestMonth = trans
|
||||
? monthUtils.monthFromDate(parseISO(fromDateRepr(trans.date)))
|
||||
let earliestMonth = earliestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
parseISO(fromDateRepr(earliestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
const latestMonth = latestTransaction
|
||||
? monthUtils.monthFromDate(
|
||||
parseISO(fromDateRepr(latestTransaction.date)),
|
||||
)
|
||||
: currentMonth;
|
||||
|
||||
// Make sure the month selects are at least populates with a
|
||||
// year's worth of months. We can undo this when we have fancier
|
||||
// date selects.
|
||||
const yearAgo = monthUtils.subMonths(monthUtils.currentMonth(), 12);
|
||||
const yearAgo = monthUtils.subMonths(latestMonth, 12);
|
||||
if (earliestMonth > yearAgo) {
|
||||
earliestMonth = yearAgo;
|
||||
}
|
||||
|
||||
const allMonths = monthUtils
|
||||
.rangeInclusive(earliestMonth, monthUtils.currentMonth())
|
||||
.rangeInclusive(earliestMonth, latestMonth)
|
||||
.map(month => ({
|
||||
name: month,
|
||||
pretty: monthUtils.format(month, 'MMMM, yyyy', locale),
|
||||
@@ -305,6 +324,7 @@ function SummaryInner({ widget }: SummaryInnerProps) {
|
||||
start={start}
|
||||
end={end}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
mode={mode}
|
||||
onChangeDates={onChangeDates}
|
||||
|
||||
@@ -26,6 +26,7 @@ export type TransactionHandlers = {
|
||||
'transactions-export-query': typeof exportTransactionsQuery;
|
||||
'transactions-merge': typeof mergeTransactions;
|
||||
'get-earliest-transaction': typeof getEarliestTransaction;
|
||||
'get-latest-transaction': typeof getLatestTransaction;
|
||||
};
|
||||
|
||||
async function handleBatchUpdateTransactions({
|
||||
@@ -104,6 +105,17 @@ async function getEarliestTransaction() {
|
||||
return data[0] || null;
|
||||
}
|
||||
|
||||
async function getLatestTransaction() {
|
||||
const { data } = await aqlQuery(
|
||||
q('transactions')
|
||||
.options({ splits: 'none' })
|
||||
.orderBy({ date: 'desc' })
|
||||
.select('*')
|
||||
.limit(1),
|
||||
);
|
||||
return data[0] || null;
|
||||
}
|
||||
|
||||
export const app = createApp<TransactionHandlers>();
|
||||
|
||||
app.method(
|
||||
@@ -119,3 +131,4 @@ app.method('transactions-parse-file', mutator(parseTransactionsFile));
|
||||
app.method('transactions-export', mutator(exportTransactions));
|
||||
app.method('transactions-export-query', mutator(exportTransactionsQuery));
|
||||
app.method('get-earliest-transaction', getEarliestTransaction);
|
||||
app.method('get-latest-transaction', getLatestTransaction);
|
||||
|
||||
6
upcoming-release-notes/5753.md
Normal file
6
upcoming-release-notes/5753.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [csenel]
|
||||
---
|
||||
|
||||
Extend report end date to the latest transaction date
|
||||
Reference in New Issue
Block a user