From 33c1f7b9624f19eb5301433101e88aab4c293dd3 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Fri, 6 Sep 2024 16:13:48 +0200 Subject: [PATCH 01/24] feat: context menu on transactions --- .../src/components/accounts/Account.jsx | 10 + .../transactions/TransactionList.jsx | 14 + .../transactions/TransactionMenu.tsx | 139 +++ .../transactions/TransactionsTable.jsx | 1035 +++++++++-------- 4 files changed, 724 insertions(+), 474 deletions(-) create mode 100644 packages/desktop-client/src/components/transactions/TransactionMenu.tsx diff --git a/packages/desktop-client/src/components/accounts/Account.jsx b/packages/desktop-client/src/components/accounts/Account.jsx index 01bcc5abd85..e27fe162fb7 100644 --- a/packages/desktop-client/src/components/accounts/Account.jsx +++ b/packages/desktop-client/src/components/accounts/Account.jsx @@ -1410,6 +1410,7 @@ class AccountInternal extends PureComponent { showExtraBalances, accountId, categoryId, + onBatchDelete, } = this.props; const { transactions, @@ -1581,6 +1582,15 @@ class AccountInternal extends PureComponent { sortField={this.state.sort.field} ascDesc={this.state.sort.ascDesc} onChange={this.onTransactionsChange} + onBatchDelete={this.onBatchDelete} + onBatchDuplicate={this.onBatchDuplicate} + onBatchLinkSchedule={this.onBatchLinkSchedule} + onBatchUnlinkSchedule={this.onBatchUnlinkSchedule} + onCreateRule={this.onCreateRule} + onScheduleAction={this.onScheduleAction} + onMakeAsNonSplitTransactions={ + this.onMakeAsNonSplitTransactions + } onRefetch={this.refetchTransactions} onRefetchUpToRow={row => this.paged.refetchUpToRow(row, { diff --git a/packages/desktop-client/src/components/transactions/TransactionList.jsx b/packages/desktop-client/src/components/transactions/TransactionList.jsx index 14fee62c9a5..fc9e9d29f5e 100644 --- a/packages/desktop-client/src/components/transactions/TransactionList.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionList.jsx @@ -87,6 +87,13 @@ export function TransactionList({ onCloseAddTransaction, onCreatePayee, onApplyFilter, + onBatchDelete, + onBatchDuplicate, + onBatchLinkSchedule, + onBatchUnlinkSchedule, + onCreateRule, + onScheduleAction, + onMakeAsNonSplitTransactions, }) { const dispatch = useDispatch(); const transactionsLatest = useRef(); @@ -234,6 +241,13 @@ export function TransactionList({ onSort={onSort} sortField={sortField} ascDesc={ascDesc} + onBatchDelete={onBatchDelete} + onBatchDuplicate={onBatchDuplicate} + onBatchLinkSchedule={onBatchLinkSchedule} + onBatchUnlinkSchedule={onBatchUnlinkSchedule} + onCreateRule={onCreateRule} + onScheduleAction={onScheduleAction} + onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions} /> ); } diff --git a/packages/desktop-client/src/components/transactions/TransactionMenu.tsx b/packages/desktop-client/src/components/transactions/TransactionMenu.tsx new file mode 100644 index 00000000000..e2723014d8f --- /dev/null +++ b/packages/desktop-client/src/components/transactions/TransactionMenu.tsx @@ -0,0 +1,139 @@ +import React, { type ComponentPropsWithoutRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; + +import { pushModal } from 'loot-core/client/actions'; +import { isPreviewId } from 'loot-core/shared/transactions'; +import { type TransactionEntity } from 'loot-core/types/models'; + +import { Menu } from '../common/Menu'; + +type BalanceMenuProps = Omit< + ComponentPropsWithoutRef, + 'onMenuSelect' | 'items' +> & { + transaction: TransactionEntity; + onDuplicate: (id: string) => void; + onDelete: (id: string) => void; + onLinkSchedule: (id: string) => void; + onUnlinkSchedule: (id: string) => void; + onCreateRule: (id: string) => void; + onScheduleAction: (action: string, id: string) => void; + onMakeAsNonSplitTransactions: (id: string) => void; + closeMenu: () => void; +}; + +export function TransactionMenu({ + transaction, + onDuplicate, + onDelete, + onLinkSchedule, + onUnlinkSchedule, + onCreateRule, + onScheduleAction, + onMakeAsNonSplitTransactions, + closeMenu, + ...props +}: BalanceMenuProps) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const isPreview = isPreviewId(transaction.id); + const linked = !!transaction.schedule; + const canUnsplitTransactions = + !transaction.reconciled && (transaction.is_parent || transaction.is_child); + + function onViewSchedule() { + const firstId = transaction.id; + let scheduleId; + if (isPreviewId(firstId)) { + const parts = firstId.split('/'); + scheduleId = parts[1]; + } else { + scheduleId = transaction.schedule; + } + + if (scheduleId) { + dispatch(pushModal('schedule-edit', { id: scheduleId })); + } + } + + return ( + { + switch (name) { + case 'duplicate': + onDuplicate(transaction.id); + break; + case 'delete': + onDelete(transaction.id); + break; + case 'unsplit-transactions': + onMakeAsNonSplitTransactions(transaction.id); + break; + case 'post-transaction': + case 'skip': + onScheduleAction(name, transaction.id); + break; + case 'view-schedule': + onViewSchedule(); + break; + case 'link-schedule': + onLinkSchedule(transaction.id); + break; + case 'unlink-schedule': + onUnlinkSchedule(transaction.id); + break; + case 'create-rule': + onCreateRule(transaction.id); + break; + default: + throw new Error(`Unrecognized menu option: ${name}`); + } + closeMenu(); + }} + items={ + isPreview + ? [ + { name: 'view-schedule', text: t('View schedule') }, + { name: 'post-transaction', text: t('Post transaction') }, + { name: 'skip', text: t('Skip scheduled date') }, + ] + : [ + { + name: 'duplicate', + text: t('Duplicate'), + }, + { name: 'delete', text: t('Delete') }, + ...(linked + ? [ + { + name: 'view-schedule', + text: t('View schedule'), + }, + { name: 'unlink-schedule', text: t('Unlink schedule') }, + ] + : [ + { + name: 'link-schedule', + text: t('Link schedule'), + }, + { + name: 'create-rule', + text: t('Create rule'), + }, + ]), + ...(canUnsplitTransactions + ? [ + { + name: 'unsplit-transactions', + text: t('Unsplit transaction'), + }, + ] + : []), + ] + } + /> + ); +} diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 20ba4a19446..43816ffd29f 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -82,6 +82,7 @@ import { Table, UnexposedCellContent, } from '../table'; +import { TransactionMenu } from './TransactionMenu'; function getDisplayValue(obj, name) { return obj ? obj[name] : ''; @@ -845,6 +846,12 @@ const Transaction = memo(function Transaction({ onSave, onEdit, onDelete, + onDuplicate, + onLinkSchedule, + onUnlinkSchedule, + onCreateRule, + onScheduleAction, + onMakeAsNonSplitTransactions, onSplit, onManagePayees, onCreatePayee, @@ -1039,522 +1046,568 @@ const Transaction = memo(function Transaction({ }, 1); }, [splitError, allTransactions]); + const [menuOpen, setMenuOpen] = useState(false); + const [crossOffset, setCrossOffset] = useState(0); + return ( - - {splitError && listContainerRef.current && ( + <> + { + if (transaction.id === 'temp') return; + e.preventDefault(); + setCrossOffset( + e.clientX - triggerRef.current.getBoundingClientRect().left, + ); + setMenuOpen(false); + setMenuOpen(true); + }} + > setMenuOpen(false)} + crossOffset={crossOffset} + style={{ width: 200 }} isNonModal - style={{ width: 375, padding: 5, maxHeight: '38px !important' }} - shouldFlip={false} - placement="bottom end" - UNSTABLE_portalContainer={listContainerRef.current} > - {splitError} + onDelete && onDelete(transaction.id)} + onDuplicate={() => onDuplicate && onDuplicate(transaction.id)} + onLinkSchedule={() => + onLinkSchedule && onLinkSchedule(transaction.id) + } + onUnlinkSchedule={() => + onUnlinkSchedule && onUnlinkSchedule(transaction.id) + } + onCreateRule={() => onCreateRule && onCreateRule(transaction.id)} + onScheduleAction={action => + onScheduleAction && onScheduleAction(action, transaction.id) + } + onMakeAsNonSplitTransactions={() => + onMakeAsNonSplitTransactions && + onMakeAsNonSplitTransactions(transaction.id) + } + closeMenu={() => setMenuOpen(false)} + /> - )} - {isChild && ( - + {showCleared && ( + + )} + + + + ); }); @@ -1878,6 +1931,12 @@ function TransactionTableInner({ onEdit={tableNavigator.onEdit} onSave={props.onSave} onDelete={props.onDelete} + onDuplicate={props.onDuplicate} + onLinkSchedule={props.onLinkSchedule} + onUnlinkSchedule={props.onUnlinkSchedule} + onCreateRule={props.onCreateRule} + onScheduleAction={props.onScheduleAction} + onMakeAsNonSplitTransactions={props.onMakeAsNonSplitTransactions} onSplit={props.onSplit} onManagePayees={props.onManagePayees} onCreatePayee={props.onCreatePayee} @@ -2332,9 +2391,31 @@ export const TransactionTable = forwardRef((props, ref) => { } setNewTransactions(deleteTransaction(newTrans, id).data); + } else { + props.onBatchDelete([id]); } }, []); + const onDuplicate = useCallback(id => { + props.onBatchDuplicate([id]); + }, []); + + const onLinkSchedule = useCallback(id => { + props.onBatchLinkSchedule([id]); + }, []); + const onUnlinkSchedule = useCallback(id => { + props.onBatchUnlinkSchedule([id]); + }, []); + const onCreateRule = useCallback(id => { + props.onCreateRule([id]); + }, []); + const onScheduleAction = useCallback((action, id) => { + props.onScheduleAction(action, [id]); + }, []); + const onMakeAsNonSplitTransactions = useCallback(id => { + props.onMakeAsNonSplitTransactions([id]); + }, []); + const onSplit = useMemo(() => { return id => { if (isTemporaryId(id)) { @@ -2478,6 +2559,12 @@ export const TransactionTable = forwardRef((props, ref) => { isExpanded={splitsExpanded.expanded} onSave={onSave} onDelete={onDelete} + onDuplicate={onDuplicate} + onLinkSchedule={onLinkSchedule} + onUnlinkSchedule={onUnlinkSchedule} + onCreateRule={onCreateRule} + onScheduleAction={onScheduleAction} + onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions} onSplit={onSplit} onCheckNewEnter={onCheckNewEnter} onCheckEnter={onCheckEnter} From 9967d3c77ed39fe686e0527fd6bba1019f23d076 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Fri, 6 Sep 2024 16:14:07 +0200 Subject: [PATCH 02/24] feat: context menu's on budget page --- .../desktop-client/src/components/budget/SidebarCategory.tsx | 4 ++++ .../desktop-client/src/components/budget/SidebarGroup.tsx | 4 ++++ .../src/components/budget/rollover/RolloverComponents.tsx | 4 ++++ .../budget/rollover/budgetsummary/ToBudgetAmount.tsx | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index fb4d860cf52..ea2fddc1de7 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -59,6 +59,10 @@ export function SidebarCategory({ opacity: category.hidden || categoryGroup?.hidden ? 0.33 : undefined, backgroundColor: 'transparent', }} + onContextMenu={e => { + e.preventDefault(); + setMenuOpen(true); + }} >
{ onToggleCollapse(group.id); }} + onContextMenu={e => { + e.preventDefault(); + setMenuOpen(true); + }} > {!dragPreview && ( setBalanceMenuOpen(true)} + onContextMenu={e => { + e.preventDefault(); + setBalanceMenuOpen(true); + }} > { + e.preventDefault(); + onClick(); + }} data-cellname={sheetName} className={`${css([ styles.veryLargeText, From d61cf4d61c17684ee9d28bb26589ef620e6cdda6 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Fri, 6 Sep 2024 16:20:41 +0200 Subject: [PATCH 03/24] chore: release note --- upcoming-release-notes/3381.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 upcoming-release-notes/3381.md diff --git a/upcoming-release-notes/3381.md b/upcoming-release-notes/3381.md new file mode 100644 index 00000000000..795b1aca4fd --- /dev/null +++ b/upcoming-release-notes/3381.md @@ -0,0 +1,6 @@ +--- +category: Enhancements +authors: [UnderKoen] +--- + +Context menu's for transactions and budget pages From f194a8623fd5470d1a19828bcc586c2dc643474c Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 8 Sep 2024 14:05:12 +0200 Subject: [PATCH 04/24] fix: losing focus on context menu --- .../src/components/accounts/Account.tsx | 1 - .../src/components/budget/SidebarCategory.tsx | 1 + .../src/components/budget/SidebarGroup.tsx | 1 + .../budget/rollover/RolloverComponents.tsx | 1 + .../rollover/budgetsummary/ToBudget.tsx | 1 + .../src/components/common/Popover.tsx | 23 +- .../transactions/TransactionsTable.jsx | 1032 ++++++++--------- 7 files changed, 541 insertions(+), 519 deletions(-) diff --git a/packages/desktop-client/src/components/accounts/Account.tsx b/packages/desktop-client/src/components/accounts/Account.tsx index 8c378089b3b..e5869733926 100644 --- a/packages/desktop-client/src/components/accounts/Account.tsx +++ b/packages/desktop-client/src/components/accounts/Account.tsx @@ -1605,7 +1605,6 @@ class AccountInternal extends PureComponent< showExtraBalances, accountId, categoryId, - onBatchDelete, } = this.props; const { transactions, diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index ea2fddc1de7..8ae8f841251 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -95,6 +95,7 @@ export function SidebarCategory({ isOpen={menuOpen} onOpenChange={() => setMenuOpen(false)} style={{ width: 200 }} + isNonModal > { diff --git a/packages/desktop-client/src/components/budget/SidebarGroup.tsx b/packages/desktop-client/src/components/budget/SidebarGroup.tsx index 1d9200003e5..5c01546b09e 100644 --- a/packages/desktop-client/src/components/budget/SidebarGroup.tsx +++ b/packages/desktop-client/src/components/budget/SidebarGroup.tsx @@ -112,6 +112,7 @@ export function SidebarGroup({ isOpen={menuOpen} onOpenChange={() => setMenuOpen(false)} style={{ width: 200 }} + isNonModal > { diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index 3bddf1fab03..8dd92888642 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -392,6 +392,7 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ isOpen={balanceMenuOpen} onOpenChange={() => setBalanceMenuOpen(false)} style={{ width: 200 }} + isNonModal > setMenuOpen(null)} style={{ width: 200 }} + isNonModal > {menuOpen === 'actions' && ( { + const ref = useRef(null); + + const handleFocus = useCallback( + (e: FocusEvent) => { + if (!ref.current?.contains(e.relatedTarget as Node)) { + props.onOpenChange?.(false); + } + }, + [props], + ); + + useEffect(() => { + if (!props.isNonModal) return; + if (props.isOpen) { + ref.current?.addEventListener('focusout', handleFocus); + } else { + ref.current?.removeEventListener('focusout', handleFocus); + } + }, [handleFocus, props.isNonModal, props.isOpen]); + return ( - { - if (transaction.id === 'temp') return; - e.preventDefault(); - setCrossOffset( - e.clientX - triggerRef.current.getBoundingClientRect().left, - ); - setMenuOpen(false); - setMenuOpen(true); - }} + { + if (transaction.id === 'temp') return; + e.preventDefault(); + setCrossOffset( + e.clientX - triggerRef.current.getBoundingClientRect().left, + ); + setMenuOpen(false); + setMenuOpen(true); + }} + > + setMenuOpen(false)} + crossOffset={crossOffset} + style={{ width: 200 }} + isNonModal > + onDelete && onDelete(transaction.id)} + onDuplicate={() => onDuplicate && onDuplicate(transaction.id)} + onLinkSchedule={() => + onLinkSchedule && onLinkSchedule(transaction.id) + } + onUnlinkSchedule={() => + onUnlinkSchedule && onUnlinkSchedule(transaction.id) + } + onCreateRule={() => onCreateRule && onCreateRule(transaction.id)} + onScheduleAction={action => + onScheduleAction && onScheduleAction(action, transaction.id) + } + onMakeAsNonSplitTransactions={() => + onMakeAsNonSplitTransactions && + onMakeAsNonSplitTransactions(transaction.id) + } + closeMenu={() => setMenuOpen(false)} + /> + + + {splitError && listContainerRef.current && ( setMenuOpen(false)} - crossOffset={crossOffset} - style={{ width: 200 }} + isOpen isNonModal + style={{ width: 375, padding: 5, maxHeight: '38px !important' }} + shouldFlip={false} + placement="bottom end" + UNSTABLE_portalContainer={listContainerRef.current} > - onDelete && onDelete(transaction.id)} - onDuplicate={() => onDuplicate && onDuplicate(transaction.id)} - onLinkSchedule={() => - onLinkSchedule && onLinkSchedule(transaction.id) - } - onUnlinkSchedule={() => - onUnlinkSchedule && onUnlinkSchedule(transaction.id) - } - onCreateRule={() => onCreateRule && onCreateRule(transaction.id)} - onScheduleAction={action => - onScheduleAction && onScheduleAction(action, transaction.id) - } - onMakeAsNonSplitTransactions={() => - onMakeAsNonSplitTransactions && - onMakeAsNonSplitTransactions(transaction.id) - } - closeMenu={() => setMenuOpen(false)} - /> + {splitError} + )} - {splitError && listContainerRef.current && ( - - {splitError} - - )} - - {isChild && ( - - + + ); }); From 696a6d7a09340bf26287b19010c96be47bd888e3 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 8 Sep 2024 14:36:57 +0200 Subject: [PATCH 05/24] feat: schedules context menu --- .../components/schedules/SchedulesTable.tsx | 220 ++++++++++-------- .../transactions/TransactionsTable.jsx | 1 - 2 files changed, 126 insertions(+), 95 deletions(-) diff --git a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx index 25d97f9e398..05677a63482 100644 --- a/packages/desktop-client/src/components/schedules/SchedulesTable.tsx +++ b/packages/desktop-client/src/components/schedules/SchedulesTable.tsx @@ -64,9 +64,6 @@ function OverflowMenu({ }) { const { t } = useTranslation(); - const triggerRef = useRef(null); - const [open, setOpen] = useState(false); - const getMenuItems = () => { const menuItems: { name: ScheduleItemAction; text: string }[] = []; @@ -99,36 +96,12 @@ function OverflowMenu({ }; return ( - - - - setOpen(false)} - > - { - onAction(name, schedule.id); - setOpen(false); - }} - items={getMenuItems()} - /> - - + { + onAction(name, schedule.id); + }} + items={getMenuItems()} + /> ); } @@ -196,6 +169,120 @@ export function ScheduleAmountCell({ ); } +function ScheduleRow({ + schedule, + onAction, + onSelect, + minimal, + statuses, + dateFormat, +}: { schedule: ScheduleEntity; dateFormat: string } & Pick< + SchedulesTableProps, + 'onSelect' | 'onAction' | 'minimal' | 'statuses' +>) { + const { t } = useTranslation(); + + const rowRef = useRef(null); + const buttonRef = useRef(null); + const [open, setOpen] = useState(false); + const [crossOffset, setCrossOffset] = useState(0); + + return ( + onSelect(schedule.id)} + style={{ + cursor: 'pointer', + backgroundColor: theme.tableBackground, + color: theme.tableText, + ':hover': { backgroundColor: theme.tableRowBackgroundHover }, + }} + onContextMenu={e => { + if (minimal) return; + e.preventDefault(); + setCrossOffset(e.clientX - rowRef.current.getBoundingClientRect().left); + setOpen('contextMenu'); + }} + > + {!minimal && ( + setOpen(false)} + isNonModal + placement="bottom start" + crossOffset={open === 'contextMenu' ? crossOffset : 0} + > + { + onAction(action, id); + setOpen(false); + }} + /> + + )} + + + {schedule.name ? schedule.name : t('None')} + + + + + + + + + + {schedule.next_date + ? monthUtilFormat(schedule.next_date, dateFormat) + : null} + + + + + + {!minimal && ( + + {schedule._date && schedule._date.frequency && ( + + )} + + )} + {!minimal && ( + + + + + + )} + + ); +} + export function SchedulesTable({ schedules, statuses, @@ -267,66 +354,6 @@ export function SchedulesTable({ return [...unCompletedSchedules, { id: 'show-completed' }]; }, [filteredSchedules, showCompleted, allowCompleted]); - function renderSchedule({ schedule }: { schedule: ScheduleEntity }) { - return ( - onSelect(schedule.id)} - style={{ - cursor: 'pointer', - backgroundColor: theme.tableBackground, - color: theme.tableText, - ':hover': { backgroundColor: theme.tableRowBackgroundHover }, - }} - > - - - {schedule.name ? schedule.name : t('None')} - - - - - - - - - - {schedule.next_date - ? monthUtilFormat(schedule.next_date, dateFormat) - : null} - - - - - - {!minimal && ( - - {schedule._date && schedule._date.frequency && ( - - )} - - )} - {!minimal && ( - - - - )} - - ); - } - function renderItem({ item }: { item: SchedulesTableItem }) { if (item.id === 'show-completed') { return ( @@ -353,7 +380,12 @@ export function SchedulesTable({ ); } - return renderSchedule({ schedule: item as ScheduleEntity }); + return ( + + ); } return ( diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 2993d0dd242..b4fe02e2137 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -1084,7 +1084,6 @@ const Transaction = memo(function Transaction({ setCrossOffset( e.clientX - triggerRef.current.getBoundingClientRect().left, ); - setMenuOpen(false); setMenuOpen(true); }} > From 453d24d17b8d8235b8b632533593727cf7532e18 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 8 Sep 2024 14:55:18 +0200 Subject: [PATCH 06/24] feat: payees context menu --- .../src/components/payees/ManagePayees.jsx | 9 ++- .../src/components/payees/PayeeTable.tsx | 13 +++- .../src/components/payees/PayeeTableRow.tsx | 67 ++++++++++++++++++- 3 files changed, 83 insertions(+), 6 deletions(-) diff --git a/packages/desktop-client/src/components/payees/ManagePayees.jsx b/packages/desktop-client/src/components/payees/ManagePayees.jsx index 2843bc644ba..0569b87e142 100644 --- a/packages/desktop-client/src/components/payees/ManagePayees.jsx +++ b/packages/desktop-client/src/components/payees/ManagePayees.jsx @@ -196,9 +196,11 @@ export const ManagePayees = forwardRef( return filteredPayees.filter(p => p.transfer_acct == null).map(p => p.id); }, [filteredPayees]); - function onDelete() { - onBatchChange({ deleted: [...selected.items].map(id => ({ id })) }); - selected.dispatch({ type: 'select-none' }); + function onDelete(ids) { + onBatchChange({ + deleted: ids ?? [...selected.items].map(id => ({ id })), + }); + if (!ids) selected.dispatch({ type: 'select-none' }); } function onFavorite() { @@ -341,6 +343,7 @@ export const ManagePayees = forwardRef( categoryGroups={categoryGroups} navigator={tableNavigator} onUpdate={onUpdate} + onDelete={id => onDelete([{ id }])} onViewRules={onViewRules} onCreateRule={onCreateRule} /> diff --git a/packages/desktop-client/src/components/payees/PayeeTable.tsx b/packages/desktop-client/src/components/payees/PayeeTable.tsx index 3efbaaad8e2..5766ba391ec 100644 --- a/packages/desktop-client/src/components/payees/PayeeTable.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTable.tsx @@ -26,7 +26,7 @@ type PayeeTableProps = { navigator: TableNavigator; } & Pick< ComponentProps, - 'onUpdate' | 'onViewRules' | 'onCreateRule' + 'onUpdate' | 'onDelete' | 'onViewRules' | 'onCreateRule' >; export const PayeeTable = forwardRef< @@ -34,7 +34,15 @@ export const PayeeTable = forwardRef< PayeeTableProps >( ( - { payees, ruleCounts, navigator, onUpdate, onViewRules, onCreateRule }, + { + payees, + ruleCounts, + navigator, + onUpdate, + onDelete, + onViewRules, + onCreateRule, + }, ref, ) => { const [hovered, setHovered] = useState(null); @@ -70,6 +78,7 @@ export const PayeeTable = forwardRef< onHover={onHover} onEdit={onEdit} onUpdate={onUpdate} + onDelete={onDelete} onViewRules={onViewRules} onCreateRule={onCreateRule} /> diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index 637a63da76b..a18cf957c17 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import { memo } from 'react'; +import { memo, useRef, useState } from 'react'; import { type PayeeEntity } from 'loot-core/src/types/models'; @@ -15,6 +15,9 @@ import { Row, SelectCell, } from '../table'; +import { Popover } from '../common/Popover'; +import { Menu } from '../common/Menu'; +import { useTranslation } from 'react-i18next'; type RuleButtonProps = { ruleCount: number; @@ -75,6 +78,7 @@ type PayeeTableRowProps = { field: EditablePayeeFields, value: unknown, ) => void; + onDelete: (id: PayeeEntity['id']) => void; onViewRules: (id: PayeeEntity['id']) => void; onCreateRule: (id: PayeeEntity['id']) => void; style?: CSSProperties; @@ -91,6 +95,7 @@ export const PayeeTableRow = memo( onViewRules, onCreateRule, onHover, + onDelete, onEdit, onUpdate, style, @@ -102,8 +107,15 @@ export const PayeeTableRow = memo( : theme.tableBorder; const backgroundFocus = hovered || focusedField === 'select'; + const { t } = useTranslation(); + + const triggerRef = useRef(null); + const [menuOpen, setMenuOpen] = useState(false); + const [crossOffset, setCrossOffset] = useState(0); + return ( onHover && onHover(payee.id)} + onContextMenu={e => { + e.preventDefault(); + setMenuOpen(true); + setCrossOffset( + e.clientX - triggerRef.current.getBoundingClientRect().left, + ); + }} > + setMenuOpen(false)} + crossOffset={crossOffset} + style={{ width: 200 }} + isNonModal + > + 0 + ? [{ name: 'view-rules', text: t('View rules') }] + : []), + { name: 'create-rule', text: t('Create rule') }, + ]} + onMenuSelect={name => { + switch (name) { + case 'delete': + onDelete(id); + break; + case 'favorite': + onUpdate(id, 'favorite', payee.favorite ? 0 : 1); + break; + case 'view-rules': + onViewRules(id); + break; + case 'create-rule': + onCreateRule(id); + break; + default: + throw new Error(`Unrecognized menu option: ${name}`); + } + setMenuOpen(false); + }} + /> + Date: Sun, 8 Sep 2024 15:10:38 +0200 Subject: [PATCH 07/24] feat: rules context menu --- .../src/components/ManageRules.tsx | 8 +++ .../src/components/payees/PayeeTableRow.tsx | 21 +++---- .../src/components/rules/RuleRow.tsx | 63 ++++++++++++++++++- .../src/components/rules/RulesList.tsx | 3 + 4 files changed, 81 insertions(+), 14 deletions(-) diff --git a/packages/desktop-client/src/components/ManageRules.tsx b/packages/desktop-client/src/components/ManageRules.tsx index a5c68ac6f16..fa76a50db2e 100644 --- a/packages/desktop-client/src/components/ManageRules.tsx +++ b/packages/desktop-client/src/components/ManageRules.tsx @@ -203,6 +203,13 @@ function ManageRulesContent({ setLoading(false); } + async function onDeleteRule(id: string) { + setLoading(true); + await send('rule-delete', id); + await loadRules(); + setLoading(false); + } + const onEditRule = useCallback(rule => { dispatch( pushModal('edit-rule', { @@ -305,6 +312,7 @@ function ManageRulesContent({ hoveredRule={hoveredRule} onHover={onHover} onEditRule={onEditRule} + onDeleteRule={rule => onDeleteRule(rule.id)} /> )} diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index a18cf957c17..40a44c047e1 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -153,18 +153,15 @@ export const PayeeTableRow = memo( > 0 - ? [{ name: 'view-rules', text: t('View rules') }] - : []), + payee.transfer_acct == null && { + name: 'delete', + text: t('Delete'), + }, + payee.transfer_acct == null && { + name: 'favorite', + text: payee.favorite ? t('Unfavorite') : t('Favorite'), + }, + ruleCount > 0 && { name: 'view-rules', text: t('View rules') }, { name: 'create-rule', text: t('Create rule') }, ]} onMenuSelect={name => { diff --git a/packages/desktop-client/src/components/rules/RuleRow.tsx b/packages/desktop-client/src/components/rules/RuleRow.tsx index c5c17a5106a..75cd7512dae 100644 --- a/packages/desktop-client/src/components/rules/RuleRow.tsx +++ b/packages/desktop-client/src/components/rules/RuleRow.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { memo } from 'react'; +import React, { memo, useRef, useState } from 'react'; import { v4 as uuid } from 'uuid'; @@ -17,6 +17,9 @@ import { SelectCell, Row, Field, Cell } from '../table'; import { ActionExpression } from './ActionExpression'; import { ConditionExpression } from './ConditionExpression'; +import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; +import { useTranslation } from 'react-i18next'; type RuleRowProps = { rule: RuleEntity; @@ -24,10 +27,18 @@ type RuleRowProps = { selected?: boolean; onHover?: (id: string | null) => void; onEditRule?: (rule: RuleEntity) => void; + onDeleteRule?: (rule: RuleEntity) => void; }; export const RuleRow = memo( - ({ rule, hovered, selected, onHover, onEditRule }: RuleRowProps) => { + ({ + rule, + hovered, + selected, + onHover, + onEditRule, + onDeleteRule, + }: RuleRowProps) => { const dispatchSelected = useSelectedDispatch(); const borderColor = selected ? theme.tableBorderSelected : 'none'; const backgroundFocus = hovered; @@ -43,8 +54,18 @@ export const RuleRow = memo( ); const hasSplits = actionSplits.length > 1; + const hasSchedule = rule.actions.some(({ op }) => op === 'link-schedule'); + + const { t } = useTranslation(); + + const triggerRef = useRef(null); + const [menuOpen, setMenuOpen] = useState(false); + const [crossOffset, setCrossOffset] = useState(0); + const [offset, setOffset] = useState(0); + return ( onHover && onHover(rule.id)} onMouseLeave={() => onHover && onHover(null)} + onContextMenu={e => { + e.preventDefault(); + setMenuOpen(true); + const rect = triggerRef.current.getBoundingClientRect(); + setCrossOffset(e.clientX - rect.left); + setOffset(e.clientY - rect.bottom + 10); + }} > + setMenuOpen(false)} + crossOffset={crossOffset} + offset={offset} + style={{ width: 200 }} + isNonModal + > + { + switch (name) { + case 'delete': + onDeleteRule(rule); + break; + case 'edit': + onEditRule(rule); + break; + default: + throw new Error(`Unrecognized menu option: ${name}`); + } + setMenuOpen(false); + }} + /> + void; onEditRule?: (rule: RuleEntity) => void; + onDeleteRule?: (rule: RuleEntity) => void; }; export function RulesList({ @@ -21,6 +22,7 @@ export function RulesList({ hoveredRule, onHover, onEditRule, + onDeleteRule, }: RulesListProps) { if (rules.length === 0) { return null; @@ -40,6 +42,7 @@ export function RulesList({ selected={selected} onHover={onHover} onEditRule={onEditRule} + onDeleteRule={onDeleteRule} /> ); })} From c34bee9c943ac1416d24ba357b29a04c2b98b82a Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 8 Sep 2024 15:11:25 +0200 Subject: [PATCH 08/24] chore: update release note --- upcoming-release-notes/3381.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upcoming-release-notes/3381.md b/upcoming-release-notes/3381.md index 795b1aca4fd..473ac8dc30c 100644 --- a/upcoming-release-notes/3381.md +++ b/upcoming-release-notes/3381.md @@ -3,4 +3,4 @@ category: Enhancements authors: [UnderKoen] --- -Context menu's for transactions and budget pages +Context menu's for transactions, budget, schedules, payees and rules pages From d852acb2f8749d120d966eb97e13db1177eba1ef Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 8 Sep 2024 15:15:49 +0200 Subject: [PATCH 09/24] chore: lint --- .../desktop-client/src/components/payees/PayeeTableRow.tsx | 6 +++--- packages/desktop-client/src/components/rules/RuleRow.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index 40a44c047e1..633e20ed9ad 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -1,11 +1,14 @@ // @ts-strict-ignore import { memo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { type PayeeEntity } from 'loot-core/src/types/models'; import { useSelectedDispatch } from '../../hooks/useSelected'; import { SvgArrowThinRight, SvgBookmark } from '../../icons/v1'; import { type CSSProperties, theme } from '../../style'; +import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; import { Text } from '../common/Text'; import { Cell, @@ -15,9 +18,6 @@ import { Row, SelectCell, } from '../table'; -import { Popover } from '../common/Popover'; -import { Menu } from '../common/Menu'; -import { useTranslation } from 'react-i18next'; type RuleButtonProps = { ruleCount: number; diff --git a/packages/desktop-client/src/components/rules/RuleRow.tsx b/packages/desktop-client/src/components/rules/RuleRow.tsx index 75cd7512dae..9aea9d45606 100644 --- a/packages/desktop-client/src/components/rules/RuleRow.tsx +++ b/packages/desktop-client/src/components/rules/RuleRow.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { memo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { v4 as uuid } from 'uuid'; @@ -10,6 +11,8 @@ import { useSelectedDispatch } from '../../hooks/useSelected'; import { SvgRightArrow2 } from '../../icons/v0'; import { styles, theme } from '../../style'; import { Button } from '../common/Button2'; +import { Menu } from '../common/Menu'; +import { Popover } from '../common/Popover'; import { Stack } from '../common/Stack'; import { Text } from '../common/Text'; import { View } from '../common/View'; @@ -17,9 +20,6 @@ import { SelectCell, Row, Field, Cell } from '../table'; import { ActionExpression } from './ActionExpression'; import { ConditionExpression } from './ConditionExpression'; -import { Menu } from '../common/Menu'; -import { Popover } from '../common/Popover'; -import { useTranslation } from 'react-i18next'; type RuleRowProps = { rule: RuleEntity; From f6a388d1d6a9d8ea844936b9afc3675843cebd44 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Mon, 9 Sep 2024 18:37:30 +0200 Subject: [PATCH 10/24] fix: broken balance movement menu --- .../budget/rollover/BalanceMovementMenu.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx index 672a25521c9..7fb8afaf2f5 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { runQuery } from 'loot-core/client/query-helpers'; import { send } from 'loot-core/platform/client/fetch'; @@ -32,12 +38,22 @@ export function BalanceMovementMenu({ const catBalance = useRolloverSheetValue( rolloverBudget.catBalance(categoryId), ); - const [menu, setMenu] = useState('menu'); + const [menu, _setMenu] = useState('menu'); + + const ref = useRef(null); + // Keep focus inside the popover on menu change + const setMenu = useCallback( + (menu: string) => { + ref.current?.focus(); + _setMenu(menu); + }, + [ref], + ); const { addBudgetTransferNotes } = useBudgetTransferNotes({ month }); return ( - <> + {menu === 'menu' && ( )} - + ); } From d068e7a9096edb9cb12801a3603ac3c65849c474 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Mon, 9 Sep 2024 18:39:16 +0200 Subject: [PATCH 11/24] fix: placement on context menu to be closer to cursor --- .../src/components/payees/PayeeTableRow.tsx | 10 ++++++---- .../desktop-client/src/components/rules/RuleRow.tsx | 4 ++-- .../src/components/schedules/SchedulesTable.tsx | 7 ++++++- .../src/components/transactions/TransactionsTable.jsx | 10 ++++++---- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index 633e20ed9ad..128e6e64985 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -112,6 +112,7 @@ export const PayeeTableRow = memo( const triggerRef = useRef(null); const [menuOpen, setMenuOpen] = useState(false); const [crossOffset, setCrossOffset] = useState(0); + const [offset, setOffset] = useState(0); return ( { e.preventDefault(); setMenuOpen(true); - setCrossOffset( - e.clientX - triggerRef.current.getBoundingClientRect().left, - ); + const rect = e.currentTarget.getBoundingClientRect(); + setCrossOffset(e.clientX - rect.left); + setOffset(e.clientY - rect.bottom); }} > setMenuOpen(false)} crossOffset={crossOffset} - style={{ width: 200 }} + offset={offset} + style={{ width: 200, margin: 1 }} isNonModal > setMenuOpen(false)} crossOffset={crossOffset} offset={offset} - style={{ width: 200 }} + style={{ width: 200, margin: 1 }} isNonModal > (false); const [crossOffset, setCrossOffset] = useState(0); + const [offset, setOffset] = useState(0); return ( { if (minimal) return; e.preventDefault(); - setCrossOffset(e.clientX - rowRef.current.getBoundingClientRect().left); + const rect = e.currentTarget.getBoundingClientRect(); + setCrossOffset(e.clientX - rect.left); + setOffset(e.clientY - rect.bottom); setOpen('contextMenu'); }} > @@ -214,6 +217,8 @@ function ScheduleRow({ isNonModal placement="bottom start" crossOffset={open === 'contextMenu' ? crossOffset : 0} + offset={open === 'contextMenu' ? offset : 0} + style={{ margin: 1 }} > { if (transaction.id === 'temp') return; e.preventDefault(); - setCrossOffset( - e.clientX - triggerRef.current.getBoundingClientRect().left, - ); + const rect = e.currentTarget.getBoundingClientRect(); + setCrossOffset(e.clientX - rect.left); + setOffset(e.clientY - rect.bottom); setMenuOpen(true); }} > @@ -1093,7 +1094,8 @@ const Transaction = memo(function Transaction({ isOpen={menuOpen} onOpenChange={() => setMenuOpen(false)} crossOffset={crossOffset} - style={{ width: 200 }} + offset={offset} + style={{ width: 200, margin: 1 }} isNonModal > Date: Mon, 9 Sep 2024 18:40:13 +0200 Subject: [PATCH 12/24] feat: context menu on budget field --- .../src/components/budget/rollover/RolloverComponents.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index 8dd92888642..a86fb488a97 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -226,6 +226,11 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ flex: 1, flexDirection: 'row', }} + onContextMenu={e => { + if (editing) return; + e.preventDefault(); + setBudgetMenuOpen(true); + }} > {!editing && ( setBudgetMenuOpen(false)} style={{ width: 200 }} + isNonModal > { From 15f0b36c8d320d5caa6961d4a556da88d795fddf Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Mon, 9 Sep 2024 18:49:19 +0200 Subject: [PATCH 13/24] chore: lint --- .../components/budget/rollover/BalanceMovementMenu.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx index 7fb8afaf2f5..e93f5631400 100644 --- a/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx +++ b/packages/desktop-client/src/components/budget/rollover/BalanceMovementMenu.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { runQuery } from 'loot-core/client/query-helpers'; import { send } from 'loot-core/platform/client/fetch'; From 5c3adb2e878656d3c3dd9760310980b5955078b4 Mon Sep 17 00:00:00 2001 From: Koen van Staveren Date: Tue, 24 Sep 2024 22:35:10 +0200 Subject: [PATCH 14/24] Update packages/desktop-client/src/components/transactions/TransactionsTable.jsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../transactions/TransactionsTable.jsx | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx index 980e8e21455..0b068f019b2 100644 --- a/packages/desktop-client/src/components/transactions/TransactionsTable.jsx +++ b/packages/desktop-client/src/components/transactions/TransactionsTable.jsx @@ -1100,21 +1100,16 @@ const Transaction = memo(function Transaction({ > onDelete && onDelete(transaction.id)} - onDuplicate={() => onDuplicate && onDuplicate(transaction.id)} - onLinkSchedule={() => - onLinkSchedule && onLinkSchedule(transaction.id) - } - onUnlinkSchedule={() => - onUnlinkSchedule && onUnlinkSchedule(transaction.id) - } - onCreateRule={() => onCreateRule && onCreateRule(transaction.id)} + onDelete={() => onDelete?.(transaction.id)} + onDuplicate={() => onDuplicate?.(transaction.id)} + onLinkSchedule={() => onLinkSchedule?.(transaction.id)} + onUnlinkSchedule={() => onUnlinkSchedule?.(transaction.id)} + onCreateRule={() => onCreateRule?.(transaction.id)} onScheduleAction={action => - onScheduleAction && onScheduleAction(action, transaction.id) + onScheduleAction?.(action, transaction.id) } onMakeAsNonSplitTransactions={() => - onMakeAsNonSplitTransactions && - onMakeAsNonSplitTransactions(transaction.id) + onMakeAsNonSplitTransactions?.(transaction.id) } closeMenu={() => setMenuOpen(false)} /> From 227988ec19a3b40bfa1a4dbcfe8a830409196dd1 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Fri, 4 Oct 2024 22:21:22 +0200 Subject: [PATCH 15/24] chore: fix merge --- .../desktop-client/src/components/payees/PayeeTableRow.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx index e1e72e8faa1..b19334eb232 100644 --- a/packages/desktop-client/src/components/payees/PayeeTableRow.tsx +++ b/packages/desktop-client/src/components/payees/PayeeTableRow.tsx @@ -1,6 +1,5 @@ // @ts-strict-ignore import { memo, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next'; import { type PayeeEntity } from 'loot-core/src/types/models'; @@ -101,8 +100,6 @@ export const PayeeTableRow = memo( onUpdate, style, }: PayeeTableRowProps) => { - const { t } = useTranslation(); - const { id } = payee; const dispatchSelected = useSelectedDispatch(); const borderColor = selected From 28c6856ad65e5a424d6eebae577d5db67f7f3243 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Fri, 4 Oct 2024 22:45:54 +0200 Subject: [PATCH 16/24] fix: e2e test --- .../budget/envelope/BalanceMovementMenu.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/desktop-client/src/components/budget/envelope/BalanceMovementMenu.tsx b/packages/desktop-client/src/components/budget/envelope/BalanceMovementMenu.tsx index eed672b2acf..f83ce9f2d63 100644 --- a/packages/desktop-client/src/components/budget/envelope/BalanceMovementMenu.tsx +++ b/packages/desktop-client/src/components/budget/envelope/BalanceMovementMenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useRef, useState } from 'react'; import { envelopeBudget } from 'loot-core/src/client/queries'; @@ -23,10 +23,20 @@ export function BalanceMovementMenu({ const catBalance = useEnvelopeSheetValue( envelopeBudget.catBalance(categoryId), ); - const [menu, setMenu] = useState('menu'); + const [menu, _setMenu] = useState('menu'); + + const ref = useRef(null); + // Keep focus inside the popover on menu change + const setMenu = useCallback( + (menu: string) => { + ref.current?.focus(); + _setMenu(menu); + }, + [ref], + ); return ( - <> + {menu === 'menu' && ( )} - + ); } From 93f2b2ad00006a3f0687d0d5e1b399e8c8e0ca14 Mon Sep 17 00:00:00 2001 From: UnderKoen Date: Sun, 13 Oct 2024 12:58:20 +0200 Subject: [PATCH 17/24] fix: moving of the popover in the sidebar --- .../desktop-client/src/components/budget/SidebarCategory.tsx | 4 +++- .../desktop-client/src/components/budget/SidebarGroup.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/desktop-client/src/components/budget/SidebarCategory.tsx b/packages/desktop-client/src/components/budget/SidebarCategory.tsx index cbbd41b20d7..49bf633d976 100644 --- a/packages/desktop-client/src/components/budget/SidebarCategory.tsx +++ b/packages/desktop-client/src/components/budget/SidebarCategory.tsx @@ -61,7 +61,9 @@ export function SidebarCategory({ WebkitUserSelect: 'none', opacity: category.hidden || categoryGroup?.hidden ? 0.33 : undefined, backgroundColor: 'transparent', + height: 20, }} + ref={triggerRef} onContextMenu={e => { e.preventDefault(); setMenuOpen(true); @@ -78,7 +80,7 @@ export function SidebarCategory({ > {category.name}
- +