mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 12:43:09 -05:00
[Feature] Hide category (#1060)
This commit is contained in:
@@ -229,10 +229,14 @@ class Budget extends PureComponent {
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.props.updateCategory(category);
|
||||
const cat = {
|
||||
...category,
|
||||
hidden: category.hidden ? 1 : 0,
|
||||
};
|
||||
|
||||
this.props.updateCategory(cat);
|
||||
this.setState({
|
||||
categoryGroups: updateCategory(categoryGroups, category),
|
||||
categoryGroups: updateCategory(categoryGroups, cat),
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -278,9 +282,14 @@ class Budget extends PureComponent {
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.props.updateGroup(group);
|
||||
const grp = {
|
||||
...group,
|
||||
hidden: group.hidden ? 1 : 0,
|
||||
};
|
||||
|
||||
this.props.updateGroup(grp);
|
||||
this.setState({
|
||||
categoryGroups: updateGroup(categoryGroups, group),
|
||||
categoryGroups: updateGroup(categoryGroups, grp),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,11 @@ import React, {
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import * as actions from 'loot-core/src/client/actions';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
|
||||
import useResizeObserver from '../../hooks/useResizeObserver';
|
||||
@@ -14,6 +18,7 @@ import ExpandArrow from '../../icons/v0/ExpandArrow';
|
||||
import ArrowThinLeft from '../../icons/v1/ArrowThinLeft';
|
||||
import ArrowThinRight from '../../icons/v1/ArrowThinRight';
|
||||
import CheveronDown from '../../icons/v1/CheveronDown';
|
||||
import DotsHorizontalTriple from '../../icons/v1/DotsHorizontalTriple';
|
||||
import { styles, colors } from '../../style';
|
||||
import {
|
||||
View,
|
||||
@@ -42,7 +47,7 @@ function getScrollbarWidth() {
|
||||
return Math.max(styles.scrollbarWidth - 2, 0);
|
||||
}
|
||||
|
||||
export class BudgetTable extends Component {
|
||||
class BudgetTable extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.budgetCategoriesRef = createRef();
|
||||
@@ -50,6 +55,7 @@ export class BudgetTable extends Component {
|
||||
this.state = {
|
||||
editing: null,
|
||||
draggingState: null,
|
||||
showHiddenCategories: props.prefs['budget.showHiddenCategories'] ?? false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -166,6 +172,15 @@ export class BudgetTable extends Component {
|
||||
this.setState({ editing: null });
|
||||
}
|
||||
|
||||
toggleHiddenCategories = () => {
|
||||
this.setState(prevState => ({
|
||||
showHiddenCategories: !prevState.showHiddenCategories,
|
||||
}));
|
||||
this.props.savePrefs({
|
||||
'budget.showHiddenCategories': !this.state.showHiddenCategories,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
let {
|
||||
type,
|
||||
@@ -188,7 +203,7 @@ export class BudgetTable extends Component {
|
||||
onShowNewGroup,
|
||||
onHideNewGroup,
|
||||
} = this.props;
|
||||
let { editing, draggingState } = this.state;
|
||||
let { editing, draggingState, showHiddenCategories } = this.state;
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -235,7 +250,10 @@ export class BudgetTable extends Component {
|
||||
monthBounds={monthBounds}
|
||||
type={type}
|
||||
>
|
||||
<BudgetTotals MonthComponent={dataComponents.BudgetTotalsComponent} />
|
||||
<BudgetTotals
|
||||
MonthComponent={dataComponents.BudgetTotalsComponent}
|
||||
toggleHiddenCategories={this.toggleHiddenCategories}
|
||||
/>
|
||||
<IntersectionBoundary.Provider value={this.budgetCategoriesRef}>
|
||||
<View
|
||||
style={{
|
||||
@@ -256,6 +274,7 @@ export class BudgetTable extends Component {
|
||||
innerRef={el => (this.budgetDataNode = el)}
|
||||
>
|
||||
<BudgetCategories
|
||||
showHiddenCategories={showHiddenCategories}
|
||||
categoryGroups={categoryGroups}
|
||||
newCategoryForGroup={newCategoryForGroup}
|
||||
isAddingGroup={isAddingGroup}
|
||||
@@ -287,6 +306,17 @@ export class BudgetTable extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const connected = connect(
|
||||
state => ({
|
||||
prefs: state.prefs.local,
|
||||
}),
|
||||
dispatch => bindActionCreators(actions, dispatch),
|
||||
null,
|
||||
{ forwardRef: true },
|
||||
)(BudgetTable);
|
||||
|
||||
export { connected as BudgetTable };
|
||||
|
||||
export function SidebarCategory({
|
||||
innerRef,
|
||||
category,
|
||||
@@ -313,6 +343,7 @@ export function SidebarCategory({
|
||||
alignItems: 'center',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
opacity: category.hidden ? 0.33 : undefined,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -352,12 +383,18 @@ export function SidebarCategory({
|
||||
onMenuSelect={type => {
|
||||
if (type === 'rename') {
|
||||
onEditName(category.id);
|
||||
} else {
|
||||
} else if (type === 'delete') {
|
||||
onDelete(category.id);
|
||||
} else if (type === 'toggleVisibility') {
|
||||
onSave({ ...category, hidden: !category.hidden });
|
||||
}
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
name: 'toggleVisibility',
|
||||
text: category.hidden ? 'Show' : 'Hide',
|
||||
},
|
||||
{ name: 'rename', text: 'Rename' },
|
||||
{ name: 'delete', text: 'Delete' },
|
||||
]}
|
||||
@@ -513,13 +550,19 @@ export function SidebarGroup({
|
||||
onEdit(group.id);
|
||||
} else if (type === 'add-category') {
|
||||
onShowNewCategory(group.id);
|
||||
} else {
|
||||
} else if (type === 'delete') {
|
||||
onDelete(group.id);
|
||||
} else if (type === 'toggleVisibility') {
|
||||
onSave({ ...group, hidden: !group.hidden });
|
||||
}
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
items={[
|
||||
{ name: 'add-category', text: 'Add category' },
|
||||
{
|
||||
name: 'toggleVisibility',
|
||||
text: group.hidden ? 'Show' : 'Hide',
|
||||
},
|
||||
{ name: 'rename', text: 'Rename' },
|
||||
onDelete && { name: 'delete', text: 'Delete' },
|
||||
]}
|
||||
@@ -612,7 +655,11 @@ function RenderMonths({ component: Component, editingIndex, args, style }) {
|
||||
});
|
||||
}
|
||||
|
||||
const BudgetTotals = memo(function BudgetTotals({ MonthComponent }) {
|
||||
const BudgetTotals = memo(function BudgetTotals({
|
||||
MonthComponent,
|
||||
toggleHiddenCategories,
|
||||
}) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
return (
|
||||
<View
|
||||
data-testid="budget-totals"
|
||||
@@ -632,10 +679,54 @@ const BudgetTotals = memo(function BudgetTotals({ MonthComponent }) {
|
||||
width: 200,
|
||||
color: colors.n4,
|
||||
justifyContent: 'center',
|
||||
paddingLeft: 18,
|
||||
paddingLeft: 15,
|
||||
paddingRight: 5,
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
}}
|
||||
>
|
||||
Category
|
||||
<View style={{ flexGrow: '1' }}>Category</View>
|
||||
<Button
|
||||
bare
|
||||
onClick={() => {
|
||||
setMenuOpen(true);
|
||||
}}
|
||||
style={{ color: 'currentColor', padding: 3 }}
|
||||
>
|
||||
<DotsHorizontalTriple
|
||||
width={15}
|
||||
height={15}
|
||||
style={{ color: colors.n5 }}
|
||||
/>
|
||||
{menuOpen && (
|
||||
<Tooltip
|
||||
position="bottom-right"
|
||||
width={200}
|
||||
style={{ padding: 0 }}
|
||||
onClose={() => {
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
onMenuSelect={type => {
|
||||
if (type === 'toggleVisibility') {
|
||||
toggleHiddenCategories();
|
||||
}
|
||||
setMenuOpen(false);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
name: 'toggleVisibility',
|
||||
text: 'Toggle hidden categories',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Button>
|
||||
</View>
|
||||
<RenderMonths component={MonthComponent} />
|
||||
</View>
|
||||
@@ -688,7 +779,10 @@ function ExpenseGroup({
|
||||
<Row
|
||||
collapsed={true}
|
||||
backgroundColor={colors.n11}
|
||||
style={{ fontWeight: 600 }}
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
opacity: group.hidden ? 0.33 : undefined,
|
||||
}}
|
||||
>
|
||||
{dragState && !dragState.preview && dragState.type === 'group' && (
|
||||
<View
|
||||
@@ -774,7 +868,14 @@ function ExpenseCategory({
|
||||
});
|
||||
|
||||
return (
|
||||
<Row innerRef={dropRef} collapsed={true} backgroundColor="transparent">
|
||||
<Row
|
||||
innerRef={dropRef}
|
||||
collapsed={true}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
opacity: cat.hidden ? 0.5 : undefined,
|
||||
}}
|
||||
>
|
||||
<DropHighlight pos={dropPos} offset={{ top: 1 }} />
|
||||
|
||||
<View
|
||||
@@ -917,6 +1018,7 @@ const BudgetCategories = memo(
|
||||
({
|
||||
categoryGroups,
|
||||
newCategoryForGroup,
|
||||
showHiddenCategories,
|
||||
isAddingGroup,
|
||||
editingCell,
|
||||
collapsed,
|
||||
@@ -943,7 +1045,15 @@ const BudgetCategories = memo(
|
||||
let items = Array.prototype.concat.apply(
|
||||
[],
|
||||
expenseGroups.map(group => {
|
||||
let items = [{ type: 'expense-group', value: group }];
|
||||
if (group.hidden && !showHiddenCategories) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const groupCategories = group.categories.filter(
|
||||
cat => showHiddenCategories || !cat.hidden,
|
||||
);
|
||||
|
||||
let items = [{ type: 'expense-group', value: { ...group } }];
|
||||
|
||||
if (newCategoryForGroup === group.id) {
|
||||
items.push({ type: 'new-category' });
|
||||
@@ -951,7 +1061,7 @@ const BudgetCategories = memo(
|
||||
|
||||
return [
|
||||
...items,
|
||||
...(collapsed.includes(group.id) ? [] : group.categories).map(
|
||||
...(collapsed.includes(group.id) ? [] : groupCategories).map(
|
||||
cat => ({
|
||||
type: 'expense-category',
|
||||
value: cat,
|
||||
@@ -973,7 +1083,9 @@ const BudgetCategories = memo(
|
||||
newCategoryForGroup === incomeGroup.id && { type: 'new-category' },
|
||||
...(collapsed.includes(incomeGroup.id)
|
||||
? []
|
||||
: incomeGroup.categories
|
||||
: incomeGroup.categories.filter(
|
||||
cat => showHiddenCategories || !cat.hidden,
|
||||
)
|
||||
).map(cat => ({
|
||||
type: 'income-category',
|
||||
value: cat,
|
||||
@@ -983,7 +1095,13 @@ const BudgetCategories = memo(
|
||||
}
|
||||
|
||||
return items;
|
||||
}, [categoryGroups, collapsed, newCategoryForGroup, isAddingGroup]);
|
||||
}, [
|
||||
categoryGroups,
|
||||
collapsed,
|
||||
newCategoryForGroup,
|
||||
isAddingGroup,
|
||||
showHiddenCategories,
|
||||
]);
|
||||
|
||||
let [dragState, setDragState] = useState(null);
|
||||
let [savedCollapsed, setSavedCollapsed] = useState(null);
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE categories ADD COLUMN hidden BOOLEAN NOT NULL DEFAULT 0;
|
||||
ALTER TABLE category_groups ADD COLUMN hidden BOOLEAN NOT NULL DEFAULT 0;
|
||||
|
||||
COMMIT;
|
||||
@@ -72,6 +72,7 @@ export const schema = {
|
||||
id: f('id'),
|
||||
name: f('string'),
|
||||
is_income: f('boolean'),
|
||||
hidden: f('boolean'),
|
||||
group: f('id', { ref: 'category_groups' }),
|
||||
sort_order: f('float'),
|
||||
tombstone: f('boolean'),
|
||||
@@ -80,6 +81,7 @@ export const schema = {
|
||||
id: f('id'),
|
||||
name: f('string'),
|
||||
is_income: f('boolean'),
|
||||
hidden: f('boolean'),
|
||||
sort_order: f('float'),
|
||||
tombstone: f('boolean'),
|
||||
},
|
||||
|
||||
6
upcoming-release-notes/1060.md
Normal file
6
upcoming-release-notes/1060.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Features
|
||||
authors: [Miodec]
|
||||
---
|
||||
|
||||
Added the ability to hide category groups while keeping them in the same category group.
|
||||
Reference in New Issue
Block a user