From 65273127f5cf9044e6cc6e07ecfd8b6a6c8c2b75 Mon Sep 17 00:00:00 2001 From: Joel Jeremy Marquez Date: Sat, 18 Nov 2023 20:22:05 -0800 Subject: [PATCH] Mobile report budget (#1880) * Mobile report budget * Release notes * Update bindings * Cleanup * Mobile report budget summary + reuse desktop components --- .../desktop-client/src/components/Modals.tsx | 27 +- .../accounts/MobileAccountDetails.js | 1 - .../src/components/budget/BudgetSummaries.tsx | 4 +- .../src/components/budget/MobileBudget.js | 65 ++- .../components/budget/MobileBudgetTable.js | 241 +++++++-- .../src/components/budget/index.tsx | 73 +-- .../budget/report/BalanceTooltip.tsx | 59 +++ .../budget/report/BudgetSummary.tsx | 489 ------------------ .../{components.tsx => ReportComponents.tsx} | 47 +- .../report/budgetsummary/BudgetSummary.tsx | 215 ++++++++ .../report/budgetsummary/BudgetTotal.tsx | 58 +++ .../report/budgetsummary/ExpenseProgress.tsx | 41 ++ .../report/budgetsummary/ExpenseTotal.tsx | 23 + .../report/budgetsummary/IncomeProgress.tsx | 38 ++ .../report/budgetsummary/IncomeTotal.tsx | 23 + .../report/budgetsummary/PieProgress.tsx | 35 ++ .../budget/report/budgetsummary/Saved.tsx | 96 ++++ .../budget/report/budgetsummary/fraction.ts | 10 + .../budget/rollover/BudgetSummary.tsx | 452 ---------------- .../budget/rollover/HoldTooltip.tsx | 14 +- ...-components.tsx => RolloverComponents.tsx} | 4 +- .../budget/rollover/TransferTooltip.tsx | 13 +- .../rollover/budgetsummary/BudgetSummary.tsx | 223 ++++++++ .../rollover/budgetsummary/ToBudget.tsx | 159 ++++++ .../rollover/budgetsummary/TotalsList.tsx | 114 ++++ .../src/components/budget/util.ts | 59 +++ .../src/components/common/HoverTarget.tsx | 8 +- .../src/components/modals/BudgetSummary.tsx | 105 ---- .../components/modals/ReportBudgetSummary.tsx | 48 ++ .../modals/RolloverBudgetSummary.tsx | 60 +++ .../components/modals/SwitchBudgetType.tsx | 59 +++ .../spreadsheet/NamespaceContext.ts | 2 +- .../src/client/state-types/modals.d.ts | 1 + upcoming-release-notes/1880.md | 6 + 34 files changed, 1634 insertions(+), 1238 deletions(-) create mode 100644 packages/desktop-client/src/components/budget/report/BalanceTooltip.tsx delete mode 100644 packages/desktop-client/src/components/budget/report/BudgetSummary.tsx rename packages/desktop-client/src/components/budget/report/{components.tsx => ReportComponents.tsx} (90%) create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/BudgetSummary.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/BudgetTotal.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/ExpenseProgress.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/ExpenseTotal.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/IncomeProgress.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/IncomeTotal.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/PieProgress.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/Saved.tsx create mode 100644 packages/desktop-client/src/components/budget/report/budgetsummary/fraction.ts delete mode 100644 packages/desktop-client/src/components/budget/rollover/BudgetSummary.tsx rename packages/desktop-client/src/components/budget/rollover/{rollover-components.tsx => RolloverComponents.tsx} (99%) create mode 100644 packages/desktop-client/src/components/budget/rollover/budgetsummary/BudgetSummary.tsx create mode 100644 packages/desktop-client/src/components/budget/rollover/budgetsummary/ToBudget.tsx create mode 100644 packages/desktop-client/src/components/budget/rollover/budgetsummary/TotalsList.tsx delete mode 100644 packages/desktop-client/src/components/modals/BudgetSummary.tsx create mode 100644 packages/desktop-client/src/components/modals/ReportBudgetSummary.tsx create mode 100644 packages/desktop-client/src/components/modals/RolloverBudgetSummary.tsx create mode 100644 packages/desktop-client/src/components/modals/SwitchBudgetType.tsx create mode 100644 upcoming-release-notes/1880.md diff --git a/packages/desktop-client/src/components/Modals.tsx b/packages/desktop-client/src/components/Modals.tsx index 3b7b4b2f096..e2e201169a7 100644 --- a/packages/desktop-client/src/components/Modals.tsx +++ b/packages/desktop-client/src/components/Modals.tsx @@ -9,7 +9,6 @@ import useCategories from '../hooks/useCategories'; import useSyncServerStatus from '../hooks/useSyncServerStatus'; import { type CommonModalProps } from '../types/modals'; -import BudgetSummary from './modals/BudgetSummary'; import CloseAccount from './modals/CloseAccount'; import ConfirmCategoryDelete from './modals/ConfirmCategoryDelete'; import CreateAccount from './modals/CreateAccount'; @@ -25,8 +24,11 @@ import LoadBackup from './modals/LoadBackup'; import ManageRulesModal from './modals/ManageRulesModal'; import MergeUnusedPayees from './modals/MergeUnusedPayees'; import PlaidExternalMsg from './modals/PlaidExternalMsg'; +import ReportBudgetSummary from './modals/ReportBudgetSummary'; +import RolloverBudgetSummary from './modals/RolloverBudgetSummary'; import SelectLinkedAccounts from './modals/SelectLinkedAccounts'; import SingleInput from './modals/SingleInput'; +import SwitchBudgetType from './modals/SwitchBudgetType'; import DiscoverSchedules from './schedules/DiscoverSchedules'; import ScheduleDetails from './schedules/EditSchedule'; import ScheduleLink from './schedules/LinkSchedule'; @@ -247,9 +249,19 @@ export default function Modals() { /> ); - case 'budget-summary': + case 'rollover-budget-summary': return ( - + ); + + case 'report-budget-summary': + return ( + ); + case 'switch-budget-type': + return ( + + ); + default: console.error('Unknown modal:', name); return null; diff --git a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js index ff66dee7832..0f91f4fd0d2 100644 --- a/packages/desktop-client/src/components/accounts/MobileAccountDetails.js +++ b/packages/desktop-client/src/components/accounts/MobileAccountDetails.js @@ -111,7 +111,6 @@ export default function AccountDetails({ { if ( @@ -78,27 +85,19 @@ class Budget extends Component { this.cleanup?.(); } - prewarmMonth = async (month, type = null) => { - type = type || this.props.budgetType; - - let method = - type === 'report' ? 'report-budget-month' : 'rollover-budget-month'; - - let values = await send(method, { month }); - - for (let value of values) { - this.props.spreadsheet.prewarmCache(value.name, value); - } - - if (!this.state.initialized) { - this.setState({ initialized: true }); + onShowBudgetSummary = () => { + if (this.props.budgetType === 'report') { + this.props.pushModal('report-budget-summary', { + month: this.state.currentMonth, + }); + } else { + this.props.pushModal('rollover-budget-summary', { + month: this.state.currentMonth, + onBudgetAction: this.props.applyBudgetAction, + }); } }; - onShowBudgetDetails = () => { - this.props.pushModal('budget-summary', { month: this.state.currentMonth }); - }; - onBudgetAction = type => { const { currentMonth } = this.state; this.props.applyBudgetAction(currentMonth, type, this.state.bounds); @@ -267,15 +266,17 @@ class Budget extends Component { }; onPrevMonth = async () => { + let { spreadsheet, budgetType } = this.props; let month = monthUtils.subMonths(this.state.currentMonth, 1); - await this.prewarmMonth(month); - this.setState({ currentMonth: month }); + await prewarmMonth(budgetType, spreadsheet, month); + this.setState({ currentMonth: month, initialized: true }); }; onNextMonth = async () => { + let { spreadsheet, budgetType } = this.props; let month = monthUtils.addMonths(this.state.currentMonth, 1); - await this.prewarmMonth(month); - this.setState({ currentMonth: month }); + await prewarmMonth(budgetType, spreadsheet, month); + this.setState({ currentMonth: month, initialized: true }); }; onOpenActionSheet = () => { @@ -321,6 +322,19 @@ class Budget extends Component { ); }; + onSwitchBudgetType = async () => { + const { spreadsheet, budgetType, loadPrefs } = this.props; + const { bounds, currentMonth } = this.state; + + this.setState({ initialized: false }); + + await switchBudgetType(budgetType, spreadsheet, bounds, currentMonth, () => + loadPrefs(), + ); + + this.setState({ initialized: true }); + }; + render() { const { currentMonth, bounds, editMode, initialized } = this.state; const { @@ -331,6 +345,7 @@ class Budget extends Component { budgetType, navigation, applyBudgetAction, + pushModal, } = this.props; let numberFormat = prefs.numberFormat || 'comma-dot'; let hideFraction = prefs.hideFraction || false; @@ -369,7 +384,7 @@ class Budget extends Component { // } editMode={editMode} onEditMode={flag => this.setState({ editMode: flag })} - onShowBudgetDetails={this.onShowBudgetDetails} + onShowBudgetSummary={this.onShowBudgetSummary} onPrevMonth={this.onPrevMonth} onNextMonth={this.onNextMonth} onSaveGroup={this.onSaveGroup} @@ -383,7 +398,9 @@ class Budget extends Component { onOpenActionSheet={() => {}} //this.onOpenActionSheet} onBudgetAction={applyBudgetAction} onRefresh={onRefresh} + onSwitchBudgetType={this.onSwitchBudgetType} savePrefs={savePrefs} + pushModal={pushModal} /> )} diff --git a/packages/desktop-client/src/components/budget/MobileBudgetTable.js b/packages/desktop-client/src/components/budget/MobileBudgetTable.js index 36c18087a43..b5a684c6b81 100644 --- a/packages/desktop-client/src/components/budget/MobileBudgetTable.js +++ b/packages/desktop-client/src/components/budget/MobileBudgetTable.js @@ -6,6 +6,7 @@ import memoizeOne from 'memoize-one'; import { rolloverBudget, reportBudget } from 'loot-core/src/client/queries'; import * as monthUtils from 'loot-core/src/shared/months'; +import useFeatureFlag from '../../hooks/useFeatureFlag'; import ArrowThinLeft from '../../icons/v1/ArrowThinLeft'; import ArrowThinRight from '../../icons/v1/ArrowThinRight'; import DotsHorizontalTriple from '../../icons/v1/DotsHorizontalTriple'; @@ -35,7 +36,8 @@ import { AmountInput } from '../util/AmountInput'; // import { DragDrop, Draggable, Droppable, DragDropHighlight } from './dragdrop'; import BalanceWithCarryover from './BalanceWithCarryover'; import { ListItem, ROW_HEIGHT } from './MobileTable'; -import BalanceTooltip from './rollover/BalanceTooltip'; +import ReportBudgetBalanceTooltip from './report/BalanceTooltip'; +import RolloverBudgetBalanceTooltip from './rollover/BalanceTooltip'; import { makeAmountGrey } from './util'; function ToBudget({ toBudget, onClick }) { @@ -68,7 +70,7 @@ function ToBudget({ toBudget, onClick }) { ); } -function Saved({ projected }) { +function Saved({ projected, onClick }) { let binding = projected ? reportBudget.totalBudgetedSaved : reportBudget.totalSaved; @@ -77,21 +79,32 @@ function Saved({ projected }) { let isNegative = saved < 0; return ( - {projected ? ( + ); } @@ -219,7 +232,13 @@ function ExpenseCategoryPreview({ name, pending, style }) { } const ExpenseCategory = memo(function ExpenseCategory({ + type, category, + goal, + budgeted, + spent, + balance, + carryover, index, // gestures, blank, @@ -244,9 +263,6 @@ const ExpenseCategory = memo(function ExpenseCategory({ let [categoryName, setCategoryName] = useState(category.name); let [isHidden, setIsHidden] = useState(category.hidden); - let budgeted = rolloverBudget.catBudgeted(category.id); - let spent = rolloverBudget.catSumAmount(category.id); - let tooltip = useTooltip(); let balanceTooltip = useTooltip(); @@ -295,6 +311,14 @@ const ExpenseCategory = memo(function ExpenseCategory({ let listItemRef = useRef(); let inputRef = useRef(); + let _onBudgetAction = (monthIndex, action, arg) => { + onBudgetAction?.( + monthUtils.getMonthFromIndex(monthUtils.getYear(month), monthIndex), + action, + arg, + ); + }; + let content = ( e.preventDefault()} > - {balanceTooltip.isOpen && ( - { - onBudgetAction?.( - monthUtils.getMonthFromIndex( - monthUtils.getYear(month), - monthIndex, - ), - action, - arg, - ); - }} - onClose={() => { - onOpenBudgetActionMenu?.(null); - }} - /> - )} + {balanceTooltip.isOpen && + (type === 'report' ? ( + { + onOpenBudgetActionMenu?.(null); + }} + /> + ) : ( + { + onOpenBudgetActionMenu?.(null); + }} + /> + ))} @@ -516,6 +543,9 @@ const ExpenseCategory = memo(function ExpenseCategory({ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ group, + budgeted, + spent, + balance, editMode, isEditing, onEdit, @@ -690,7 +720,7 @@ const ExpenseGroupTotals = memo(function ExpenseGroupTotals({ }} > - {budget && ( + {budgeted && ( - {budget && ( + {budgeted && ( - + {/* + /> */} )} @@ -1350,7 +1441,7 @@ function IncomeGroup({ ); })} @@ -1448,6 +1543,7 @@ function BudgetGroups({ return ( )} @@ -1526,11 +1626,13 @@ export function BudgetTable(props) { onEditMode, onReorderCategory, onReorderGroup, - onShowBudgetDetails, + onShowBudgetSummary, // onOpenActionSheet, onBudgetAction, onRefresh, + onSwitchBudgetType, savePrefs, + pushModal, } = props; const GROUP_EDIT_ACTION = 'group'; @@ -1627,6 +1729,8 @@ export function BudgetTable(props) { onNextMonth={onNextMonth} showHiddenCategories={showHiddenCategories} savePrefs={savePrefs} + pushModal={pushModal} + onSwitchBudgetType={onSwitchBudgetType} /> {type === 'report' ? ( - = currentMonth} /> + = currentMonth} + onClick={onShowBudgetSummary} + /> ) : ( )} @@ -1676,7 +1783,11 @@ export function BudgetTable(props) { style={{ color: theme.buttonNormalText }} />