Skip to content

Commit

Permalink
Add basic UI for budget automations in the budget page
Browse files Browse the repository at this point in the history
  • Loading branch information
jfdoming committed Feb 18, 2025
1 parent 4f0b07d commit 4776221
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 8 deletions.
62 changes: 54 additions & 8 deletions packages/desktop-client/src/components/budget/BudgetCategories.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
import React, { memo, useState, useMemo } from 'react';

import { styles } from '@actual-app/components/styles';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';

import { useLocalPref } from '../../hooks/useLocalPref';
import { theme, styles } from '../../style';
import { View } from '../common/View';
import { DropHighlightPosContext } from '../sort';
import { Row } from '../table';

import { BudgetGoalRow } from './BudgetGoalRow';
import { ExpenseCategory } from './ExpenseCategory';
import { ExpenseGroup } from './ExpenseGroup';
import { useBudgetAutomationCategories } from './goals/useBudgetAutomationCategories';
import { IncomeCategory } from './IncomeCategory';
import { IncomeGroup } from './IncomeGroup';
import { IncomeHeader } from './IncomeHeader';
import { SidebarCategory } from './SidebarCategory';
import { SidebarGroup } from './SidebarGroup';
import { separateGroups } from './util';

// TODO(jfdoming): replace with real data source later
const MOCK_GOALS = [
{
id: 'goal1',
type: 'simple',
monthly: 1000,
},
{
id: 'goal2',
type: 'simple',
monthly: 500,
},
];

