Apply Template to All Categories in Group for Web (#3666)

* add function to apply template to multiple category and add button to group sidebar

* add function to apply template to multiple category and add button to group sidebar

* add correct month

* clean up code

* clean up code

* clean up code

* clean up code

* add notification and clean up

* add notification and clean up

* add notification and clean up

* add notification and clean up

* add notification and clean up

* add release note

* excluded hidden categories

* removed unused method from api

* adjust template to run on already budgeted categories

* fix typecheck

* add apply multiple as budget action and remove from api

* lint clean up

* fix notification and remove log

---------

Co-authored-by: dreptschar <dreptschar@gmail.com>
This commit is contained in:
Dreptschar
2024-11-04 15:22:29 +01:00
committed by GitHub
parent e078ed21ba
commit a267e3abb5
10 changed files with 93 additions and 4 deletions

View File

@@ -28,6 +28,7 @@ export const BudgetCategories = memo(
onSaveGroup,
onDeleteCategory,
onDeleteGroup,
onApplyBudgetTemplatesInGroup,
onReorderCategory,
onReorderGroup,
}) => {
@@ -245,6 +246,7 @@ export const BudgetCategories = memo(
onReorderCategory={onReorderCategory}
onToggleCollapse={onToggleCollapse}
onShowNewCategory={onShowNewCategory}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
/>
);
break;

View File

@@ -28,6 +28,7 @@ export function BudgetTable(props) {
onDeleteCategory,
onSaveGroup,
onDeleteGroup,
onApplyBudgetTemplatesInGroup,
onReorderCategory,
onReorderGroup,
onShowActivity,
@@ -235,6 +236,7 @@ export function BudgetTable(props) {
onReorderGroup={_onReorderGroup}
onBudgetAction={onBudgetAction}
onShowActivity={onShowActivity}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
/>
</View>
</View>

View File

@@ -25,6 +25,9 @@ type ExpenseGroupProps = {
onEditName?: ComponentProps<typeof SidebarGroup>['onEdit'];
onSave?: ComponentProps<typeof SidebarGroup>['onSave'];
onDelete?: ComponentProps<typeof SidebarGroup>['onDelete'];
onApplyBudgetTemplatesInGroup?: ComponentProps<
typeof SidebarGroup
>['onApplyBudgetTemplatesInGroup'];
onDragChange: OnDragChangeCallback<
ComponentProps<typeof SidebarGroup>['group']
>;
@@ -43,6 +46,7 @@ export function ExpenseGroup({
onEditName,
onSave,
onDelete,
onApplyBudgetTemplatesInGroup,
onDragChange,
onReorderGroup,
onReorderCategory,
@@ -125,6 +129,7 @@ export function ExpenseGroup({
onEdit={onEditName}
onSave={onSave}
onDelete={onDelete}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
onShowNewCategory={onShowNewCategory}
/>
<RenderMonths component={MonthComponent} args={{ group }} />

View File

@@ -33,6 +33,7 @@ type SidebarGroupProps = {
onEdit?: (id: string) => void;
onSave?: (group: object) => Promise<void>;
onDelete?: (id: string) => Promise<void>;
onApplyBudgetTemplatesInGroup?: (categories: object[]) => void;
onShowNewCategory?: (groupId: string) => void;
onHideNewGroup?: () => void;
onToggleCollapse?: (id: string) => void;
@@ -48,11 +49,13 @@ export function SidebarGroup({
onEdit,
onSave,
onDelete,
onApplyBudgetTemplatesInGroup,
onShowNewCategory,
onHideNewGroup,
onToggleCollapse,
}: SidebarGroupProps) {
const { t } = useTranslation();
const isGoalTemplatesEnabled = useFeatureFlag('goalTemplatesEnabled');
const temporary = group.id === 'new';
const [menuOpen, setMenuOpen] = useState(false);
@@ -132,6 +135,12 @@ export function SidebarGroup({
onDelete(group.id);
} else if (type === 'toggle-visibility') {
onSave({ ...group, hidden: !group.hidden });
} else if (type === 'apply-multiple-category-template') {
onApplyBudgetTemplatesInGroup?.(
group.categories
.filter(c => !c['hidden'])
.map(c => c['id']),
);
}
setMenuOpen(false);
}}
@@ -143,6 +152,14 @@ export function SidebarGroup({
text: group.hidden ? t('Show') : t('Hide'),
},
onDelete && { name: 'delete', text: t('Delete') },
...(isGoalTemplatesEnabled
? [
{
name: 'apply-multiple-category-template',
text: t('Apply budget templates'),
},
]
: []),
]}
/>
</Popover>

View File

@@ -265,6 +265,15 @@ function BudgetInner(props: BudgetInnerProps) {
}
};
const onApplyBudgetTemplatesInGroup = async categories => {
dispatch(
applyBudgetAction(startMonth, 'apply-multiple-templates', {
month: startMonth,
categories,
}),
);
};
const onBudgetAction = (month, type, args) => {
dispatch(applyBudgetAction(month, type, args));
};
@@ -366,6 +375,7 @@ function BudgetInner(props: BudgetInnerProps) {
onMonthSelect={onMonthSelect}
onDeleteCategory={onDeleteCategory}
onDeleteGroup={onDeleteGroup}
onApplyBudgetTemplatesInGroup={onApplyBudgetTemplatesInGroup}
onSaveCategory={onSaveCategory}
onSaveGroup={onSaveGroup}
onBudgetAction={onBudgetAction}

View File

@@ -102,6 +102,16 @@ export function applyBudgetAction(month, type, args) {
category: args.category,
});
break;
case 'apply-multiple-templates':
dispatch(
addNotification(
await send('budget/apply-multiple-templates', {
month,
categoryIds: args.categories,
}),
),
);
break;
case 'set-single-3-avg':
await send('budget/set-n-month-avg', {
month,

View File

@@ -29,6 +29,10 @@ app.method(
'budget/apply-goal-template',
mutator(undoable(goalActions.applyTemplate)),
);
app.method(
'budget/apply-multiple-templates',
mutator(undoable(goalActions.applyMultipleCategoryTemplates)),
);
app.method(
'budget/overwrite-goal-template',
mutator(undoable(goalActions.overwriteTemplate)),

View File

@@ -37,6 +37,23 @@ export async function overwriteTemplate({ month }) {
return ret;
}
export async function applyMultipleCategoryTemplates({ month, categoryIds }) {
const placeholders = categoryIds.map(() => '?').join(', ');
const query = `SELECT * FROM v_categories WHERE id IN (${placeholders})`;
const categories = await db.all(query, categoryIds);
await storeTemplates();
const category_templates = await getTemplates(categories, 'template');
const category_goals = await getTemplates(categories, 'goal');
const ret = await processTemplate(
month,
true,
category_templates,
categories,
);
await processGoals(category_goals, month);
return ret;
}
export async function applySingleCategoryTemplate({ month, category }) {
const categories = await db.all(`SELECT * FROM v_categories WHERE id = ?`, [
category,
@@ -48,7 +65,7 @@ export async function applySingleCategoryTemplate({ month, category }) {
month,
true,
category_templates,
categories[0],
categories,
);
await processGoals(category_goals, month, categories[0]);
return ret;
@@ -135,7 +152,19 @@ async function getTemplates(category, directive: string) {
for (let ll = 0; ll < goal_def.length; ll++) {
templates[goal_def[ll].id] = JSON.parse(goal_def[ll].goal_def);
}
if (category) {
if (Array.isArray(category)) {
const multipleCategoryTemplates = [];
for (let dd = 0; dd < category.length; dd++) {
const categoryId = category[dd].id;
if (templates[categoryId] !== undefined) {
multipleCategoryTemplates[categoryId] = templates[categoryId];
multipleCategoryTemplates[categoryId] = multipleCategoryTemplates[
categoryId
].filter(t => t.directive === directive);
}
}
return multipleCategoryTemplates;
} else if (category) {
const singleCategoryTemplate = [];
if (templates[category.id] !== undefined) {
singleCategoryTemplate[category.id] = templates[category.id].filter(
@@ -174,11 +203,10 @@ async function processTemplate(
let categories = [];
const categories_remove = [];
if (category) {
categories[0] = category;
categories = category;
} else {
categories = await getCategories();
}
//clears templated categories
for (let c = 0; c < categories.length; c++) {
const category = categories[c];

View File

@@ -79,4 +79,9 @@ export interface BudgetHandlers {
month: string;
category: string; //category id
}) => Promise<void>;
'budget/apply-multiple-templates': (arg: {
month: string;
categoryIds: string[];
}) => Promise<Notification>;
}

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [Dreptschar]
---
Adds a Button to Group Menu that allows users to apply all Budget Templates in this Group