feat(CommandBar): display account balances (#5355)

* feat(CommandBar): display account balances

* add release note

* rename release note
This commit is contained in:
Elijah Olmos
2025-07-21 09:36:05 -07:00
committed by GitHub
parent 9aed1f400c
commit 548f36e8d6
2 changed files with 89 additions and 7 deletions

View File

@@ -1,5 +1,6 @@
import {
type ComponentType,
type ReactNode,
type SVGProps,
useCallback,
useEffect,
@@ -22,18 +23,41 @@ import {
SvgCalendar3,
SvgNotesPaperText,
} from '@actual-app/components/icons/v2';
import { styles } from '@actual-app/components/styles';
import { Text } from '@actual-app/components/text';
import { View } from '@actual-app/components/view';
import { css } from '@emotion/css';
import { Command } from 'cmdk';
import { CellValue, CellValueText } from './spreadsheet/CellValue';
import { useAccounts } from '@desktop-client/hooks/useAccounts';
import { useMetadataPref } from '@desktop-client/hooks/useMetadataPref';
import { useModalState } from '@desktop-client/hooks/useModalState';
import { useNavigate } from '@desktop-client/hooks/useNavigate';
import { useReports } from '@desktop-client/hooks/useReports';
import type {
Binding,
SheetFields,
SheetNames,
} from '@desktop-client/spreadsheet';
import {
accountBalance,
allAccountBalance,
offBudgetAccountBalance,
onBudgetAccountBalance,
} from '@desktop-client/spreadsheet/bindings';
type SearchableItem = {
id: string;
/** The name to display and use for searching */
name: string;
/**
* The item content to display. If not provided, {@link SearchableItem.name `name`} will be used.
*
* Meant for complex items that want to display more than just static text.
*/
content?: ReactNode;
Icon: ComponentType<SVGProps<SVGSVGElement>>;
};
@@ -44,6 +68,38 @@ type SearchSection = {
onSelect: (item: Pick<SearchableItem, 'id'>) => void;
};
function BalanceRow<
SheetName extends SheetNames,
FieldName extends SheetFields<SheetName>,
>({
label,
binding,
}: {
label: string;
binding: Binding<SheetName, FieldName>;
}) {
return (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
flex: 1,
}}
>
<Text>{label}</Text>
<CellValue binding={binding} type="financial">
{props => (
<CellValueText
{...props}
style={{ ...styles.tnum, whiteSpace: 'nowrap', opacity: 0.9 }}
/>
)}
</CellValue>
</View>
);
}
export function CommandBar() {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
@@ -75,6 +131,12 @@ export function CommandBar() {
id: 'accounts',
name: t('All Accounts'),
path: '/accounts',
content: (
<BalanceRow<'account', 'accounts-balance'>
label={t('All Accounts')}
binding={allAccountBalance()}
/>
),
Icon: SvgLibrary,
},
],
@@ -120,11 +182,7 @@ export function CommandBar() {
{
key: 'navigation',
heading: t('Navigation'),
items: navigationItems.map(({ id, name, Icon }) => ({
id,
name,
Icon,
})),
items: navigationItems,
onSelect: ({ id }) => {
const item = navigationItems.find(item => item.id === id);
if (!!item) handleNavigate(item.path);
@@ -137,15 +195,33 @@ export function CommandBar() {
{
id: 'onbudget',
name: t('On Budget'),
content: (
<BalanceRow<'account', 'onbudget-accounts-balance'>
label={t('On Budget')}
binding={onBudgetAccountBalance()}
/>
),
Icon: SvgLibrary,
},
{
id: 'offbudget',
name: t('Off Budget'),
content: (
<BalanceRow<'account', 'offbudget-accounts-balance'>
label={t('Off Budget')}
binding={offBudgetAccountBalance()}
/>
),
Icon: SvgLibrary,
},
...accounts.map(account => ({
...account,
content: (
<BalanceRow<'account', 'balance'>
label={account.name}
binding={accountBalance(account.id)}
/>
),
Icon: SvgPiggyBank,
})),
],
@@ -245,7 +321,7 @@ export function CommandBar() {
},
})}
>
{section.items.map(({ id, name, Icon }) => (
{section.items.map(({ id, name, Icon, content }) => (
<Command.Item
key={id}
onSelect={() => section.onSelect({ id })}
@@ -274,7 +350,7 @@ export function CommandBar() {
})}
>
<Icon width={16} height={16} />
{name}
{content || name}
</Command.Item>
))}
</Command.Group>