export const BudgetCategories = memo(
({
categoryGroups,
editingCell,
dataComponents,
schedules,
onBudgetAction,
onShowActivity,
onEditName,
Expand All @@ -31,6 +50,7 @@ export const BudgetCategories = memo(
onApplyBudgetTemplatesInGroup,
onReorderCategory,
onReorderGroup,
showGoals,
}) => {
const [collapsedGroupIds = [], setCollapsedGroupIdsPref] =
useLocalPref('budget.collapsed');
Expand All @@ -41,6 +61,7 @@ export const BudgetCategories = memo(

const [isAddingGroup, setIsAddingGroup] = useState(false);
const [newCategoryForGroup, setNewCategoryForGroup] = useState(null);
const goalCategories = useBudgetAutomationCategories();
const items = useMemo(() => {
const [expenseGroups, incomeGroup] = separateGroups(categoryGroups);

Expand All @@ -65,12 +86,19 @@ export const BudgetCategories = memo(
...items,
...(collapsedGroupIds.includes(group.id)
? []
: groupCategories
).map(cat => ({
type: 'expense-category',
value: cat,
group,
})),
: groupCategories.flatMap(cat => [
{
type: 'expense-category',
value: cat,
group,
},
...(showGoals
? MOCK_GOALS.map(goal => ({
type: 'goal',
value: { ...goal, id: `${cat.id}-${goal.id}` },
}))
: []),
])),
];
}),
);
Expand Down Expand Up @@ -105,6 +133,7 @@ export const BudgetCategories = memo(
newCategoryForGroup,
isAddingGroup,
showHiddenCategories,
showGoals,
]);

const [dragState, setDragState] = useState(null);
Expand Down Expand Up @@ -266,6 +295,7 @@ export const BudgetCategories = memo(
onReorder={onReorderCategory}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
goalsShown={showGoals}
/>
);
break;
Expand Down Expand Up @@ -313,6 +343,22 @@ export const BudgetCategories = memo(
onReorder={onReorderCategory}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
goalsShown={showGoals}
/>
);
break;
case 'goal':
content = (
<BudgetGoalRow
template={item.value}
categories={goalCategories}
schedules={schedules}
onSaveTemplate={() => {
// Handle template editing
}}
onDeleteTemplate={() => {
// Handle template deletion
}}
/>
);
break;
Expand Down
52 changes: 52 additions & 0 deletions packages/desktop-client/src/components/budget/BudgetGoalRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';

import { type Template } from 'loot-core/server/budget/types/templates';
import {
type ScheduleEntity,
type CategoryGroupEntity,
} from 'loot-core/types/models';

import { BudgetAutomation } from './goals/BudgetAutomation';

type GoalRowProps = {
template: Template;
categories: CategoryGroupEntity[];
schedules: readonly ScheduleEntity[];
onSaveTemplate: () => void;
onDeleteTemplate: () => void;
};

export function BudgetGoalRow({
template,
categories,
schedules,
onSaveTemplate,
onDeleteTemplate,
}: GoalRowProps) {
return (
<View
style={{
flexDirection: 'column',
backgroundColor: theme.tableBackground,
borderBottom: `1px solid ${theme.tableBorder}`,
minHeight: 31,
}}
>
<BudgetAutomation
template={template}
categories={categories}
schedules={schedules}
onSave={onSaveTemplate}
onDelete={onDeleteTemplate}
inline={true}
readOnlyStyle={{
minHeight: 31,
padding: '4px 8px',
paddingLeft: 30,
flex: 1,
}}
/>
</View>
);
}
14 changes: 14 additions & 0 deletions packages/desktop-client/src/components/budget/BudgetTable.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, {
type ComponentPropsWithoutRef,
type KeyboardEvent,
useMemo,
useState,
} from 'react';

import { useSchedules } from 'loot-core/client/data-hooks/schedules';
import { q } from 'loot-core/shared/query';
import {
type CategoryEntity,
type CategoryGroupEntity,
Expand Down Expand Up @@ -78,6 +81,9 @@ export function BudgetTable(props: BudgetTableProps) {
} = props;

const { grouped: categoryGroups = [] } = useCategories();

const scheduleQuery = useMemo(() => q('schedules').select('*'), []);
const { schedules } = useSchedules({ query: scheduleQuery });
const [collapsedGroupIds = [], setCollapsedGroupIdsPref] =
useLocalPref('budget.collapsed');
const [showHiddenCategories, setShowHiddenCategoriesPef] = useLocalPref(
Expand All @@ -86,6 +92,7 @@ export function BudgetTable(props: BudgetTableProps) {
const [editing, setEditing] = useState<{ id: string; cell: string } | null>(
null,
);
const [showGoals, setShowGoals] = useState(false);

const onEditMonth = (id: string, month: string) => {
setEditing(id ? { id, cell: month } : null);
Expand Down Expand Up @@ -221,6 +228,10 @@ export function BudgetTable(props: BudgetTableProps) {
onCollapse(categoryGroups.map(g => g.id));
};

const toggleGoals = () => {
setShowGoals(!showGoals);
};

return (
<View
data-testid="budget-table"
Expand Down Expand Up @@ -269,6 +280,7 @@ export function BudgetTable(props: BudgetTableProps) {
toggleHiddenCategories={toggleHiddenCategories}
expandAllCategories={expandAllCategories}
collapseAllCategories={collapseAllCategories}
toggleGoals={toggleGoals}
/>
<View
style={{
Expand Down Expand Up @@ -301,6 +313,8 @@ export function BudgetTable(props: BudgetTableProps) {
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
showGoals={showGoals}
schedules={schedules}
/>
</View>
</View>
Expand Down
15 changes: 15 additions & 0 deletions packages/desktop-client/src/components/budget/BudgetTotals.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { type ComponentProps, memo, useRef, useState } from 'react';
import { useTranslation, Trans } from 'react-i18next';

import { useFeatureFlag } from '../../hooks/useFeatureFlag';
import { SvgDotsHorizontalTriple } from '../../icons/v1';
import { theme, styles } from '../../style';
import { Button } from '../common/Button2';
Expand All @@ -16,17 +17,21 @@ type BudgetTotalsProps = {
toggleHiddenCategories: () => void;
expandAllCategories: () => void;
collapseAllCategories: () => void;
toggleGoals: () => void;
};

export const BudgetTotals = memo(function BudgetTotals({
MonthComponent,
toggleHiddenCategories,
expandAllCategories,
collapseAllCategories,
toggleGoals,
}: BudgetTotalsProps) {
const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
const goalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
const goalTemplatesUIEnabled = useFeatureFlag('goalTemplatesUIEnabled');

return (
<View
Expand Down Expand Up @@ -87,6 +92,8 @@ export const BudgetTotals = memo(function BudgetTotals({
expandAllCategories();
} else if (type === 'collapseAllCategories') {
collapseAllCategories();
} else if (type === 'toggle-goals') {
toggleGoals();
}
setMenuOpen(false);
}}
Expand All @@ -103,6 +110,14 @@ export const BudgetTotals = memo(function BudgetTotals({
name: 'collapseAllCategories',
text: t('Collapse all'),
},
...(goalTemplatesEnabled && goalTemplatesUIEnabled
? [
{
name: 'toggle-goals',
text: t('Toggle budget automations'),
},
]
: []),
]}
/>
</Popover>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type ExpenseCategoryProps = {
onBudgetAction: (month: number, action: string, arg: unknown) => void;
onShowActivity: (id: string, month: string) => void;
onReorder: OnDropCallback;
goalsShown: boolean;
};

export function ExpenseCategory({
Expand All @@ -51,6 +52,7 @@ export function ExpenseCategory({
onShowActivity,
onDragChange,
onReorder,
goalsShown,
}: ExpenseCategoryProps) {
let dragging = dragState && dragState.item === cat;

Expand Down Expand Up @@ -97,6 +99,7 @@ export function ExpenseCategory({
onEditName={onEditName}
onSave={onSave}
onDelete={onDelete}
goalsShown={goalsShown}
/>

<RenderMonths
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type IncomeCategoryProps = {
onBudgetAction: (month: string, action: string, arg: unknown) => void;
onReorder: OnDropCallback;
onShowActivity: (id: string, month: string) => void;
goalsShown: boolean;
};

export function IncomeCategory({
Expand All @@ -43,6 +44,7 @@ export function IncomeCategory({
onBudgetAction,
onReorder,
onShowActivity,
goalsShown,
}: IncomeCategoryProps) {
const { dragRef } = useDraggable({
type: 'income-category',
Expand Down Expand Up @@ -79,6 +81,7 @@ export function IncomeCategory({
onEditName={onEditName}
onSave={onSave}
onDelete={onDelete}
goalsShown={goalsShown}
/>
<RenderMonths
component={MonthComponent}
Expand Down

0 comments on commit 4776221

Please sign in to comment.