Ensure Budget Views keep independent ordering

This commit is contained in:
Alex
2025-11-12 11:48:46 -05:00
parent 2036c2c7fb
commit dba741ba26
3 changed files with 119 additions and 4 deletions

View File

@@ -3,14 +3,16 @@ import { Trans, useTranslation } from 'react-i18next';
import { Button } from '@actual-app/components/button';
import { SvgDelete } from '@actual-app/components/icons/v0';
import { SvgAdd, SvgEditPencil, SvgPencilWrite } from '@actual-app/components/icons/v1';
import {
SvgAdd,
SvgEditPencil,
SvgPencilWrite,
} from '@actual-app/components/icons/v1';
import { SpaceBetween } from '@actual-app/components/space-between';
import { Text } from '@actual-app/components/text';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { type CategoryEntity } from 'loot-core/types/models';
import {
useDraggable,
useDroppable,
@@ -19,6 +21,7 @@ import {
type OnDropCallback,
type DragState,
} from '@desktop-client/components/sort';
import { useBudgetViews } from '@desktop-client/hooks/useBudgetViews';
import { useCategories } from '@desktop-client/hooks/useCategories';
import { useDragRef } from '@desktop-client/hooks/useDragRef';
import { useSyncedPrefJson } from '@desktop-client/hooks/useSyncedPrefJson';
@@ -123,7 +126,8 @@ function BudgetViewItem({
export function BudgetViews() {
const { t } = useTranslation();
const dispatch = useDispatch();
const { list: categories, grouped: categoryGroups = [] } = useCategories();
const { list: categories } = useCategories();
const { setViewCategoryOrder, setViewGroupOrder } = useBudgetViews();
const [budgetViewMap = {}, setBudgetViewMapPref] = useSyncedPrefJson<
'budget.budgetViewMap',
Record<string, string[]>
@@ -207,6 +211,8 @@ export function BudgetViews() {
if (Array.isArray(customViews)) {
setCustomViews(customViews.filter((v: BudgetView) => v.id !== viewId));
}
setViewCategoryOrder(viewId, []);
setViewGroupOrder(viewId, []);
},
[
budgetViewMap,
@@ -214,6 +220,8 @@ export function BudgetViews() {
views,
setBudgetViewMapPref,
setCustomViews,
setViewCategoryOrder,
setViewGroupOrder,
t,
],
);

View File

@@ -95,6 +95,9 @@ function BudgetInner(props: BudgetInnerProps) {
string[] | null
>(null);
const {
views = [],
viewCategoryOrder = {},
viewGroupOrder = {},
setViewCategoryOrder,
setViewGroupOrder,
viewMap = {},
@@ -128,6 +131,76 @@ function BudgetInner(props: BudgetInnerProps) {
});
}, [props.accountId]);
useEffect(() => {
if (!categoryGroups || categoryGroups.length === 0) {
return;
}
const validGroupIds = new Set(categoryGroups.map(group => group.id));
Object.entries(viewGroupOrder).forEach(([viewId, order]) => {
if (!Array.isArray(order) || order.length === 0) {
return;
}
const filtered = order.filter(groupId => validGroupIds.has(groupId));
if (filtered.length !== order.length) {
setViewGroupOrder(viewId, filtered);
}
});
}, [categoryGroups, viewGroupOrder, setViewGroupOrder]);
useEffect(() => {
if (!categoryGroups || categoryGroups.length === 0) {
return;
}
const globalCategoryOrder = categoryGroups.flatMap(group =>
(group.categories || []).map(category => category.id),
);
const getCategoriesForView = (viewId: string) =>
globalCategoryOrder.filter(catId =>
Array.isArray(viewMap[catId]) ? viewMap[catId].includes(viewId) : false,
);
const getGroupsForView = (viewId: string) =>
categoryGroups
.filter(group =>
(group.categories || []).some(category =>
Array.isArray(viewMap[category.id])
? viewMap[category.id].includes(viewId)
: false,
),
)
.map(group => group.id);
views.forEach(view => {
if (!viewCategoryOrder[view.id]) {
const categoryIds = getCategoriesForView(view.id);
if (categoryIds.length > 0) {
setViewCategoryOrder(view.id, categoryIds);
}
}
if (!viewGroupOrder[view.id]) {
const groupIds = getGroupsForView(view.id);
if (groupIds.length > 0) {
setViewGroupOrder(view.id, groupIds);
}
}
});
}, [
categoryGroups,
views,
viewMap,
viewCategoryOrder,
viewGroupOrder,
setViewCategoryOrder,
setViewGroupOrder,
]);
const onMonthSelect = async (month, numDisplayed) => {
setStartMonthPref(month);

View File

@@ -136,6 +136,23 @@ export function useBudgetViews(): {
// Set category order for a specific view
const setViewCategoryOrder = (viewId: string, categoryIds: string[]) => {
if (!categoryIds || categoryIds.length === 0) {
if (viewCategoryOrder[viewId]) {
const { [viewId]: _, ...rest } = viewCategoryOrder;
setViewCategoryOrderPref(rest);
}
return;
}
const existingOrder = viewCategoryOrder[viewId];
if (
existingOrder &&
existingOrder.length === categoryIds.length &&
existingOrder.every((id, index) => id === categoryIds[index])
) {
return;
}
setViewCategoryOrderPref({
...viewCategoryOrder,
[viewId]: categoryIds,
@@ -143,6 +160,23 @@ export function useBudgetViews(): {
};
const setViewGroupOrder = (viewId: string, groupIds: string[]) => {
if (!groupIds || groupIds.length === 0) {
if (viewGroupOrder[viewId]) {
const { [viewId]: _, ...rest } = viewGroupOrder;
setViewGroupOrderPref(rest);
}
return;
}
const existingOrder = viewGroupOrder[viewId];
if (
existingOrder &&
existingOrder.length === groupIds.length &&
existingOrder.every((id, index) => id === groupIds[index])
) {
return;
}
setViewGroupOrderPref({
...viewGroupOrder,
[viewId]: groupIds,