diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png
index 1aeaa14f98c..ce13427af87 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-1-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png
index 76c562bd188..4b22f0e2f6e 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-from-accounts-id-page-2-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png
index 39f7c042e38..85905bc0ee1 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-1-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png
index 8320db53651..efc32336eed 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-2-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png
index 591897b5a47..42cb054ab58 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-3-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png
index 3d879590fd4..034509229c4 100644
Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-creates-a-transaction-via-footer-button-4-chromium-linux.png differ
diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx
index 95e0713602f..824a59460bf 100644
--- a/packages/desktop-client/src/components/Modals.tsx
+++ b/packages/desktop-client/src/components/Modals.tsx
@@ -14,7 +14,7 @@ import { useSyncServerStatus } from '../hooks/useSyncServerStatus';
import { ModalTitle } from './common/Modal';
import { AccountAutocompleteModal } from './modals/AccountAutocompleteModal';
import { AccountMenuModal } from './modals/AccountMenuModal';
-import { BudgetMenuModal } from './modals/BudgetMenuModal';
+import { BudgetMonthMenuModal } from './modals/BudgetMonthMenuModal';
import { CategoryAutocompleteModal } from './modals/CategoryAutocompleteModal';
import { CategoryGroupMenuModal } from './modals/CategoryGroupMenuModal';
import { CategoryMenuModal } from './modals/CategoryMenuModal';
@@ -40,8 +40,10 @@ import { Notes } from './modals/Notes';
import { PayeeAutocompleteModal } from './modals/PayeeAutocompleteModal';
import { PlaidExternalMsg } from './modals/PlaidExternalMsg';
import { ReportBalanceMenuModal } from './modals/ReportBalanceMenuModal';
+import { ReportBudgetMenuModal } from './modals/ReportBudgetMenuModal';
import { ReportBudgetSummaryModal } from './modals/ReportBudgetSummaryModal';
import { RolloverBalanceMenuModal } from './modals/RolloverBalanceMenuModal';
+import { RolloverBudgetMenuModal } from './modals/RolloverBudgetMenuModal';
import { RolloverBudgetSummaryModal } from './modals/RolloverBudgetSummaryModal';
import { RolloverToBudgetMenuModal } from './modals/RolloverToBudgetMenuModal';
import { ScheduledTransactionMenuModal } from './modals/ScheduledTransactionMenuModal';
@@ -429,7 +431,6 @@ export function Modals() {
key={name}
modalProps={modalProps}
categoryId={options.categoryId}
- categoryGroup={options.categoryGroup}
onSave={options.onSave}
onEditNotes={options.onEditNotes}
onDelete={options.onDelete}
@@ -437,6 +438,40 @@ export function Modals() {
/>
);
+ case 'rollover-budget-menu':
+ return (
+
+
+
+ );
+
+ case 'report-budget-menu':
+ return (
+
+
+
+ );
+
case 'category-group-menu':
return (
);
- case 'rollover-to-budget-menu':
+ case 'rollover-summary-to-budget-menu':
return (
);
- case 'budget-menu':
+ case 'budget-month-menu':
return (
- {
- setEditing(id ? { id, cell: monthIndex } : null);
+ const onEditMonth = (id, month) => {
+ setEditing(id ? { id, cell: month } : null);
};
const onEditName = id => {
@@ -134,18 +132,6 @@ export function BudgetTable(props) {
}
};
- const resolveMonth = monthIndex => {
- return monthUtils.addMonths(startMonth, monthIndex);
- };
-
- const _onShowActivity = (catId, monthIndex) => {
- onShowActivity(catId, resolveMonth(monthIndex));
- };
-
- const _onBudgetAction = (monthIndex, type, args) => {
- onBudgetAction(resolveMonth(monthIndex), type, args);
- };
-
const onCollapse = collapsedIds => {
setCollapsedPref(collapsedIds);
};
@@ -244,8 +230,8 @@ export function BudgetTable(props) {
onDeleteGroup={onDeleteGroup}
onReorderCategory={_onReorderCategory}
onReorderGroup={_onReorderGroup}
- onBudgetAction={_onBudgetAction}
- onShowActivity={_onShowActivity}
+ onBudgetAction={onBudgetAction}
+ onShowActivity={onShowActivity}
/>
diff --git a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx
index 5007b81c8d1..bb9da828c58 100644
--- a/packages/desktop-client/src/components/budget/ExpenseCategory.tsx
+++ b/packages/desktop-client/src/components/budget/ExpenseCategory.tsx
@@ -28,12 +28,12 @@ type ExpenseCategoryProps = {
dragState: DragState;
MonthComponent: ComponentProps['component'];
onEditName?: ComponentProps['onEditName'];
- onEditMonth?: (id: string, monthIndex: number) => void;
+ onEditMonth?: (id: string, month: string) => void;
onSave?: ComponentProps['onSave'];
onDelete?: ComponentProps['onDelete'];
onDragChange: OnDragChangeCallback;
- onBudgetAction: (idx: number, action: string, arg: unknown) => void;
- onShowActivity: (id: string, idx: number) => void;
+ onBudgetAction: (month: number, action: string, arg: unknown) => void;
+ onShowActivity: (id: string, month: string) => void;
onReorder: OnDropCallback;
};
@@ -101,7 +101,7 @@ export function ExpenseCategory({
['component'];
onEditName: ComponentProps['onEditName'];
- onEditMonth?: (id: string, monthIndex: number) => void;
+ onEditMonth?: (id: string, month: string) => void;
onSave: ComponentProps['onSave'];
onDelete: ComponentProps['onDelete'];
onDragChange: OnDragChangeCallback;
- onBudgetAction: (idx: number, action: string, arg: unknown) => void;
+ onBudgetAction: (month: string, action: string, arg: unknown) => void;
onReorder: OnDropCallback;
- onShowActivity: (id: string, idx: number) => void;
+ onShowActivity: (id: string, month: string) => void;
};
export function IncomeCategory({
@@ -76,7 +76,7 @@ export function IncomeCategory({
/>
;
- editingIndex?: string | number;
+ component?: ComponentType<{ month: string; editing: boolean }>;
+ editingMonth?: string;
args?: object;
style?: CSSProperties;
};
export function RenderMonths({
component: Component,
- editingIndex,
+ editingMonth,
args,
style,
}: RenderMonthsProps) {
const { months } = useContext(MonthsContext);
return months.map((month, index) => {
- const editing = editingIndex === index;
+ const editing = editingMonth === month;
return (
-
+
);
diff --git a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx
index 66999627bf7..91aa1ade6d1 100644
--- a/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx
+++ b/packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx
@@ -7,15 +7,15 @@ import { BalanceMenu } from './BalanceMenu';
type BalanceTooltipProps = {
categoryId: string;
tooltip: { close: () => void };
- monthIndex: number;
- onBudgetAction: (idx: number, action: string, arg: unknown) => void;
+ month: string;
+ onBudgetAction: (month: string, action: string, arg: unknown) => void;
onClose?: () => void;
};
export function BalanceTooltip({
categoryId,
tooltip,
- monthIndex,
+ month,
onBudgetAction,
onClose,
...tooltipProps
@@ -36,7 +36,7 @@ export function BalanceTooltip({
{
- onBudgetAction?.(monthIndex, 'carryover', {
+ onBudgetAction?.(month, 'carryover', {
category: categoryId,
flag: carryover,
});
diff --git a/packages/desktop-client/src/components/budget/report/BudgetMenu.tsx b/packages/desktop-client/src/components/budget/report/BudgetMenu.tsx
new file mode 100644
index 00000000000..aeed3cddd88
--- /dev/null
+++ b/packages/desktop-client/src/components/budget/report/BudgetMenu.tsx
@@ -0,0 +1,75 @@
+import React, { type ComponentPropsWithoutRef } from 'react';
+
+import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
+import { Menu } from '../../common/Menu';
+
+type BudgetMenuProps = Omit<
+ ComponentPropsWithoutRef,
+ 'onMenuSelect' | 'items'
+> & {
+ onCopyLastMonthAverage: () => void;
+ onSetMonthsAverage: (numberOfMonths: number) => void;
+ onApplyBudgetTemplate: () => void;
+};
+export function BudgetMenu({
+ onCopyLastMonthAverage,
+ onSetMonthsAverage,
+ onApplyBudgetTemplate,
+ ...props
+}: BudgetMenuProps) {
+ const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
+ const onMenuSelect = (name: string) => {
+ switch (name) {
+ case 'copy-single-last':
+ onCopyLastMonthAverage?.();
+ break;
+ case 'set-single-3-avg':
+ onSetMonthsAverage?.(3);
+ break;
+ case 'set-single-6-avg':
+ onSetMonthsAverage?.(6);
+ break;
+ case 'set-single-12-avg':
+ onSetMonthsAverage?.(12);
+ break;
+ case 'apply-single-category-template':
+ onApplyBudgetTemplate?.();
+ break;
+ default:
+ throw new Error(`Unrecognized menu item: ${name}`);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx
index 3181c8da7cb..5d09472c8ff 100644
--- a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx
+++ b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx
@@ -5,11 +5,9 @@ import { reportBudget } from 'loot-core/src/client/queries';
import { evalArithmetic } from 'loot-core/src/shared/arithmetic';
import { integerToCurrency, amountToInteger } from 'loot-core/src/shared/util';
-import { useFeatureFlag } from '../../../hooks/useFeatureFlag';
import { SvgCheveronDown } from '../../../icons/v1';
import { styles, theme, type CSSProperties } from '../../../style';
import { Button } from '../../common/Button';
-import { Menu } from '../../common/Menu';
import { Text } from '../../common/Text';
import { View } from '../../common/View';
import { CellValue } from '../../spreadsheet/CellValue';
@@ -20,6 +18,7 @@ import { BalanceWithCarryover } from '../BalanceWithCarryover';
import { makeAmountGrey } from '../util';
import { BalanceTooltip } from './BalanceTooltip';
+import { BudgetMenu } from './BudgetMenu';
const headerLabelStyle: CSSProperties = {
flex: 1,
@@ -142,15 +141,15 @@ export const GroupMonth = memo(function GroupMonth({ group }: GroupMonthProps) {
});
type CategoryMonthProps = {
- monthIndex: number;
+ month: string;
category: { id: string; name: string; is_income: boolean };
editing: boolean;
- onEdit: (id: string | null, idx?: number) => void;
- onBudgetAction: (idx: number, action: string, arg: unknown) => void;
- onShowActivity: (id: string, idx: number) => void;
+ onEdit: (id: string | null, month?: string) => void;
+ onBudgetAction: (month: string, action: string, arg: unknown) => void;
+ onShowActivity: (id: string, month: string) => void;
};
export const CategoryMonth = memo(function CategoryMonth({
- monthIndex,
+ month,
category,
editing,
onEdit,
@@ -160,7 +159,6 @@ export const CategoryMonth = memo(function CategoryMonth({
const balanceTooltip = useTooltip();
const [menuOpen, setMenuOpen] = useState(false);
const [hover, setHover] = useState(false);
- const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
return (
setMenuOpen(false)}
>
-