diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png index a6bf93a8ad9155a83e5c7184f0cb8bf0a4c8d3c2..3210c3fbdb44e30af96936ba4786f829441199d3 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png index 77e03e79be6d838b06f7df845d453ae255318410..417b2394f5dee777978ac74344f4fd189af50b37 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png index 75cd2a5fd873710191de011d2c977c0c85542b05..c2fa9a0210ce6607d32f355d01059d7df7b0cad0 100644 Binary files a/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png and b/packages/desktop-client/e2e/budget.test.js-snapshots/Budget-transfer-funds-to-another-category-3-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png index ae0a737040551d60c17cd86b8d7e971dc6e9e3c3..c8e0b47f5278f7b6f1f48e80b10734b3a58a7907 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-1-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png index 9ed4d23ce0c6065bc005f6516f753a71ab54ee59..59bb41b121d02313d95529fe811ede8c18e45407 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-2-chromium-linux.png differ diff --git a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png index a1c7e77c60a5b7e6d9733e88ab0856daef32ce7b..49df0e1a1d09676e154906bcd86f29bd2bbbe5f5 100644 Binary files a/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png and b/packages/desktop-client/e2e/mobile.test.js-snapshots/Mobile-loads-the-budget-page-with-budgeted-amounts-3-chromium-linux.png differ diff --git a/packages/desktop-client/src/components/accounts/Balance.jsx b/packages/desktop-client/src/components/accounts/Balance.jsx index 3422add9161372e1c1d79fdc15014f1d917729f1..0a0dc5c73503cbd63ecf4f3140c03208ebaeb013 100644 --- a/packages/desktop-client/src/components/accounts/Balance.jsx +++ b/packages/desktop-client/src/components/accounts/Balance.jsx @@ -15,7 +15,7 @@ import { Button } from '../common/Button2'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { PrivacyFilter } from '../PrivacyFilter'; -import { CellValue } from '../spreadsheet/CellValue'; +import { CellValue, CellValueText } from '../spreadsheet/CellValue'; import { useFormat } from '../spreadsheet/useFormat'; import { useSheetValue } from '../spreadsheet/useSheetValue'; @@ -172,22 +172,25 @@ export function Balances({ paddingBottom: 1, }} > - <CellValue - binding={{ ...balanceQuery, value: 0 }} - type="financial" - style={{ fontSize: 22, fontWeight: 400 }} - getStyle={value => ({ - color: - value < 0 - ? theme.errorText - : value > 0 - ? theme.noticeTextLight - : theme.pageTextSubdued, - })} - privacyFilter={{ - blurIntensity: 5, - }} - /> + <CellValue binding={{ ...balanceQuery, value: 0 }} type="financial"> + {props => ( + <PrivacyFilter blurIntensity={5}> + <CellValueText + {...props} + style={{ + fontSize: 22, + fontWeight: 400, + color: + props.value < 0 + ? theme.errorText + : props.value > 0 + ? theme.noticeTextLight + : theme.pageTextSubdued, + }} + /> + </PrivacyFilter> + )} + </CellValue> <SvgArrowButtonRight1 style={{ diff --git a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx index d788509bef76e86d17f36c3b4c06b03ba9c232ec..7e97f7cf2c19585099c50db0ece9ab8f3af12326 100644 --- a/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx +++ b/packages/desktop-client/src/components/budget/BalanceWithCarryover.tsx @@ -1,5 +1,9 @@ // @ts-strict-ignore -import React, { type ComponentPropsWithoutRef } from 'react'; +import React, { + type ComponentType, + type ComponentPropsWithoutRef, + useCallback, +} from 'react'; import { useTranslation } from 'react-i18next'; import { useFeatureFlag } from '../../hooks/useFeatureFlag'; @@ -8,7 +12,7 @@ import { type CSSProperties, theme, styles } from '../../style'; import { Tooltip } from '../common/Tooltip'; import { View } from '../common/View'; import { type Binding } from '../spreadsheet'; -import { CellValue } from '../spreadsheet/CellValue'; +import { CellValue, CellValueText } from '../spreadsheet/CellValue'; import { useFormat } from '../spreadsheet/useFormat'; import { useSheetValue } from '../spreadsheet/useSheetValue'; @@ -18,20 +22,7 @@ type CarryoverIndicatorProps = { style?: CSSProperties; }; -type BalanceWithCarryoverProps = Omit< - ComponentPropsWithoutRef<typeof CellValue>, - 'binding' -> & { - carryover: Binding<'rollover-budget', 'carryover'>; - balance: Binding<'rollover-budget', 'leftover'>; - goal: Binding<'rollover-budget', 'goal'>; - budgeted: Binding<'rollover-budget', 'budget'>; - longGoal: Binding<'rollover-budget', 'long-goal'>; - disabled?: boolean; - carryoverIndicator?: ({ style }: CarryoverIndicatorProps) => JSX.Element; -}; - -export function DefaultCarryoverIndicator({ style }: CarryoverIndicatorProps) { +export function CarryoverIndicator({ style }: CarryoverIndicatorProps) { return ( <View style={{ @@ -68,6 +59,19 @@ function GoalTooltipRow({ children }) { ); } +type BalanceWithCarryoverProps = Omit< + ComponentPropsWithoutRef<typeof CellValue>, + 'binding' +> & { + carryover: Binding<'rollover-budget', 'carryover'>; + balance: Binding<'rollover-budget', 'leftover'>; + goal: Binding<'rollover-budget', 'goal'>; + budgeted: Binding<'rollover-budget', 'budget'>; + longGoal: Binding<'rollover-budget', 'long-goal'>; + disabled?: boolean; + CarryoverIndicator?: ComponentType<CarryoverIndicatorProps>; +}; + export function BalanceWithCarryover({ carryover, balance, @@ -75,115 +79,124 @@ export function BalanceWithCarryover({ budgeted, longGoal, disabled, - carryoverIndicator = DefaultCarryoverIndicator, + CarryoverIndicator: CarryoverIndicatorComponent = CarryoverIndicator, + children, ...props }: BalanceWithCarryoverProps) { const { t } = useTranslation(); const carryoverValue = useSheetValue(carryover); - const balanceValue = useSheetValue(balance); const goalValue = useSheetValue(goal); const budgetedValue = useSheetValue(budgeted); const longGoalValue = useSheetValue(longGoal); const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled'); - const valueStyle = makeBalanceAmountStyle( - balanceValue, - isGoalTemplatesEnabled ? goalValue : null, - longGoalValue === 1 ? balanceValue : budgetedValue, + const getBalanceStyle = useCallback( + (balanceValue: number) => + makeBalanceAmountStyle( + balanceValue, + isGoalTemplatesEnabled ? goalValue : null, + longGoalValue === 1 ? balanceValue : budgetedValue, + ), + [budgetedValue, goalValue, isGoalTemplatesEnabled, longGoalValue], ); const format = useFormat(); - const differenceToGoal = - longGoalValue === 1 ? balanceValue - goalValue : budgetedValue - goalValue; - - const balanceCellValue = ( - <CellValue - {...props} - binding={balance} - type="financial" - getStyle={value => - makeBalanceAmountStyle( - value, - isGoalTemplatesEnabled ? goalValue : null, - longGoalValue === 1 ? balanceValue : budgetedValue, - ) - } - style={{ - overflow: 'hidden', - textOverflow: 'ellipsis', - textAlign: 'right', - ...(!disabled && { - cursor: 'pointer', - }), - ...props.style, - }} - /> + const differenceToGoal = useCallback( + (balanceValue: number) => + longGoalValue === 1 + ? balanceValue - goalValue + : budgetedValue - goalValue, + [budgetedValue, goalValue, longGoalValue], ); return ( - <span - style={{ - alignItems: 'center', - display: 'inline-flex', - justifyContent: 'right', - maxWidth: '100%', - }} - > - {isGoalTemplatesEnabled && goalValue !== null ? ( - <Tooltip - content={ - <View style={{ padding: 10 }}> - <span style={{ fontWeight: 'bold' }}> - {differenceToGoal === 0 ? ( - <span style={{ color: theme.noticeText }}> - {t('Fully funded')} - </span> - ) : differenceToGoal > 0 ? ( - <span style={{ color: theme.noticeText }}> - {t('Overfunded ({{amount}})', { - amount: format(differenceToGoal, 'financial'), - })} - </span> - ) : ( - <span style={{ color: theme.errorText }}> - {t('Underfunded ({{amount}})', { - amount: format(differenceToGoal, 'financial'), - })} + <CellValue binding={balance} type="financial" {...props}> + {({ type, name, value: balanceValue }) => ( + <> + {children ? ( + children({ type, name, value: balanceValue }) + ) : ( + <Tooltip + content={ + <View style={{ padding: 10 }}> + <span style={{ fontWeight: 'bold' }}> + {differenceToGoal(balanceValue) === 0 ? ( + <span style={{ color: theme.noticeText }}> + {t('Fully funded')} + </span> + ) : differenceToGoal(balanceValue) > 0 ? ( + <span style={{ color: theme.noticeText }}> + {t('Overfunded ({{amount}})', { + amount: format( + differenceToGoal(balanceValue), + 'financial', + ), + })} + </span> + ) : ( + <span style={{ color: theme.errorText }}> + {t('Underfunded ({{amount}})', { + amount: format( + differenceToGoal(balanceValue), + 'financial', + ), + })} + </span> + )} </span> - )} - </span> - <GoalTooltipRow> - <div>{t('Goal Type:')}</div> - <div>{longGoalValue === 1 ? 'Long' : 'Template'}</div> - </GoalTooltipRow> - <GoalTooltipRow> - <div>{t('Goal:')}</div> - <div>{format(goalValue, 'financial')}</div> - </GoalTooltipRow> - <GoalTooltipRow> - {longGoalValue !== 1 ? ( - <> - <div>{t('Budgeted:')}</div> - <div>{format(budgetedValue, 'financial')}</div> - </> - ) : ( - <> - <div>{t('Balance:')}</div> - <div>{format(balanceValue, 'financial')}</div> - </> - )} - </GoalTooltipRow> - </View> - } - style={{ ...styles.tooltip, borderRadius: '0px 5px 5px 0px' }} - placement="bottom" - triggerProps={{ delay: 750 }} - > - {balanceCellValue} - </Tooltip> - ) : ( - balanceCellValue + <GoalTooltipRow> + <div>{t('Goal Type:')}</div> + <div>{longGoalValue === 1 ? 'Long' : 'Template'}</div> + </GoalTooltipRow> + <GoalTooltipRow> + <div>{t('Goal:')}</div> + <div>{format(goalValue, 'financial')}</div> + </GoalTooltipRow> + <GoalTooltipRow> + {longGoalValue !== 1 ? ( + <> + <div>{t('Budgeted:')}</div> + <div>{format(budgetedValue, 'financial')}</div> + </> + ) : ( + <> + <div>{t('Balance:')}</div> + <div>{format(balanceValue, type)}</div> + </> + )} + </GoalTooltipRow> + </View> + } + style={{ ...styles.tooltip, borderRadius: '0px 5px 5px 0px' }} + placement="bottom" + triggerProps={{ + delay: 750, + isDisabled: !isGoalTemplatesEnabled || goalValue == null, + }} + > + <CellValueText + type={type} + name={name} + value={balanceValue} + style={{ + ...getBalanceStyle(balanceValue), + overflow: 'hidden', + textOverflow: 'ellipsis', + textAlign: 'right', + ...(!disabled && { + cursor: 'pointer', + }), + ':hover': { textDecoration: 'underline' }, + }} + /> + </Tooltip> + )} + {carryoverValue && ( + <CarryoverIndicatorComponent + style={getBalanceStyle(balanceValue)} + /> + )} + </> )} - {carryoverValue && carryoverIndicator({ style: valueStyle })} - </span> + </CellValue> ); } diff --git a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx index 2d8157df8bb5e61c961b103f80239cf6327171d6..6e7060cedc5d19f003187a9ccf9e726494a9ed85 100644 --- a/packages/desktop-client/src/components/budget/report/ReportComponents.tsx +++ b/packages/desktop-client/src/components/budget/report/ReportComponents.tsx @@ -1,5 +1,5 @@ // @ts-strict-ignore -import React, { memo, useRef, useState } from 'react'; +import React, { type ComponentProps, memo, useRef, useState } from 'react'; import { Trans } from 'react-i18next'; import { reportBudget } from 'loot-core/src/client/queries'; @@ -15,8 +15,7 @@ import { Popover } from '../../common/Popover'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { type Binding, type SheetFields } from '../../spreadsheet'; -import { CellValue, type CellValueProps } from '../../spreadsheet/CellValue'; -import { useFormat } from '../../spreadsheet/useFormat'; +import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { Field, SheetCell, type SheetCellProps } from '../../table'; import { BalanceWithCarryover } from '../BalanceWithCarryover'; @@ -34,7 +33,7 @@ export const useReportSheetValue = < }; const ReportCellValue = <FieldName extends SheetFields<'report-budget'>>( - props: CellValueProps<'report-budget', FieldName>, + props: ComponentProps<typeof CellValue<'report-budget', FieldName>>, ) => { return <CellValue {...props} />; }; @@ -50,8 +49,13 @@ const headerLabelStyle: CSSProperties = { padding: '0 5px', textAlign: 'right', }; + +const cellStyle: CSSProperties = { + color: theme.pageTextLight, + fontWeight: 600, +}; + export const BudgetTotalsMonth = memo(function BudgetTotalsMonth() { - const format = useFormat(); return ( <View style={{ @@ -69,31 +73,25 @@ export const BudgetTotalsMonth = memo(function BudgetTotalsMonth() { <ReportCellValue binding={reportBudget.totalBudgetedExpense} type="financial" - style={{ color: theme.pageTextLight, fontWeight: 600 }} - formatter={value => { - return format(parseFloat(value || '0'), 'financial'); - }} - /> + > + {props => <CellValueText {...props} style={cellStyle} />} + </ReportCellValue> </View> <View style={headerLabelStyle}> <Text style={{ color: theme.pageTextLight }}> <Trans>Spent</Trans> </Text> - <ReportCellValue - binding={reportBudget.totalSpent} - type="financial" - style={{ color: theme.pageTextLight, fontWeight: 600 }} - /> + <ReportCellValue binding={reportBudget.totalSpent} type="financial"> + {props => <CellValueText {...props} style={cellStyle} />} + </ReportCellValue> </View> <View style={headerLabelStyle}> <Text style={{ color: theme.pageTextLight }}> <Trans>Balance</Trans> </Text> - <ReportCellValue - binding={reportBudget.totalLeftover} - type="financial" - style={{ color: theme.pageTextLight, fontWeight: 600 }} - /> + <ReportCellValue binding={reportBudget.totalLeftover} type="financial"> + {props => <CellValueText {...props} style={cellStyle} />} + </ReportCellValue> </View> </View> ); @@ -365,14 +363,20 @@ export const CategoryMonth = memo(function CategoryMonth({ <ReportCellValue binding={reportBudget.catSumAmount(category.id)} type="financial" - getStyle={makeAmountGrey} - style={{ - cursor: 'pointer', - ':hover': { - textDecoration: 'underline', - }, - }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ + cursor: 'pointer', + ':hover': { + textDecoration: 'underline', + }, + ...makeAmountGrey(props.value), + }} + /> + )} + </ReportCellValue> </span> </Field> @@ -384,9 +388,7 @@ export const CategoryMonth = memo(function CategoryMonth({ > <span ref={triggerBalanceMenuRef} - {...(category.is_income - ? {} - : { onClick: () => setBalanceMenuOpen(true) })} + onClick={() => !category.is_income && setBalanceMenuOpen(true)} > <BalanceWithCarryover disabled={category.is_income} @@ -395,9 +397,6 @@ export const CategoryMonth = memo(function CategoryMonth({ goal={reportBudget.catGoal(category.id)} budgeted={reportBudget.catBudgeted(category.id)} longGoal={reportBudget.catLongGoal(category.id)} - style={{ - ':hover': { textDecoration: 'underline' }, - }} /> </span> diff --git a/packages/desktop-client/src/components/budget/report/budgetsummary/BudgetTotal.tsx b/packages/desktop-client/src/components/budget/report/budgetsummary/BudgetTotal.tsx index 2f3966d0fd46b9310de4a9aa6764b5816cd4318d..5767dace486141355ab8e206d3a3b5b2bd25cd74 100644 --- a/packages/desktop-client/src/components/budget/report/budgetsummary/BudgetTotal.tsx +++ b/packages/desktop-client/src/components/budget/report/budgetsummary/BudgetTotal.tsx @@ -10,7 +10,7 @@ import { theme, styles } from '../../../../style'; import { Text } from '../../../common/Text'; import { View } from '../../../common/View'; import { type SheetFields, type Binding } from '../../../spreadsheet'; -import { CellValue } from '../../../spreadsheet/CellValue'; +import { CellValue, CellValueText } from '../../../spreadsheet/CellValue'; type BudgetTotalProps< CurrentField extends SheetFields<'report-budget'>, @@ -55,11 +55,9 @@ export function BudgetTotal< <Text style={{ color: theme.pageTextSubdued, fontStyle: 'italic' }}> {' '} {t('of')}{' '} - <CellValue - binding={target} - type="financial" - style={styles.notFixed} - /> + <CellValue binding={target} type="financial"> + {props => <CellValueText {...props} style={styles.notFixed} />} + </CellValue> </Text> </Text> </View> diff --git a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx index f4638d934f84be6d15f841922156f64e47d57720..f1e873d00647579ed1dba8b856722974cd2c168f 100644 --- a/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx +++ b/packages/desktop-client/src/components/budget/rollover/RolloverComponents.tsx @@ -1,4 +1,4 @@ -import React, { memo, useRef, useState } from 'react'; +import React, { type ComponentProps, memo, useRef, useState } from 'react'; import { rolloverBudget } from 'loot-core/src/client/queries'; import { evalArithmetic } from 'loot-core/src/shared/arithmetic'; @@ -13,8 +13,7 @@ import { Popover } from '../../common/Popover'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { type Binding, type SheetFields } from '../../spreadsheet'; -import { CellValue, type CellValueProps } from '../../spreadsheet/CellValue'; -import { useFormat } from '../../spreadsheet/useFormat'; +import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetName } from '../../spreadsheet/useSheetName'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { Row, Field, SheetCell, type SheetCellProps } from '../../table'; @@ -39,7 +38,7 @@ export function useRolloverSheetValue< export const RolloverCellValue = < FieldName extends SheetFields<'rollover-budget'>, >( - props: CellValueProps<'rollover-budget', FieldName>, + props: ComponentProps<typeof CellValue<'rollover-budget', FieldName>>, ) => { return <CellValue {...props} />; }; @@ -56,8 +55,12 @@ const headerLabelStyle: CSSProperties = { textAlign: 'right', }; +const cellStyle: CSSProperties = { + color: theme.tableHeaderText, + fontWeight: 600, +}; + export const BudgetTotalsMonth = memo(function BudgetTotalsMonth() { - const format = useFormat(); return ( <View style={{ @@ -73,27 +76,26 @@ export const BudgetTotalsMonth = memo(function BudgetTotalsMonth() { <RolloverCellValue binding={rolloverBudget.totalBudgeted} type="financial" - style={{ color: theme.tableHeaderText, fontWeight: 600 }} - formatter={value => { - return format(-parseFloat(value || '0'), 'financial'); - }} - /> + > + {props => ( + <CellValueText {...props} value={-props.value} style={cellStyle} /> + )} + </RolloverCellValue> </View> <View style={headerLabelStyle}> <Text style={{ color: theme.tableHeaderText }}>Spent</Text> - <RolloverCellValue - binding={rolloverBudget.totalSpent} - type="financial" - style={{ color: theme.tableHeaderText, fontWeight: 600 }} - /> + <RolloverCellValue binding={rolloverBudget.totalSpent} type="financial"> + {props => <CellValueText {...props} style={cellStyle} />} + </RolloverCellValue> </View> <View style={headerLabelStyle}> <Text style={{ color: theme.tableHeaderText }}>Balance</Text> <RolloverCellValue binding={rolloverBudget.totalBalance} type="financial" - style={{ color: theme.tableHeaderText, fontWeight: 600 }} - /> + > + {props => <CellValueText {...props} style={cellStyle} />} + </RolloverCellValue> </View> </View> ); @@ -353,12 +355,18 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ <RolloverCellValue binding={rolloverBudget.catSumAmount(category.id)} type="financial" - getStyle={makeAmountGrey} - style={{ - cursor: 'pointer', - ':hover': { textDecoration: 'underline' }, - }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ + cursor: 'pointer', + ':hover': { textDecoration: 'underline' }, + ...makeAmountGrey(props.value), + }} + /> + )} + </RolloverCellValue> </span> </Field> <Field @@ -376,9 +384,6 @@ export const ExpenseCategoryMonth = memo(function ExpenseCategoryMonth({ goal={rolloverBudget.catGoal(category.id)} budgeted={rolloverBudget.catBudgeted(category.id)} longGoal={rolloverBudget.catLongGoal(category.id)} - style={{ - ':hover': { textDecoration: 'underline' }, - }} /> </span> @@ -463,11 +468,17 @@ export function IncomeCategoryMonth({ <RolloverCellValue binding={rolloverBudget.catSumAmount(category.id)} type="financial" - style={{ - cursor: 'pointer', - ':hover': { textDecoration: 'underline' }, - }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ + cursor: 'pointer', + ':hover': { textDecoration: 'underline' }, + }} + /> + )} + </RolloverCellValue> </span> </Field> </View> diff --git a/packages/desktop-client/src/components/budget/rollover/budgetsummary/TotalsList.tsx b/packages/desktop-client/src/components/budget/rollover/budgetsummary/TotalsList.tsx index a007d602648c310b7e1f06127a6ce55642b16145..3b6e760d0da157a4bdd8b39dda70c304b78c2cc8 100644 --- a/packages/desktop-client/src/components/budget/rollover/budgetsummary/TotalsList.tsx +++ b/packages/desktop-client/src/components/budget/rollover/budgetsummary/TotalsList.tsx @@ -7,6 +7,7 @@ import { AlignedText } from '../../../common/AlignedText'; import { Block } from '../../../common/Block'; import { Tooltip } from '../../../common/Tooltip'; import { View } from '../../../common/View'; +import { CellValueText } from '../../../spreadsheet/CellValue'; import { useFormat } from '../../../spreadsheet/useFormat'; import { RolloverCellValue } from '../RolloverComponents'; @@ -14,6 +15,7 @@ type TotalsListProps = { prevMonthName: string; style?: CSSProperties; }; + export function TotalsList({ prevMonthName, style }: TotalsListProps) { const format = useFormat(); return ( @@ -43,7 +45,6 @@ export function TotalsList({ prevMonthName, style }: TotalsListProps) { <RolloverCellValue binding={rolloverBudget.totalIncome} type="financial" - privacyFilter={false} /> } /> @@ -53,7 +54,6 @@ export function TotalsList({ prevMonthName, style }: TotalsListProps) { <RolloverCellValue binding={rolloverBudget.fromLastMonth} type="financial" - privacyFilter={false} /> } /> @@ -64,40 +64,58 @@ export function TotalsList({ prevMonthName, style }: TotalsListProps) { <RolloverCellValue binding={rolloverBudget.incomeAvailable} type="financial" - style={{ fontWeight: 600 }} - /> + > + {props => <CellValueText {...props} style={{ fontWeight: 600 }} />} + </RolloverCellValue> </Tooltip> <RolloverCellValue binding={rolloverBudget.lastMonthOverspent} type="financial" - formatter={value => { - const v = format(value, 'financial'); - return value > 0 ? '+' + v : value === 0 ? '-' + v : v; - }} - style={{ fontWeight: 600, ...styles.tnum }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ fontWeight: 600 }} + formatter={(value, type) => { + const v = format(value, type); + return value > 0 ? '+' + v : value === 0 ? '-' + v : v; + }} + /> + )} + </RolloverCellValue> <RolloverCellValue binding={rolloverBudget.totalBudgeted} type="financial" - formatter={value => { - const v = format(value, 'financial'); - return value > 0 ? '+' + v : value === 0 ? '-' + v : v; - }} - style={{ fontWeight: 600, ...styles.tnum }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ fontWeight: 600 }} + formatter={(value, type) => { + const v = format(value, type); + return value > 0 ? '+' + v : value === 0 ? '-' + v : v; + }} + /> + )} + </RolloverCellValue> <RolloverCellValue binding={rolloverBudget.forNextMonth} type="financial" - formatter={value => { - const n = parseInt(value) || 0; - const v = format(Math.abs(n), 'financial'); - return n >= 0 ? '-' + v : '+' + v; - }} - style={{ fontWeight: 600, ...styles.tnum }} - /> + > + {props => ( + <CellValueText + {...props} + style={{ fontWeight: 600 }} + formatter={(value, type) => { + const v = format(Math.abs(value), type); + return value >= 0 ? '-' + v : '+' + v; + }} + /> + )} + </RolloverCellValue> </View> <View> diff --git a/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx b/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx index 10fa6a20742a0eb3d23cfa05ff9cc4ce5e0d5a74..2649951427ce4898a389e33583460cda4ab92134 100644 --- a/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx +++ b/packages/desktop-client/src/components/mobile/accounts/Accounts.jsx @@ -19,7 +19,7 @@ import { Text } from '../../common/Text'; import { TextOneLine } from '../../common/TextOneLine'; import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; -import { CellValue } from '../../spreadsheet/CellValue'; +import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { MOBILE_NAV_HEIGHT } from '../MobileNavTabs'; import { PullToRefresh } from '../PullToRefresh'; @@ -47,11 +47,11 @@ function AccountHeader({ name, amount, style = {} }) { {name} </Text> </View> - <CellValue - binding={amount} - style={{ ...styles.text, fontSize: 14 }} - type="financial" - /> + <CellValue binding={amount} type="financial"> + {props => ( + <CellValueText {...props} style={{ ...styles.text, fontSize: 14 }} /> + )} + </CellValue> </View> ); } @@ -119,13 +119,19 @@ function AccountCard({ </TextOneLine> </View> </View> - <CellValue - binding={getBalanceQuery(account)} - type="financial" - style={{ fontSize: 16, color: 'inherit' }} - getStyle={makeAmountFullStyle} - data-testid="account-balance" - /> + <CellValue binding={getBalanceQuery(account)} type="financial"> + {props => ( + <CellValueText + {...props} + style={{ + fontSize: 16, + color: 'inherit', + ...makeAmountFullStyle(props.value), + }} + data-testid="account-balance" + /> + )} + </CellValue> </Button> ); } diff --git a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx index 5d2e1c892aa5bb89b98b43aa52d88dc27b3f1cfc..6ecc225e7691223af1c1acf83ceec334b999de6f 100644 --- a/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx +++ b/packages/desktop-client/src/components/mobile/budget/BudgetTable.jsx @@ -35,6 +35,7 @@ import { Label } from '../../common/Label'; import { Text } from '../../common/Text'; import { View } from '../../common/View'; import { MobilePageHeader, Page } from '../../Page'; +import { PrivacyFilter } from '../../PrivacyFilter'; import { CellValue } from '../../spreadsheet/CellValue'; import { useFormat } from '../../spreadsheet/useFormat'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; @@ -72,11 +73,7 @@ function ToBudget({ toBudget, onPress, show3Cols }) { width: sidebarColumnWidth, }} > - <Button - variant="bare" - style={{ maxWidth: sidebarColumnWidth }} - onPress={onPress} - > + <Button variant="bare" onPress={onPress}> <View> <Label title={amount < 0 ? 'Overbudgeted' : 'To Budget'} @@ -87,27 +84,28 @@ function ToBudget({ toBudget, onPress, show3Cols }) { textAlign: 'left', }} /> - <CellValue - binding={toBudget} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: sidebarColumnWidth, - fontSize: 12, - fontWeight: '700', - color: amount < 0 ? theme.errorText : theme.formInputText, - }} - > - {format(value, 'financial')} - </AutoTextSize> + <CellValue binding={toBudget} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + fontSize: 12, + fontWeight: '700', + color: amount < 0 ? theme.errorText : theme.formInputText, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> )} - /> + </CellValue> </View> <SvgCheveronRight style={{ @@ -142,14 +140,10 @@ function Saved({ projected, onPress, show3Cols }) { width: sidebarColumnWidth, }} > - <Button - variant="bare" - style={{ maxWidth: sidebarColumnWidth }} - onPress={onPress} - > - <View> - <View> - {projected ? ( + <Button variant="bare" onPress={onPress}> + <View style={{ alignItems: 'flex-start' }}> + {projected ? ( + <View> <AutoTextSize as={Label} minFontSizePx={6} @@ -157,48 +151,49 @@ function Saved({ projected, onPress, show3Cols }) { mode="oneline" title="Projected Savings" style={{ - maxWidth: sidebarColumnWidth, color: theme.formInputText, textAlign: 'left', fontSize: 12, }} /> - ) : ( - <Label - title={isNegative ? 'Overspent' : 'Saved'} - style={{ - color: theme.formInputText, - textAlign: 'left', - }} - /> - )} - </View> + </View> + ) : ( + <Label + title={isNegative ? 'Overspent' : 'Saved'} + style={{ + color: theme.formInputText, + textAlign: 'left', + }} + /> + )} - <CellValue - binding={binding} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: sidebarColumnWidth, - fontSize: 12, - fontWeight: '700', - color: projected - ? theme.warningText - : isNegative - ? theme.errorTextDark - : theme.formInputText, - }} - > - {format(value, 'financial')} - </AutoTextSize> + <CellValue binding={binding} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + textAlign: 'left', + fontSize: 12, + fontWeight: '700', + color: projected + ? theme.warningText + : isNegative + ? theme.errorTextDark + : theme.formInputText, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> )} - /> + </CellValue> </View> <SvgCheveronRight style={{ @@ -221,9 +216,12 @@ function BudgetCell({ category, month, onBudgetAction, + children, ...props }) { + const columnWidth = getColumnWidth(); const dispatch = useDispatch(); + const format = useFormat(); const { showUndoNotification } = useUndo(); const [budgetType = 'rollover'] = useSyncedPref('budgetType'); @@ -279,17 +277,45 @@ function BudgetCell({ }; return ( - <CellValue - binding={binding} - type="financial" - getStyle={makeAmountGrey} - data-testid={name} - onPointerUp={e => { - e.stopPropagation(); - onOpenCategoryBudgetMenu(); - }} - {...props} - /> + <CellValue binding={binding} type="financial" data-testid={name} {...props}> + {({ type, name, value }) => + children?.({ + type, + name, + value, + onPress: onOpenCategoryBudgetMenu, + }) || ( + <Button + variant="bare" + style={{ + ...PILL_STYLE, + maxWidth: columnWidth, + ...makeAmountGrey(value), + }} + onPress={onOpenCategoryBudgetMenu} + > + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> + </Button> + ) + } + </CellValue> ); } @@ -548,109 +574,96 @@ const ExpenseCategory = memo(function ExpenseCategory({ }} > <BudgetCell + key={`${show3Cols}|${showBudgetedCol}`} name="budgeted" binding={budgeted} + type="financial" category={category} month={month} onBudgetAction={onBudgetAction} - formatter={value => ( - <Button - variant="bare" - style={{ - ...PILL_STYLE, - maxWidth: columnWidth, - }} - > - <AutoTextSize - key={`${value}|${show3Cols}|${showBudgetedCol}`} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - textAlign: 'right', - fontSize: 12, - }} - > - {format(value, 'financial')} - </AutoTextSize> - </Button> - )} /> </View> <View style={{ ...(!show3Cols && showBudgetedCol && { display: 'none' }), + width: columnWidth, justifyContent: 'center', alignItems: 'flex-end', - width: columnWidth, }} > - <CellValue - name="spent" - binding={spent} - getStyle={makeAmountGrey} - type="financial" - onPointerUp={e => { - e.stopPropagation(); - onShowActivity(); - }} - formatter={value => ( + <CellValue name="spent" binding={spent} type="financial"> + {({ type, value }) => ( <Button variant="bare" style={{ ...PILL_STYLE, - maxWidth: columnWidth, }} + onPress={onShowActivity} > - <AutoTextSize - key={`${value}|${show3Cols}|${showBudgetedCol}`} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - textAlign: 'right', - fontSize: 12, - }} - > - {format(value, 'financial')} - </AutoTextSize> + <PrivacyFilter> + <AutoTextSize + key={`${value}|${show3Cols}|${showBudgetedCol}`} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + ...makeAmountGrey(value), + maxWidth: columnWidth, + textAlign: 'right', + fontSize: 12, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> </Button> )} - /> + </CellValue> </View> <View style={{ ...styles.noTapHighlight, + width: columnWidth, justifyContent: 'center', alignItems: 'flex-end', - width: columnWidth, }} > - <span - role="button" - onPointerUp={e => { - e.stopPropagation(); - onOpenBalanceMenu(); - }} + <BalanceWithCarryover + type="financial" + carryover={carryover} + balance={balance} + goal={goal} + budgeted={budgeted} + longGoal={longGoal} + CarryoverIndicator={({ style }) => ( + <View + style={{ + position: 'absolute', + right: '-3px', + top: '-5px', + borderRadius: '50%', + backgroundColor: style?.color ?? theme.pillText, + }} + > + <SvgArrowThickRight + width={11} + height={11} + style={{ color: theme.pillBackgroundLight }} + /> + </View> + )} > - <BalanceWithCarryover - carryover={carryover} - balance={balance} - goal={goal} - budgeted={budgeted} - longGoal={longGoal} - formatter={value => ( - <Button - variant="bare" - style={{ - ...PILL_STYLE, - maxWidth: columnWidth, - }} - > + {({ type, value }) => ( + <Button + variant="bare" + style={{ + ...PILL_STYLE, + maxWidth: columnWidth, + }} + onPress={onOpenBalanceMenu} + > + <PrivacyFilter> <AutoTextSize key={value} as={Text} @@ -668,29 +681,12 @@ const ExpenseCategory = memo(function ExpenseCategory({ fontSize: 12, }} > - {format(value, 'financial')} + {format(value, type)} </AutoTextSize> - </Button> - )} - carryoverIndicator={({ style }) => ( - <View - style={{ - position: 'absolute', - right: '-3px', - top: '-5px', - borderRadius: '50%', - backgroundColor: style?.color ?? theme.pillText, - }} - > - <SvgArrowThickRight - width={11} - height={11} - style={{ color: theme.pillBackgroundLight }} - /> - </View> - )} - /> - </span> + </PrivacyFilter> + </Button> + )} + </BalanceWithCarryover> </View> </View> </ListItem> @@ -753,6 +749,14 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ }); const columnWidth = getColumnWidth({ show3Cols }); + const amountStyle = { + width: columnWidth, + fontSize: 12, + fontWeight: '500', + paddingLeft: 5, + textAlign: 'right', + }; + const content = ( <ListItem style={{ @@ -840,97 +844,67 @@ const ExpenseGroupHeader = memo(function ExpenseGroupHeader({ }} > <View - style={{ - ...(!show3Cols && !showBudgetedCol && { display: 'none' }), - width: columnWidth, - justifyContent: 'center', - alignItems: 'flex-end', - }} + style={{ ...(!show3Cols && !showBudgetedCol && { display: 'none' }) }} > - <CellValue - binding={budgeted} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - fontSize: 12, - fontWeight: '500', - paddingLeft: 5, - textAlign: 'right', - }} - > - {format(value, 'financial')} - </AutoTextSize> - )} - /> - </View> - <View - style={{ - ...(!show3Cols && showBudgetedCol && { display: 'none' }), - width: columnWidth, - justifyContent: 'center', - alignItems: 'flex-end', - }} - > - <CellValue - binding={spent} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - fontSize: 12, - fontWeight: '500', - paddingLeft: 5, - textAlign: 'right', - }} - > - {format(value, 'financial')} - </AutoTextSize> + <CellValue binding={budgeted} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={amountStyle} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> )} - /> + </CellValue> </View> <View - style={{ - width: columnWidth, - justifyContent: 'center', - alignItems: 'flex-end', - }} + style={{ ...(!show3Cols && showBudgetedCol && { display: 'none' }) }} > - <CellValue - binding={balance} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - fontSize: 12, - fontWeight: '500', - paddingLeft: 5, - textAlign: 'right', - }} - > - {format(value, 'financial')} - </AutoTextSize> + <CellValue binding={spent} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={amountStyle} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> )} - /> + </CellValue> </View> + <CellValue binding={balance} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={amountStyle} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> + )} + </CellValue> </View> {/* {editMode && ( @@ -1062,17 +1036,37 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ }} > {budgeted && ( - <View - style={{ - justifyContent: 'center', - alignItems: 'flex-end', - width: columnWidth, - }} - > - <CellValue - binding={budgeted} - type="financial" - formatter={value => ( + <CellValue binding={budgeted} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + width: columnWidth, + justifyContent: 'center', + alignItems: 'flex-end', + paddingLeft: 5, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> + )} + </CellValue> + )} + <CellValue binding={balance} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> <AutoTextSize key={value} as={Text} @@ -1080,49 +1074,21 @@ const IncomeGroupHeader = memo(function IncomeGroupHeader({ maxFontSizePx={12} mode="oneline" style={{ - maxWidth: columnWidth, + width: columnWidth, + justifyContent: 'center', + alignItems: 'flex-end', paddingLeft: 5, textAlign: 'right', fontSize: 12, fontWeight: '500', }} > - {format(value, 'financial')} + {format(value, type)} </AutoTextSize> - )} - /> - </View> - )} - <View - style={{ - justifyContent: 'center', - alignItems: 'flex-end', - width: columnWidth, - }} - > - <CellValue - binding={balance} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - paddingLeft: 5, - textAlign: 'right', - fontSize: 12, - fontWeight: '500', - }} - > - {format(value, 'financial')} - </AutoTextSize> - )} - /> - </View> + </PrivacyFilter> + </View> + )} + </CellValue> </View> </ListItem> ); @@ -1217,106 +1183,43 @@ const IncomeCategory = memo(function IncomeCategory({ <BudgetCell name="budgeted" binding={budgeted} + type="financial" category={category} month={month} onBudgetAction={onBudgetAction} - formatter={value => ( - <Button - variant="bare" - style={{ ...PILL_STYLE, maxWidth: columnWidth }} - > - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - textAlign: 'right', - fontSize: 12, - }} - > - {format(value, 'financial')} - </AutoTextSize> - </Button> - )} /> </View> )} - <View - style={{ - justifyContent: 'center', - alignItems: 'flex-end', - width: columnWidth, - paddingRight: 5, - }} - > - <CellValue - binding={balance} - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - textAlign: 'right', - fontSize: 12, - }} - > - {format(value, 'financial')} - </AutoTextSize> - )} - /> - </View> + <CellValue binding={balance} type="financial"> + {({ type, value }) => ( + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + width: columnWidth, + justifyContent: 'center', + alignItems: 'flex-end', + textAlign: 'right', + fontSize: 12, + paddingRight: 5, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> + )} + </CellValue> </View> </ListItem> ); }); -// export function BudgetAccessoryView() { -// let emitter = useContext(AmountAccessoryContext); - -// return ( -// <View> -// <View -// style={{ -// flexDirection: 'row', -// justifyContent: 'flex-end', -// alignItems: 'stretch', -// backgroundColor: colorsm.tableBackground, -// padding: 5, -// height: 45 -// }} -// > -// <MathOperations emitter={emitter} /> -// <View style={{ flex: 1 }} /> -// <Button -// onPointerUp={() => emitter.emit('moveUp')} -// style={{ marginRight: 5 }} -// data-testid="up" -// > -// <ArrowThinUp width={13} height={13} /> -// </Button> -// <Button -// onPointerUp={() => emitter.emit('moveDown')} -// style={{ marginRight: 5 }} -// data-testid="down" -// > -// <ArrowThinDown width={13} height={13} /> -// </Button> -// <Button onPointerUp={() => emitter.emit('done')} data-testid="done"> -// Done -// </Button> -// </View> -// </View> -// ); -// } - const ExpenseGroup = memo(function ExpenseGroup({ type, group, @@ -1773,6 +1676,14 @@ function BudgetTableHeader({ }; const sidebarColumnWidth = getColumnWidth({ show3Cols, isSidebar: true }); const columnWidth = getColumnWidth({ show3Cols }); + + const amountStyle = { + color: theme.formInputText, + textAlign: 'right', + fontSize: 12, + fontWeight: '500', + }; + return ( <View style={{ @@ -1817,101 +1728,137 @@ function BudgetTableHeader({ }} > {(show3Cols || !showSpentColumn) && ( - <View - style={{ - width: columnWidth, - alignItems: 'flex-end', - }} + <CellValue + binding={ + type === 'report' + ? reportBudget.totalBudgetedExpense + : rolloverBudget.totalBudgeted + } + type="financial" > - <Button - variant="bare" - isDisabled={show3Cols} - onPress={toggleSpentColumn} - style={buttonStyle} - > - <View style={{ alignItems: 'flex-end' }}> - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - {!show3Cols && ( - <SvgViewShow - width={12} - height={12} - style={{ - color: theme.pageTextSubdued, - marginRight: 5, - }} + {({ type, value }) => ( + <Button + variant="bare" + isDisabled={show3Cols} + onPress={toggleSpentColumn} + style={{ + ...buttonStyle, + width: columnWidth, + }} + > + <View style={{ flex: 1, alignItems: 'flex-end' }}> + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + {!show3Cols && ( + <SvgViewShow + width={12} + height={12} + style={{ + flexShrink: 0, + color: theme.pageTextSubdued, + marginRight: 5, + }} + /> + )} + <Label + title="Budgeted" + style={{ color: theme.formInputText, paddingRight: 4 }} /> - )} - <Label - title="Budgeted" - style={{ color: theme.formInputText }} - /> + </View> + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + ...amountStyle, + paddingRight: 4, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> </View> - <CellValue - binding={ - type === 'report' - ? reportBudget.totalBudgetedExpense - : rolloverBudget.totalBudgeted - } - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - color: theme.formInputText, - paddingLeft: 5, - textAlign: 'right', - fontSize: 12, - fontWeight: '500', - }} - > - {format(value, 'financial')} - </AutoTextSize> - )} - /> - </View> - </Button> - </View> + </Button> + )} + </CellValue> )} {(show3Cols || showSpentColumn) && ( - <View - style={{ - width: columnWidth, - alignItems: 'flex-end', - }} + <CellValue + binding={ + type === 'report' + ? reportBudget.totalSpent + : rolloverBudget.totalSpent + } + type="financial" > - <Button - variant="bare" - isDisabled={show3Cols} - onPress={toggleSpentColumn} - style={buttonStyle} - > - <View style={{ alignItems: 'flex-end' }}> - <View style={{ flexDirection: 'row', alignItems: 'center' }}> - {!show3Cols && ( - <SvgViewShow - width={12} - height={12} - style={{ - color: theme.pageTextSubdued, - marginRight: 5, - }} + {({ type, value }) => ( + <Button + variant="bare" + isDisabled={show3Cols} + onPress={toggleSpentColumn} + style={{ + ...buttonStyle, + width: columnWidth, + }} + > + <View style={{ flex: 1, alignItems: 'flex-end' }}> + <View style={{ flexDirection: 'row', alignItems: 'center' }}> + {!show3Cols && ( + <SvgViewShow + width={12} + height={12} + style={{ + flexShrink: 0, + color: theme.pageTextSubdued, + marginRight: 5, + }} + /> + )} + <Label + title="Spent" + style={{ color: theme.formInputText, paddingRight: 4 }} /> - )} - <Label title="Spent" style={{ color: theme.formInputText }} /> + </View> + <View> + <PrivacyFilter> + <AutoTextSize + key={value} + as={Text} + minFontSizePx={6} + maxFontSizePx={12} + mode="oneline" + style={{ + ...amountStyle, + paddingRight: 4, + }} + > + {format(value, type)} + </AutoTextSize> + </PrivacyFilter> + </View> </View> - <CellValue - binding={ - type === 'report' - ? reportBudget.totalSpent - : rolloverBudget.totalSpent - } - type="financial" - formatter={value => ( + </Button> + )} + </CellValue> + )} + <CellValue + binding={ + type === 'report' + ? reportBudget.totalLeftover + : rolloverBudget.totalBalance + } + type="financial" + > + {({ type, value }) => ( + <View style={{ width: columnWidth }}> + <View style={{ flex: 1, alignItems: 'flex-end' }}> + <Label title="Balance" style={{ color: theme.formInputText }} /> + <View> + <PrivacyFilter> <AutoTextSize key={value} as={Text} @@ -1919,57 +1866,17 @@ function BudgetTableHeader({ maxFontSizePx={12} mode="oneline" style={{ - maxWidth: columnWidth, - color: theme.formInputText, - paddingLeft: 5, - textAlign: 'right', - fontSize: 12, - fontWeight: '500', + ...amountStyle, }} > - {format(value, 'financial')} + {format(value, type)} </AutoTextSize> - )} - /> + </PrivacyFilter> + </View> </View> - </Button> - </View> - )} - <View - style={{ - width: columnWidth, - alignItems: 'flex-end', - }} - > - <Label title="Balance" style={{ color: theme.formInputText }} /> - <CellValue - binding={ - type === 'report' - ? reportBudget.totalLeftover - : rolloverBudget.totalBalance - } - type="financial" - formatter={value => ( - <AutoTextSize - key={value} - as={Text} - minFontSizePx={6} - maxFontSizePx={12} - mode="oneline" - style={{ - maxWidth: columnWidth, - color: theme.formInputText, - paddingLeft: 5, - textAlign: 'right', - fontSize: 12, - fontWeight: '500', - }} - > - {format(value, 'financial')} - </AutoTextSize> - )} - /> - </View> + </View> + )} + </CellValue> </View> </View> ); diff --git a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx index 24ce42a6d7e566d9112cf6b237295e01f1e1a482..124ebcf0bf31989c7469036639f04f1e9f5d0abb 100644 --- a/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx +++ b/packages/desktop-client/src/components/mobile/transactions/TransactionListWithBalances.jsx @@ -7,7 +7,7 @@ import { styles, theme } from '../../../style'; import { InputWithContent } from '../../common/InputWithContent'; import { Label } from '../../common/Label'; import { View } from '../../common/View'; -import { CellValue } from '../../spreadsheet/CellValue'; +import { CellValue, CellValueText } from '../../spreadsheet/CellValue'; import { useSheetValue } from '../../spreadsheet/useSheetValue'; import { PullToRefresh } from '../PullToRefresh'; @@ -101,32 +101,38 @@ export function TransactionListWithBalances({ title="Cleared" style={{ textAlign: 'center', fontSize: 12 }} /> - <CellValue - binding={balanceCleared} - type="financial" - style={{ - fontSize: 12, - textAlign: 'center', - fontWeight: '500', - }} - data-testid="transactions-balance-cleared" - /> + <CellValue binding={balanceCleared} type="financial"> + {props => ( + <CellValueText + {...props} + style={{ + fontSize: 12, + textAlign: 'center', + fontWeight: '500', + }} + /> + )} + </CellValue> </View> <View style={{ flexBasis: '33%' }}> <Label title="Balance" style={{ textAlign: 'center' }} /> - <CellValue - binding={balance} - type="financial" - style={{ - fontSize: 18, - textAlign: 'center', - fontWeight: '500', - }} - getStyle={value => ({ - color: value < 0 ? theme.errorText : theme.pillTextHighlighted, - })} - data-testid="transactions-balance" - /> + <CellValue binding={balance} type="financial"> + {props => ( + <CellValueText + {...props} + style={{ + fontSize: 18, + textAlign: 'center', + fontWeight: '500', + color: + props.value < 0 + ? theme.errorText + : theme.pillTextHighlighted, + }} + data-testid="transactions-balance" + /> + )} + </CellValue> </View> <View style={{ @@ -138,16 +144,19 @@ export function TransactionListWithBalances({ title="Uncleared" style={{ textAlign: 'center', fontSize: 12 }} /> - <CellValue - binding={balanceUncleared} - type="financial" - style={{ - fontSize: 12, - textAlign: 'center', - fontWeight: '500', - }} - data-testid="transactions-balance-uncleared" - /> + <CellValue binding={balanceUncleared} type="financial"> + {props => ( + <CellValueText + {...props} + style={{ + fontSize: 12, + textAlign: 'center', + fontWeight: '500', + }} + data-testid="transactions-balance-uncleared" + /> + )} + </CellValue> </View> </View> <TransactionSearchInput diff --git a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx index 94318a59cedf707edf7c74ae417a16a2507fcff6..a22cecc22a8c2a5ca8f8aab9d22978a3d7498cf6 100644 --- a/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/ReportBalanceMenuModal.tsx @@ -6,7 +6,7 @@ import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BalanceWithCarryover, - DefaultCarryoverIndicator, + CarryoverIndicator, } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/report/BalanceMenu'; import { @@ -17,6 +17,7 @@ import { } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; +import { CellValueText } from '../spreadsheet/CellValue'; type ReportBalanceMenuModalProps = ComponentPropsWithoutRef<typeof BalanceMenu>; @@ -62,27 +63,33 @@ export function ReportBalanceMenuModal({ </Text> <BalanceWithCarryover disabled - style={{ - textAlign: 'center', - ...styles.veryLargeText, - }} carryover={reportBudget.catCarryover(categoryId)} balance={reportBudget.catBalance(categoryId)} goal={reportBudget.catGoal(categoryId)} budgeted={reportBudget.catBudgeted(categoryId)} longGoal={reportBudget.catLongGoal(categoryId)} - carryoverIndicator={({ style }) => - DefaultCarryoverIndicator({ - style: { + CarryoverIndicator={({ style }) => ( + <CarryoverIndicator + style={{ width: 15, height: 15, display: 'inline-flex', position: 'relative', ...style, - }, - }) - } - /> + }} + /> + )} + > + {props => ( + <CellValueText + {...props} + style={{ + textAlign: 'center', + ...styles.veryLargeText, + }} + /> + )} + </BalanceWithCarryover> </View> <BalanceMenu categoryId={categoryId} diff --git a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx index 9c5b21a37fc340b5e0c9b9b750a8c9022b273301..1bb11b0ce88120cd181261a3bfb79c79108f3d53 100644 --- a/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx +++ b/packages/desktop-client/src/components/modals/RolloverBalanceMenuModal.tsx @@ -6,7 +6,7 @@ import { useCategory } from '../../hooks/useCategory'; import { type CSSProperties, theme, styles } from '../../style'; import { BalanceWithCarryover, - DefaultCarryoverIndicator, + CarryoverIndicator, } from '../budget/BalanceWithCarryover'; import { BalanceMenu } from '../budget/rollover/BalanceMenu'; import { @@ -17,6 +17,7 @@ import { } from '../common/Modal'; import { Text } from '../common/Text'; import { View } from '../common/View'; +import { CellValueText } from '../spreadsheet/CellValue'; type RolloverBalanceMenuModalProps = ComponentPropsWithoutRef< typeof BalanceMenu @@ -66,27 +67,33 @@ export function RolloverBalanceMenuModal({ </Text> <BalanceWithCarryover disabled - style={{ - textAlign: 'center', - ...styles.veryLargeText, - }} carryover={rolloverBudget.catCarryover(categoryId)} balance={rolloverBudget.catBalance(categoryId)} goal={rolloverBudget.catGoal(categoryId)} budgeted={rolloverBudget.catBudgeted(categoryId)} longGoal={rolloverBudget.catLongGoal(categoryId)} - carryoverIndicator={({ style }) => - DefaultCarryoverIndicator({ - style: { + CarryoverIndicator={({ style }) => ( + <CarryoverIndicator + style={{ width: 15, height: 15, display: 'inline-flex', position: 'relative', ...style, - }, - }) - } - /> + }} + /> + )} + > + {props => ( + <CellValueText + {...props} + style={{ + textAlign: 'center', + ...styles.veryLargeText, + }} + /> + )} + </BalanceWithCarryover> </View> <BalanceMenu categoryId={categoryId} diff --git a/packages/desktop-client/src/components/spreadsheet/CellValue.tsx b/packages/desktop-client/src/components/spreadsheet/CellValue.tsx index dd96739e3657085568a717e4a8e42ef6f24d4617..ab9380a31543301d0fe2ff63874a9376e7051d46 100644 --- a/packages/desktop-client/src/components/spreadsheet/CellValue.tsx +++ b/packages/desktop-client/src/components/spreadsheet/CellValue.tsx @@ -1,70 +1,93 @@ // @ts-strict-ignore -import React, { type ComponentProps, type ReactNode } from 'react'; +import React, { type ComponentPropsWithoutRef, type ReactNode } from 'react'; import { type CSSProperties, styles } from '../../style'; import { Text } from '../common/Text'; -import { ConditionalPrivacyFilter } from '../PrivacyFilter'; import { type FormatType, useFormat } from './useFormat'; import { useSheetName } from './useSheetName'; import { useSheetValue } from './useSheetValue'; -import { type Binding, type SheetNames, type SheetFields } from '.'; +import { + type Binding, + type SheetNames, + type SheetFields, + type Spreadsheets, +} from '.'; -export type CellValueProps< +type CellValueProps< SheetName extends SheetNames, FieldName extends SheetFields<SheetName>, > = { + children?: ({ + type, + name, + value, + }: { + type?: FormatType; + name: string; + value: Spreadsheets[SheetName][FieldName]; + }) => ReactNode; binding: Binding<SheetName, FieldName>; type?: FormatType; - formatter?: (value) => ReactNode; - style?: CSSProperties; - getStyle?: (value) => CSSProperties; - privacyFilter?: ComponentProps< - typeof ConditionalPrivacyFilter - >['privacyFilter']; - ['data-testid']?: string; }; export function CellValue< SheetName extends SheetNames, FieldName extends SheetFields<SheetName>, +>({ type, binding, children, ...props }: CellValueProps<SheetName, FieldName>) { + const { fullSheetName } = useSheetName(binding); + const sheetValue = useSheetValue(binding); + + return children ? ( + <>{children({ type, name: fullSheetName, value: sheetValue })}</> + ) : ( + <CellValueText + type={type} + name={fullSheetName} + value={sheetValue} + {...props} + /> + ); +} + +type CellValueTextProps< + SheetName extends SheetNames, + FieldName extends SheetFields<SheetName>, +> = Omit<ComponentPropsWithoutRef<typeof Text>, 'value'> & { + type?: FormatType; + name: string; + value: Spreadsheets[SheetName][FieldName]; + style?: CSSProperties; + formatter?: ( + value: Spreadsheets[SheetName][FieldName], + type?: FormatType, + ) => string; +}; + +export function CellValueText< + SheetName extends SheetNames, + FieldName extends SheetFields<SheetName>, >({ - binding, type, + name, + value, formatter, style, - getStyle, - privacyFilter, - 'data-testid': testId, ...props -}: CellValueProps<SheetName, FieldName>) { - const { fullSheetName } = useSheetName(binding); - const sheetValue = useSheetValue(binding); +}: CellValueTextProps<SheetName, FieldName>) { const format = useFormat(); - return ( - <ConditionalPrivacyFilter - privacyFilter={ - privacyFilter != null - ? privacyFilter - : type === 'financial' - ? true - : undefined - } + <Text + style={{ + ...(type === 'financial' && styles.tnum), + ...style, + }} + data-testid={name} + data-cellname={name} + {...props} > - <Text - style={{ - ...(type === 'financial' && styles.tnum), - ...style, - ...(getStyle && getStyle(sheetValue)), - }} - data-testid={testId || fullSheetName} - data-cellname={fullSheetName} - {...props} - > - {formatter ? formatter(sheetValue) : format(sheetValue, type)} - </Text> - </ConditionalPrivacyFilter> + {formatter ? formatter(value, type) : format(value, type)} + </Text> ); } diff --git a/upcoming-release-notes/3374.md b/upcoming-release-notes/3374.md new file mode 100644 index 0000000000000000000000000000000000000000..3f05cf826af96b2e80c991c23f64183628f44429 --- /dev/null +++ b/upcoming-release-notes/3374.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [joel-jeremy] +--- + +Mobile - Fix budget cells being triggered when pulling down to refresh on budget table.