mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 06:02:22 -05:00
Fix react-hooks/exhaustive-deps in CustomReport (#6867)
* Fix react-hooks/exhaustive-deps in CustomReport * Add release notes for PR #6867 * Fix typecheck errors * [autofix.ci] apply automated fixes * Change category to Maintenance and update description * [autofix.ci] apply automated fixes --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
2fb98156f6
commit
b271de32b6
@@ -19,6 +19,7 @@ import {
|
||||
type CustomReportEntity,
|
||||
type sortByOpType,
|
||||
type TimeFrame,
|
||||
type TransactionEntity,
|
||||
} from 'loot-core/types/models';
|
||||
import { type SyncedPrefs } from 'loot-core/types/prefs';
|
||||
|
||||
@@ -39,20 +40,26 @@ type ReportSidebarProps = {
|
||||
categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] };
|
||||
dateRangeLine: number;
|
||||
allIntervals: { name: string; pretty: string }[];
|
||||
setDateRange: (value: string) => void;
|
||||
setGraphType: (value: string) => void;
|
||||
setGroupBy: (value: string) => void;
|
||||
setInterval: (value: string) => void;
|
||||
setBalanceType: (value: string) => void;
|
||||
setSortBy: (value: string) => void;
|
||||
setMode: (value: string) => void;
|
||||
setIsDateStatic: (value: boolean) => void;
|
||||
setShowEmpty: (value: boolean) => void;
|
||||
setShowOffBudget: (value: boolean) => void;
|
||||
setShowHiddenCategories: (value: boolean) => void;
|
||||
setShowUncategorized: (value: boolean) => void;
|
||||
setTrimIntervals: (value: boolean) => void;
|
||||
setIncludeCurrentInterval: (value: boolean) => void;
|
||||
setDateRange: (value: CustomReportEntity['dateRange']) => void;
|
||||
setGraphType: (value: CustomReportEntity['graphType']) => void;
|
||||
setGroupBy: (value: CustomReportEntity['groupBy']) => void;
|
||||
setInterval: (value: CustomReportEntity['interval']) => void;
|
||||
setBalanceType: (value: CustomReportEntity['balanceType']) => void;
|
||||
setSortBy: (value: CustomReportEntity['sortBy']) => void;
|
||||
setMode: (value: CustomReportEntity['mode']) => void;
|
||||
setIsDateStatic: (value: CustomReportEntity['isDateStatic']) => void;
|
||||
setShowEmpty: (value: CustomReportEntity['showEmpty']) => void;
|
||||
setShowOffBudget: (value: CustomReportEntity['showOffBudget']) => void;
|
||||
setShowHiddenCategories: (
|
||||
value: CustomReportEntity['showHiddenCategories'],
|
||||
) => void;
|
||||
setShowUncategorized: (
|
||||
value: CustomReportEntity['showUncategorized'],
|
||||
) => void;
|
||||
setTrimIntervals: (value: CustomReportEntity['trimIntervals']) => void;
|
||||
setIncludeCurrentInterval: (
|
||||
value: CustomReportEntity['includeCurrentInterval'],
|
||||
) => void;
|
||||
setSelectedCategories: (value: CategoryEntity[]) => void;
|
||||
onChangeDates: (
|
||||
dateStart: string,
|
||||
@@ -63,8 +70,8 @@ type ReportSidebarProps = {
|
||||
disabledItems: (type: string) => string[];
|
||||
defaultItems: (item: string) => void;
|
||||
defaultModeItems: (graph: string, item: string) => void;
|
||||
earliestTransaction: string;
|
||||
latestTransaction: string;
|
||||
earliestTransaction: TransactionEntity['date'];
|
||||
latestTransaction: TransactionEntity['date'];
|
||||
firstDayOfWeekIdx: SyncedPrefs['firstDayOfWeekIdx'];
|
||||
isComplexCategoryCondition?: boolean;
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
|
||||
import { GraphButton } from './GraphButton';
|
||||
import { SaveReportWrapper } from './SaveReport';
|
||||
import { type SavedStatus } from './SaveReportMenu';
|
||||
import { setSessionReport } from './setSessionReport';
|
||||
import { SnapshotButton } from './SnapshotButton';
|
||||
|
||||
@@ -33,7 +34,7 @@ import { FilterButton } from '@desktop-client/components/filters/FiltersMenu';
|
||||
type ReportTopbarProps = {
|
||||
customReportItems: CustomReportEntity;
|
||||
report: CustomReportEntity;
|
||||
savedStatus: string;
|
||||
savedStatus: SavedStatus;
|
||||
setGraphType: (value: string) => void;
|
||||
viewLegend: boolean;
|
||||
viewSummary: boolean;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { LoadingIndicator } from './LoadingIndicator';
|
||||
import { SaveReportChoose } from './SaveReportChoose';
|
||||
import { SaveReportDelete } from './SaveReportDelete';
|
||||
import { SaveReportMenu } from './SaveReportMenu';
|
||||
import { SaveReportMenu, type SavedStatus } from './SaveReportMenu';
|
||||
import { SaveReportName } from './SaveReportName';
|
||||
|
||||
import { FormField, FormLabel } from '@desktop-client/components/forms';
|
||||
@@ -28,7 +28,7 @@ import { useReports } from '@desktop-client/hooks/useReports';
|
||||
type SaveReportProps<T extends CustomReportEntity = CustomReportEntity> = {
|
||||
customReportItems: T;
|
||||
report: CustomReportEntity;
|
||||
savedStatus: string;
|
||||
savedStatus: SavedStatus;
|
||||
onReportChange: (
|
||||
params:
|
||||
| {
|
||||
|
||||
@@ -3,13 +3,15 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Menu, type MenuItem } from '@actual-app/components/menu';
|
||||
|
||||
export type SavedStatus = 'saved' | 'new' | 'modified';
|
||||
|
||||
export function SaveReportMenu({
|
||||
onMenuSelect,
|
||||
savedStatus,
|
||||
listReports,
|
||||
}: {
|
||||
onMenuSelect: (item: string) => void;
|
||||
savedStatus: string;
|
||||
savedStatus: SavedStatus;
|
||||
listReports: number;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useEffect, useEffectEvent, useMemo, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router';
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type DataEntity,
|
||||
type RuleConditionEntity,
|
||||
type sortByOpType,
|
||||
type TransactionEntity,
|
||||
} from 'loot-core/types/models';
|
||||
import { type TransObjectLiteral } from 'loot-core/types/util';
|
||||
|
||||
@@ -52,6 +53,7 @@ import {
|
||||
import { ReportSidebar } from '@desktop-client/components/reports/ReportSidebar';
|
||||
import { ReportSummary } from '@desktop-client/components/reports/ReportSummary';
|
||||
import { ReportTopbar } from '@desktop-client/components/reports/ReportTopbar';
|
||||
import { type SavedStatus } from '@desktop-client/components/reports/SaveReportMenu';
|
||||
import { setSessionReport } from '@desktop-client/components/reports/setSessionReport';
|
||||
import { createCustomSpreadsheet } from '@desktop-client/components/reports/spreadsheets/custom-spreadsheet';
|
||||
import { createGroupedSpreadsheet } from '@desktop-client/components/reports/spreadsheets/grouped-spreadsheet';
|
||||
@@ -168,11 +170,11 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
if (['/reports'].includes(prevUrl)) sessionStorage.clear();
|
||||
|
||||
const reportFromSessionStorage = sessionStorage.getItem('report');
|
||||
const session = reportFromSessionStorage
|
||||
const session: Partial<CustomReportEntity> = reportFromSessionStorage
|
||||
? JSON.parse(reportFromSessionStorage)
|
||||
: {};
|
||||
const combine = initialReport ?? defaultReport;
|
||||
const loadReport = { ...combine, ...session };
|
||||
const loadReport: CustomReportEntity = { ...combine, ...session };
|
||||
|
||||
const [allIntervals, setAllIntervals] = useState<
|
||||
Array<{
|
||||
@@ -274,39 +276,44 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
const [intervals, setIntervals] = useState(
|
||||
monthUtils.rangeInclusive(startDate, endDate),
|
||||
);
|
||||
const [earliestTransaction, setEarliestTransaction] = useState('');
|
||||
const [latestTransaction, setLatestTransaction] = useState('');
|
||||
const [earliestTransactionDate, setEarliestTransactionDate] =
|
||||
useState<TransactionEntity['date']>('');
|
||||
const [latestTransactionDate, setLatestTransactionDate] =
|
||||
useState<TransactionEntity['date']>('');
|
||||
const [report, setReport] = useState(loadReport);
|
||||
const [savedStatus, setSavedStatus] = useState(
|
||||
session.savedStatus ?? (initialReport ? 'saved' : 'new'),
|
||||
const [savedStatus, setSavedStatus] = useState<SavedStatus>(
|
||||
'savedStatus' in session
|
||||
? (session.savedStatus as SavedStatus)
|
||||
: initialReport
|
||||
? 'saved'
|
||||
: 'new',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
const onApplyFilterConditions = useEffectEvent(
|
||||
(
|
||||
currentConditions?: RuleConditionEntity[],
|
||||
currentConditionsOp?: RuleConditionEntity['conditionsOp'],
|
||||
) => {
|
||||
onApplyFilter(null);
|
||||
|
||||
const filtersToApply =
|
||||
savedStatus !== 'saved' ? conditions : report.conditions;
|
||||
savedStatus !== 'saved' ? conditions : currentConditions;
|
||||
const conditionsOpToApply =
|
||||
savedStatus !== 'saved' ? conditionsOp : report.conditionsOp;
|
||||
savedStatus !== 'saved' ? conditionsOp : currentConditionsOp;
|
||||
|
||||
filtersToApply?.forEach((condition: RuleConditionEntity) =>
|
||||
onApplyFilter(condition),
|
||||
);
|
||||
onConditionsOpChange(conditionsOpToApply);
|
||||
|
||||
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(),
|
||||
);
|
||||
filtersToApply?.forEach(onApplyFilter);
|
||||
if (conditionsOpToApply) {
|
||||
onConditionsOpChange(conditionsOpToApply);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const onSetAllIntervals = useEffectEvent(
|
||||
async (
|
||||
earliestTransaction: TransactionEntity,
|
||||
latestTransaction: TransactionEntity,
|
||||
interval: CustomReportEntity['interval'],
|
||||
) => {
|
||||
const fromDate =
|
||||
interval === 'Weekly'
|
||||
? 'dayFromDate'
|
||||
@@ -380,7 +387,17 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
.reverse();
|
||||
|
||||
setAllIntervals(allIntervalsMap);
|
||||
},
|
||||
);
|
||||
|
||||
const onSetStartAndEndDates = useEffectEvent(
|
||||
(
|
||||
earliestTransaction: TransactionEntity,
|
||||
latestTransaction: TransactionEntity,
|
||||
dateRange: CustomReportEntity['dateRange'],
|
||||
isDateStatic: CustomReportEntity['isDateStatic'],
|
||||
includeCurrentInterval: CustomReportEntity['includeCurrentInterval'],
|
||||
) => {
|
||||
if (!isDateStatic) {
|
||||
const [dateStart, dateEnd] = getLiveRange(
|
||||
dateRange,
|
||||
@@ -394,22 +411,44 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
setStartDate(dateStart);
|
||||
setEndDate(dateEnd);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
onApplyFilterConditions(report.conditions, report.conditionsOp);
|
||||
|
||||
const earliestTransaction = await send('get-earliest-transaction');
|
||||
setEarliestTransactionDate(
|
||||
earliestTransaction
|
||||
? earliestTransaction.date
|
||||
: monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
const latestTransaction = await send('get-latest-transaction');
|
||||
setLatestTransactionDate(
|
||||
latestTransaction ? latestTransaction.date : monthUtils.currentDay(),
|
||||
);
|
||||
|
||||
onSetAllIntervals(earliestTransaction, latestTransaction, interval);
|
||||
onSetStartAndEndDates(
|
||||
earliestTransaction,
|
||||
latestTransaction,
|
||||
dateRange,
|
||||
isDateStatic,
|
||||
includeCurrentInterval,
|
||||
);
|
||||
}
|
||||
|
||||
run();
|
||||
// omitted `conditions` and `conditionsOp` from dependencies to avoid infinite loops
|
||||
// oxlint-disable-next-line react/exhaustive-deps
|
||||
}, [
|
||||
interval,
|
||||
dateRange,
|
||||
firstDayOfWeekIdx,
|
||||
isDateStatic,
|
||||
onApplyFilter,
|
||||
onConditionsOpChange,
|
||||
report.conditions,
|
||||
report.conditionsOp,
|
||||
includeCurrentInterval,
|
||||
locale,
|
||||
savedStatus,
|
||||
]);
|
||||
|
||||
@@ -619,7 +658,7 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
const defaultSort = defaultsGraphList(mode, chooseGraph, 'defaultSort');
|
||||
if (defaultSort) {
|
||||
setSessionReport('sortBy', defaultSort);
|
||||
setSortBy(defaultSort);
|
||||
setSortBy(defaultSort as CustomReportEntity['sortBy']);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -834,8 +873,8 @@ function CustomReportInner({ report: initialReport }: CustomReportInnerProps) {
|
||||
disabledItems={disabledItems}
|
||||
defaultItems={defaultItems}
|
||||
defaultModeItems={defaultModeItems}
|
||||
earliestTransaction={earliestTransaction}
|
||||
latestTransaction={latestTransaction}
|
||||
earliestTransaction={earliestTransactionDate}
|
||||
latestTransaction={latestTransactionDate}
|
||||
firstDayOfWeekIdx={firstDayOfWeekIdx}
|
||||
isComplexCategoryCondition={isComplexCategoryCondition}
|
||||
/>
|
||||
|
||||
@@ -47,7 +47,7 @@ type BaseConditionEntity<
|
||||
month?: boolean;
|
||||
year?: boolean;
|
||||
};
|
||||
conditionsOp?: string;
|
||||
conditionsOp?: 'and' | 'or';
|
||||
type?: 'id' | 'boolean' | 'date' | 'number' | 'string';
|
||||
customName?: string;
|
||||
queryFilter?: Record<string, { $oneof: string[] }>;
|
||||
|
||||
6
upcoming-release-notes/6867.md
Normal file
6
upcoming-release-notes/6867.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Fix type safety issues and react-hooks/exhaustive-deps errors in CustomReport
|
||||
Reference in New Issue
Block a user