mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-28 18:40:34 -05:00
Custom Reports - split out hidden categories from offbudget (#2302)
* Add Toggles * budget table * testing * updates * updates * fixes * updates * fix Menu * lint fixes * fix keybindings * revert budget menu changes * notes * remove default exports * fixes * disabled fix * add style option * lint fix * remove css * lint fixes * color updates * merge menu with togglemenu * host * menu fixes * fix regression * remove host * adjustments * work * fix hidden filters * merge fixes * adjustments * updates * fix uncat table values * fixes * notes * title change * Adjust showHide selector
This commit is contained in:
@@ -22,36 +22,41 @@ import { GraphButton } from './GraphButton';
|
||||
|
||||
type CategorySelectorProps = {
|
||||
categoryGroups: Array<CategoryGroupEntity>;
|
||||
categories: Array<CategoryEntity>;
|
||||
selectedCategories: CategoryListProps['items'];
|
||||
setSelectedCategories: (selectedCategories: CategoryEntity[]) => null;
|
||||
showHiddenCategories?: boolean;
|
||||
};
|
||||
|
||||
export function CategorySelector({
|
||||
categoryGroups,
|
||||
categories,
|
||||
selectedCategories,
|
||||
setSelectedCategories,
|
||||
showHiddenCategories = true,
|
||||
}: CategorySelectorProps) {
|
||||
const [uncheckedHidden, setUncheckedHidden] = useState(false);
|
||||
const filteredGroup = (categoryGroup: CategoryGroupEntity) => {
|
||||
return categoryGroup.categories.filter(f => {
|
||||
return showHiddenCategories || !f.hidden ? true : false;
|
||||
});
|
||||
};
|
||||
|
||||
const selectAll: CategoryEntity[] = [];
|
||||
categoryGroups.map(categoryGroup =>
|
||||
filteredGroup(categoryGroup).map(category => selectAll.push(category)),
|
||||
);
|
||||
|
||||
const selectedCategoryMap = useMemo(
|
||||
() => selectedCategories.map(selected => selected.id),
|
||||
[selectedCategories],
|
||||
);
|
||||
const allCategoriesSelected = categories.every(category =>
|
||||
const allCategoriesSelected = selectAll.every(category =>
|
||||
selectedCategoryMap.includes(category.id),
|
||||
);
|
||||
|
||||
const allCategoriesUnselected = !categories.some(category =>
|
||||
const allCategoriesUnselected = !selectAll.some(category =>
|
||||
selectedCategoryMap.includes(category.id),
|
||||
);
|
||||
|
||||
const selectAll: CategoryEntity[] = [];
|
||||
categoryGroups.map(categoryGroup =>
|
||||
categoryGroup.categories.map(category => selectAll.push(category)),
|
||||
);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View
|
||||
@@ -126,13 +131,14 @@ export function CategorySelector({
|
||||
>
|
||||
{categoryGroups &&
|
||||
categoryGroups.map(categoryGroup => {
|
||||
const allCategoriesInGroupSelected = categoryGroup.categories.every(
|
||||
category =>
|
||||
selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
),
|
||||
const allCategoriesInGroupSelected = filteredGroup(
|
||||
categoryGroup,
|
||||
).every(category =>
|
||||
selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
),
|
||||
);
|
||||
const noCategorySelected = categoryGroup.categories.every(
|
||||
const noCategorySelected = filteredGroup(categoryGroup).every(
|
||||
category =>
|
||||
!selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
@@ -155,7 +161,7 @@ export function CategorySelector({
|
||||
const selectedCategoriesExcludingGroupCategories =
|
||||
selectedCategories.filter(
|
||||
selectedCategory =>
|
||||
!categoryGroup.categories.some(
|
||||
!filteredGroup(categoryGroup).some(
|
||||
groupCategory =>
|
||||
groupCategory.id === selectedCategory.id,
|
||||
),
|
||||
@@ -167,7 +173,7 @@ export function CategorySelector({
|
||||
} else {
|
||||
setSelectedCategories(
|
||||
selectedCategoriesExcludingGroupCategories.concat(
|
||||
categoryGroup.categories,
|
||||
filteredGroup(categoryGroup),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -189,7 +195,7 @@ export function CategorySelector({
|
||||
paddingLeft: 10,
|
||||
}}
|
||||
>
|
||||
{categoryGroup.categories.map(category => {
|
||||
{filteredGroup(categoryGroup).map(category => {
|
||||
const isChecked = selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
);
|
||||
|
||||
@@ -16,7 +16,8 @@ export const defaultState: CustomReportEntity = {
|
||||
groupBy: 'Category',
|
||||
balanceType: 'Payment',
|
||||
showEmpty: false,
|
||||
showOffBudgetHidden: false,
|
||||
showOffBudget: false,
|
||||
showHiddenCategories: false,
|
||||
showUncategorized: false,
|
||||
graphType: 'BarGraph',
|
||||
startDate,
|
||||
@@ -77,7 +78,9 @@ const intervalOptions = [
|
||||
export type QueryDataEntity = {
|
||||
date: string;
|
||||
category: string;
|
||||
categoryHidden: boolean;
|
||||
categoryGroup: string;
|
||||
categoryGroupHidden: boolean;
|
||||
account: string;
|
||||
accountOffBudget: boolean;
|
||||
payee: string;
|
||||
@@ -85,10 +88,7 @@ export type QueryDataEntity = {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type UncategorizedEntity = Pick<
|
||||
CategoryEntity,
|
||||
'name' | 'id' | 'hidden'
|
||||
> & {
|
||||
export type UncategorizedEntity = Pick<CategoryEntity, 'name' | 'hidden'> & {
|
||||
/*
|
||||
When looking at uncategorized and hidden transactions we
|
||||
need a way to group them. To do this we give them a unique
|
||||
@@ -104,7 +104,6 @@ export type UncategorizedEntity = Pick<
|
||||
|
||||
const uncategorizedCategory: UncategorizedEntity = {
|
||||
name: 'Uncategorized',
|
||||
id: undefined,
|
||||
uncategorized_id: '1',
|
||||
hidden: false,
|
||||
is_off_budget: false,
|
||||
@@ -113,7 +112,6 @@ const uncategorizedCategory: UncategorizedEntity = {
|
||||
};
|
||||
const transferCategory: UncategorizedEntity = {
|
||||
name: 'Transfers',
|
||||
id: undefined,
|
||||
uncategorized_id: '2',
|
||||
hidden: false,
|
||||
is_off_budget: false,
|
||||
@@ -122,7 +120,6 @@ const transferCategory: UncategorizedEntity = {
|
||||
};
|
||||
const offBudgetCategory: UncategorizedEntity = {
|
||||
name: 'Off Budget',
|
||||
id: undefined,
|
||||
uncategorized_id: '3',
|
||||
hidden: false,
|
||||
is_off_budget: true,
|
||||
@@ -144,26 +141,19 @@ const uncategorizedGroup: UncategorizedGroupEntity = {
|
||||
categories: [uncategorizedCategory, transferCategory, offBudgetCategory],
|
||||
};
|
||||
|
||||
export const categoryLists = (
|
||||
showOffBudgetHidden: boolean,
|
||||
showUncategorized: boolean,
|
||||
categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] },
|
||||
) => {
|
||||
const categoryList = showUncategorized
|
||||
? [
|
||||
...categories.list.filter(f => showOffBudgetHidden || !f.hidden),
|
||||
uncategorizedCategory,
|
||||
transferCategory,
|
||||
offBudgetCategory,
|
||||
]
|
||||
: categories.list;
|
||||
const categoryGroup = showUncategorized
|
||||
? [
|
||||
...categories.grouped.filter(f => showOffBudgetHidden || !f.hidden),
|
||||
uncategorizedGroup,
|
||||
]
|
||||
: categories.grouped;
|
||||
return [categoryList, categoryGroup] as const;
|
||||
export const categoryLists = (categories: {
|
||||
list: CategoryEntity[];
|
||||
grouped: CategoryGroupEntity[];
|
||||
}) => {
|
||||
const categoryList = [
|
||||
...categories.list,
|
||||
uncategorizedCategory,
|
||||
offBudgetCategory,
|
||||
transferCategory,
|
||||
];
|
||||
|
||||
const categoryGroup = [...categories.grouped, uncategorizedGroup];
|
||||
return [categoryList, categoryGroup.filter(group => group !== null)] as const;
|
||||
};
|
||||
|
||||
export const groupBySelections = (
|
||||
|
||||
@@ -35,7 +35,8 @@ export function ReportSidebar({
|
||||
setMode,
|
||||
setIsDateStatic,
|
||||
setShowEmpty,
|
||||
setShowOffBudgetHidden,
|
||||
setShowOffBudget,
|
||||
setShowHiddenCategories,
|
||||
setShowUncategorized,
|
||||
setSelectedCategories,
|
||||
onChangeDates,
|
||||
@@ -266,10 +267,12 @@ export function ReportSidebar({
|
||||
<Menu
|
||||
onMenuSelect={type => {
|
||||
if (type === 'show-hidden-categories') {
|
||||
setShowOffBudgetHidden(
|
||||
!customReportItems.showOffBudgetHidden,
|
||||
setShowHiddenCategories(
|
||||
!customReportItems.showHiddenCategories,
|
||||
);
|
||||
} else if (type === 'show-empty-rows') {
|
||||
} else if (type === 'show-off-budget') {
|
||||
setShowOffBudget(!customReportItems.showOffBudget);
|
||||
} else if (type === 'show-empty-items') {
|
||||
setShowEmpty(!customReportItems.showEmpty);
|
||||
} else if (type === 'show-uncategorized') {
|
||||
setShowUncategorized(
|
||||
@@ -279,20 +282,26 @@ export function ReportSidebar({
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
name: 'show-empty-rows',
|
||||
text: 'Show Empty Rows',
|
||||
name: 'show-hidden-categories',
|
||||
text: 'Show hidden categories',
|
||||
tooltip: 'Show hidden categories',
|
||||
toggle: customReportItems.showHiddenCategories,
|
||||
},
|
||||
{
|
||||
name: 'show-empty-items',
|
||||
text: 'Show empty rows',
|
||||
tooltip: 'Show rows that are zero or blank',
|
||||
toggle: customReportItems.showEmpty,
|
||||
},
|
||||
{
|
||||
name: 'show-hidden-categories',
|
||||
text: 'Show Off Budget',
|
||||
tooltip: 'Show off budget accounts and hidden categories',
|
||||
toggle: customReportItems.showOffBudgetHidden,
|
||||
name: 'show-off-budget',
|
||||
text: 'Show off budget',
|
||||
tooltip: 'Show off budget accounts',
|
||||
toggle: customReportItems.showOffBudget,
|
||||
},
|
||||
{
|
||||
name: 'show-uncategorized',
|
||||
text: 'Show Uncategorized',
|
||||
text: 'Show uncategorized',
|
||||
tooltip: 'Show uncategorized transactions',
|
||||
toggle: customReportItems.showUncategorized,
|
||||
},
|
||||
@@ -440,10 +449,14 @@ export function ReportSidebar({
|
||||
}}
|
||||
>
|
||||
<CategorySelector
|
||||
categoryGroups={categories.grouped}
|
||||
categories={categories.list}
|
||||
categoryGroups={categories.grouped.filter(f => {
|
||||
return customReportItems.showHiddenCategories || !f.hidden
|
||||
? true
|
||||
: false;
|
||||
})}
|
||||
selectedCategories={customReportItems.selectedCategories}
|
||||
setSelectedCategories={setSelectedCategories}
|
||||
showHiddenCategories={customReportItems.showHiddenCategories}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -144,7 +144,6 @@ export function CategorySpending() {
|
||||
>
|
||||
<View style={{ width: 200 }}>
|
||||
<CategorySelector
|
||||
categories={categories.list}
|
||||
categoryGroups={categories.grouped.filter(
|
||||
categoryGroup => !categoryGroup.is_income,
|
||||
)}
|
||||
|
||||
@@ -69,8 +69,9 @@ export function CustomReport() {
|
||||
const [groupBy, setGroupBy] = useState(loadReport.groupBy);
|
||||
const [balanceType, setBalanceType] = useState(loadReport.balanceType);
|
||||
const [showEmpty, setShowEmpty] = useState(loadReport.showEmpty);
|
||||
const [showOffBudgetHidden, setShowOffBudgetHidden] = useState(
|
||||
loadReport.showOffBudgetHidden,
|
||||
const [showOffBudget, setShowOffBudget] = useState(loadReport.showOffBudget);
|
||||
const [showHiddenCategories, setShowHiddenCategories] = useState(
|
||||
loadReport.showHiddenCategories,
|
||||
);
|
||||
const [showUncategorized, setShowUncategorized] = useState(
|
||||
loadReport.showUncategorized,
|
||||
@@ -131,7 +132,8 @@ export function CustomReport() {
|
||||
conditions: filters,
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
balanceTypeOp,
|
||||
});
|
||||
@@ -147,7 +149,8 @@ export function CustomReport() {
|
||||
filters,
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
graphType,
|
||||
]);
|
||||
@@ -162,7 +165,8 @@ export function CustomReport() {
|
||||
conditions: filters,
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
groupBy,
|
||||
balanceTypeOp,
|
||||
@@ -183,7 +187,8 @@ export function CustomReport() {
|
||||
filters,
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
graphType,
|
||||
]);
|
||||
@@ -198,7 +203,8 @@ export function CustomReport() {
|
||||
groupBy,
|
||||
balanceType,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
graphType,
|
||||
startDate,
|
||||
@@ -259,7 +265,8 @@ export function CustomReport() {
|
||||
setMode={setMode}
|
||||
setIsDateStatic={setIsDateStatic}
|
||||
setShowEmpty={setShowEmpty}
|
||||
setShowOffBudgetHidden={setShowOffBudgetHidden}
|
||||
setShowOffBudget={setShowOffBudget}
|
||||
setShowHiddenCategories={setShowHiddenCategories}
|
||||
setShowUncategorized={setShowUncategorized}
|
||||
setSelectedCategories={setSelectedCategories}
|
||||
onChangeDates={onChangeDates}
|
||||
|
||||
@@ -27,6 +27,10 @@ export function CustomReportCard(reports) {
|
||||
groupBy,
|
||||
balanceTypeOp: 'totalDebts',
|
||||
categories,
|
||||
showEmpty: false,
|
||||
showOffBudget: false,
|
||||
showHiddenCategories: false,
|
||||
showUncategorized: false,
|
||||
});
|
||||
}, [startDate, endDate, categories]);
|
||||
const data = useReport('default', getGraphData);
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { categoryLists, groupBySelections } from '../ReportOptions';
|
||||
|
||||
import { calculateLegend } from './calculateLegend';
|
||||
import { filterEmptyRows } from './filterEmptyRows';
|
||||
import { filterHiddenItems } from './filterHiddenItems';
|
||||
import { makeQuery } from './makeQuery';
|
||||
import { recalculate } from './recalculate';
|
||||
@@ -28,14 +29,15 @@ export type createCustomSpreadsheetProps = {
|
||||
conditions: RuleConditionEntity[];
|
||||
conditionsOp: string;
|
||||
showEmpty: boolean;
|
||||
showOffBudgetHidden: boolean;
|
||||
showOffBudget: boolean;
|
||||
showHiddenCategories: boolean;
|
||||
showUncategorized: boolean;
|
||||
groupBy?: string;
|
||||
balanceTypeOp?: string;
|
||||
payees?: PayeeEntity[];
|
||||
accounts?: AccountEntity[];
|
||||
setDataCheck?: (value: boolean) => void;
|
||||
graphType: string;
|
||||
graphType?: string;
|
||||
};
|
||||
|
||||
export function createCustomSpreadsheet({
|
||||
@@ -46,7 +48,8 @@ export function createCustomSpreadsheet({
|
||||
conditions = [],
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
groupBy,
|
||||
balanceTypeOp,
|
||||
@@ -55,15 +58,10 @@ export function createCustomSpreadsheet({
|
||||
setDataCheck,
|
||||
graphType,
|
||||
}: createCustomSpreadsheetProps) {
|
||||
const [categoryList, categoryGroup] = categoryLists(
|
||||
showOffBudgetHidden,
|
||||
showUncategorized,
|
||||
categories,
|
||||
);
|
||||
const [categoryList, categoryGroup] = categoryLists(categories);
|
||||
|
||||
const categoryFilter = (categoryList || []).filter(
|
||||
const categoryFilter = (categories.list || []).filter(
|
||||
category =>
|
||||
!category.hidden &&
|
||||
selectedCategories &&
|
||||
selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
@@ -94,7 +92,6 @@ export function createCustomSpreadsheet({
|
||||
'assets',
|
||||
startDate,
|
||||
endDate,
|
||||
showOffBudgetHidden,
|
||||
selectedCategories,
|
||||
categoryFilter,
|
||||
conditionsOpKey,
|
||||
@@ -106,7 +103,6 @@ export function createCustomSpreadsheet({
|
||||
'debts',
|
||||
startDate,
|
||||
endDate,
|
||||
showOffBudgetHidden,
|
||||
selectedCategories,
|
||||
categoryFilter,
|
||||
conditionsOpKey,
|
||||
@@ -128,15 +124,31 @@ export function createCustomSpreadsheet({
|
||||
groupByList.map(item => {
|
||||
let stackAmounts = 0;
|
||||
|
||||
const monthAssets = filterHiddenItems(item, assets)
|
||||
const monthAssets = filterHiddenItems(
|
||||
item,
|
||||
assets,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
asset => asset.date === month && asset[groupByLabel] === item.id,
|
||||
asset =>
|
||||
asset.date === month && asset[groupByLabel] === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
perMonthAssets += monthAssets;
|
||||
|
||||
const monthDebts = filterHiddenItems(item, debts)
|
||||
.filter(debt => debt.date === month && debt[groupByLabel] === item.id)
|
||||
const monthDebts = filterHiddenItems(
|
||||
item,
|
||||
debts,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
debt =>
|
||||
debt.date === month && debt[groupByLabel] === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
perMonthDebts += monthDebts;
|
||||
|
||||
@@ -168,11 +180,20 @@ export function createCustomSpreadsheet({
|
||||
}, []);
|
||||
|
||||
const calcData = groupByList.map(item => {
|
||||
const calc = recalculate({ item, months, assets, debts, groupByLabel });
|
||||
const calc = recalculate({
|
||||
item,
|
||||
months,
|
||||
assets,
|
||||
debts,
|
||||
groupByLabel,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
});
|
||||
return { ...calc };
|
||||
});
|
||||
const calcDataFiltered = calcData.filter(i =>
|
||||
!showEmpty ? i[balanceTypeOp] !== 0 : true,
|
||||
filterEmptyRows(showEmpty, i, balanceTypeOp),
|
||||
);
|
||||
|
||||
const legend = calculateLegend(
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// @ts-strict-ignore
|
||||
import { type GroupedEntity } from 'loot-core/types/models/reports';
|
||||
|
||||
export function filterEmptyRows(
|
||||
showEmpty: boolean,
|
||||
data: GroupedEntity,
|
||||
balanceTypeOp: string,
|
||||
): boolean {
|
||||
let showHide;
|
||||
if (balanceTypeOp === 'totalTotals') {
|
||||
showHide =
|
||||
data['totalDebts'] !== 0 ||
|
||||
data['totalAssets'] !== 0 ||
|
||||
data['totalTotals'] !== 0;
|
||||
} else {
|
||||
showHide = data[balanceTypeOp] !== 0;
|
||||
}
|
||||
return !showEmpty ? showHide : true;
|
||||
}
|
||||
@@ -6,19 +6,46 @@ import {
|
||||
export function filterHiddenItems(
|
||||
item: UncategorizedEntity,
|
||||
data: QueryDataEntity[],
|
||||
showOffBudget?: boolean,
|
||||
showHiddenCategories?: boolean,
|
||||
showUncategorized?: boolean,
|
||||
) {
|
||||
return data.filter(asset => {
|
||||
const showHide = data
|
||||
.filter(e =>
|
||||
!showHiddenCategories
|
||||
? e.categoryHidden === false && e.categoryGroupHidden === false
|
||||
: true,
|
||||
)
|
||||
.filter(f =>
|
||||
showOffBudget
|
||||
? showUncategorized
|
||||
? //true,true
|
||||
true
|
||||
: //true,false
|
||||
f.category !== null ||
|
||||
f.accountOffBudget !== false ||
|
||||
f.transferAccount !== null
|
||||
: showUncategorized
|
||||
? //false, true
|
||||
f.accountOffBudget === false && f.transferAccount === null
|
||||
: //false false
|
||||
f.category !== null &&
|
||||
f.accountOffBudget === false &&
|
||||
f.transferAccount === null,
|
||||
);
|
||||
|
||||
return showHide.filter(query => {
|
||||
if (!item.uncategorized_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isTransfer = item.is_transfer
|
||||
? asset.transferAccount
|
||||
: !asset.transferAccount;
|
||||
const isHidden = item.has_category ? true : !asset.category;
|
||||
? query.transferAccount
|
||||
: !query.transferAccount;
|
||||
const isHidden = item.has_category ? true : !query.category;
|
||||
const isOffBudget = item.is_off_budget
|
||||
? asset.accountOffBudget
|
||||
: !asset.accountOffBudget;
|
||||
? query.accountOffBudget
|
||||
: !query.accountOffBudget;
|
||||
|
||||
return isTransfer && isHidden && isOffBudget;
|
||||
});
|
||||
|
||||
@@ -3,10 +3,12 @@ import { runQuery } from 'loot-core/src/client/query-helpers';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { integerToAmount } from 'loot-core/src/shared/util';
|
||||
import { type GroupedEntity } from 'loot-core/types/models/reports';
|
||||
|
||||
import { categoryLists } from '../ReportOptions';
|
||||
|
||||
import { type createCustomSpreadsheetProps } from './custom-spreadsheet';
|
||||
import { filterEmptyRows } from './filterEmptyRows';
|
||||
import { filterHiddenItems } from './filterHiddenItems';
|
||||
import { makeQuery } from './makeQuery';
|
||||
import { recalculate } from './recalculate';
|
||||
@@ -19,19 +21,15 @@ export function createGroupedSpreadsheet({
|
||||
conditions = [],
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudgetHidden,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
balanceTypeOp,
|
||||
}: createCustomSpreadsheetProps) {
|
||||
const [categoryList, categoryGroup] = categoryLists(
|
||||
showOffBudgetHidden,
|
||||
showUncategorized,
|
||||
categories,
|
||||
);
|
||||
const [categoryList, categoryGroup] = categoryLists(categories);
|
||||
|
||||
const categoryFilter = (categoryList || []).filter(
|
||||
const categoryFilter = (categories.list || []).filter(
|
||||
category =>
|
||||
!category.hidden &&
|
||||
selectedCategories &&
|
||||
selectedCategories.some(
|
||||
selectedCategory => selectedCategory.id === category.id,
|
||||
@@ -54,7 +52,6 @@ export function createGroupedSpreadsheet({
|
||||
'assets',
|
||||
startDate,
|
||||
endDate,
|
||||
showOffBudgetHidden,
|
||||
selectedCategories,
|
||||
categoryFilter,
|
||||
conditionsOpKey,
|
||||
@@ -66,7 +63,6 @@ export function createGroupedSpreadsheet({
|
||||
'debts',
|
||||
startDate,
|
||||
endDate,
|
||||
showOffBudgetHidden,
|
||||
selectedCategories,
|
||||
categoryFilter,
|
||||
conditionsOpKey,
|
||||
@@ -77,7 +73,7 @@ export function createGroupedSpreadsheet({
|
||||
|
||||
const months = monthUtils.rangeInclusive(startDate, endDate);
|
||||
|
||||
const groupedData = categoryGroup.map(
|
||||
const groupedData: GroupedEntity[] = categoryGroup.map(
|
||||
group => {
|
||||
let totalAssets = 0;
|
||||
let totalDebts = 0;
|
||||
@@ -87,16 +83,30 @@ export function createGroupedSpreadsheet({
|
||||
let groupedDebts = 0;
|
||||
|
||||
group.categories.forEach(item => {
|
||||
const monthAssets = filterHiddenItems(item, assets)
|
||||
const monthAssets = filterHiddenItems(
|
||||
item,
|
||||
assets,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
asset => asset.date === month && asset.category === item.id,
|
||||
asset =>
|
||||
asset.date === month && asset.category === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
groupedAssets += monthAssets;
|
||||
|
||||
const monthDebts = filterHiddenItems(item, debts)
|
||||
const monthDebts = filterHiddenItems(
|
||||
item,
|
||||
debts,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
debts => debts.date === month && debts.category === item.id,
|
||||
debts =>
|
||||
debts.date === month && debts.category === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
groupedDebts += monthDebts;
|
||||
@@ -122,6 +132,9 @@ export function createGroupedSpreadsheet({
|
||||
assets,
|
||||
debts,
|
||||
groupByLabel: 'category',
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
});
|
||||
return { ...calc };
|
||||
});
|
||||
@@ -134,14 +147,14 @@ export function createGroupedSpreadsheet({
|
||||
totalTotals: integerToAmount(totalAssets + totalDebts),
|
||||
monthData,
|
||||
categories: stackedCategories.filter(i =>
|
||||
!showEmpty ? i[balanceTypeOp] !== 0 : true,
|
||||
filterEmptyRows(showEmpty, i, balanceTypeOp),
|
||||
),
|
||||
};
|
||||
},
|
||||
[startDate, endDate],
|
||||
);
|
||||
setData(
|
||||
groupedData.filter(i => (!showEmpty ? i[balanceTypeOp] !== 0 : true)),
|
||||
groupedData.filter(i => filterEmptyRows(showEmpty, i, balanceTypeOp)),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,35 +5,12 @@ export function makeQuery(
|
||||
name: string,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
showOffBudgetHidden: boolean,
|
||||
selectedCategories: CategoryEntity[],
|
||||
categoryFilter: CategoryEntity[],
|
||||
conditionsOpKey: string,
|
||||
filters: unknown[],
|
||||
) {
|
||||
const query = q('transactions')
|
||||
.filter(
|
||||
//Show Offbudget and hidden categories
|
||||
!showOffBudgetHidden && {
|
||||
$and: [
|
||||
{
|
||||
'account.offbudget': false,
|
||||
$or: [
|
||||
{
|
||||
'category.hidden': false,
|
||||
category: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
$or: [
|
||||
{
|
||||
'payee.transfer_acct.offbudget': true,
|
||||
'payee.transfer_acct': null,
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
//Apply Category_Selector
|
||||
.filter(
|
||||
selectedCategories && {
|
||||
@@ -74,7 +51,9 @@ export function makeQuery(
|
||||
.select([
|
||||
{ date: { $month: '$date' } },
|
||||
{ category: { $id: '$category.id' } },
|
||||
{ categoryHidden: { $id: '$category.hidden' } },
|
||||
{ categoryGroup: { $id: '$category.group.id' } },
|
||||
{ categoryGroupHidden: { $id: '$category.group.hidden' } },
|
||||
{ account: { $id: '$account.id' } },
|
||||
{ accountOffBudget: { $id: '$account.offbudget' } },
|
||||
{ payee: { $id: '$payee.id' } },
|
||||
|
||||
@@ -13,6 +13,9 @@ type recalculateProps = {
|
||||
assets: QueryDataEntity[];
|
||||
debts: QueryDataEntity[];
|
||||
groupByLabel: string;
|
||||
showOffBudget?: boolean;
|
||||
showHiddenCategories?: boolean;
|
||||
showUncategorized?: boolean;
|
||||
};
|
||||
|
||||
export function recalculate({
|
||||
@@ -21,19 +24,39 @@ export function recalculate({
|
||||
assets,
|
||||
debts,
|
||||
groupByLabel,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
}: recalculateProps) {
|
||||
let totalAssets = 0;
|
||||
let totalDebts = 0;
|
||||
const monthData = months.reduce((arr, month) => {
|
||||
const last = arr.length === 0 ? null : arr[arr.length - 1];
|
||||
|
||||
const monthAssets = filterHiddenItems(item, assets)
|
||||
.filter(asset => asset.date === month && asset[groupByLabel] === item.id)
|
||||
const monthAssets = filterHiddenItems(
|
||||
item,
|
||||
assets,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
asset =>
|
||||
asset.date === month && asset[groupByLabel] === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
totalAssets += monthAssets;
|
||||
|
||||
const monthDebts = filterHiddenItems(item, debts)
|
||||
.filter(debt => debt.date === month && debt[groupByLabel] === item.id)
|
||||
const monthDebts = filterHiddenItems(
|
||||
item,
|
||||
debts,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
)
|
||||
.filter(
|
||||
debt => debt.date === month && debt[groupByLabel] === (item.id ?? null),
|
||||
)
|
||||
.reduce((a, v) => (a = a + v.amount), 0);
|
||||
totalDebts += monthDebts;
|
||||
|
||||
|
||||
13
packages/loot-core/src/types/models/reports.d.ts
vendored
13
packages/loot-core/src/types/models/reports.d.ts
vendored
@@ -6,7 +6,8 @@ export interface CustomReportEntity {
|
||||
groupBy: string;
|
||||
balanceType: string;
|
||||
showEmpty: boolean;
|
||||
showOffBudgetHidden: boolean;
|
||||
showOffBudget: boolean;
|
||||
showHiddenCategories: boolean;
|
||||
showUncategorized: boolean;
|
||||
graphType: string;
|
||||
selectedCategories;
|
||||
@@ -21,12 +22,12 @@ export interface CustomReportEntity {
|
||||
}
|
||||
|
||||
export interface GroupedEntity {
|
||||
data: DataEntity[];
|
||||
data?: DataEntity[];
|
||||
monthData: DataEntity[];
|
||||
groupedData: DataEntity[];
|
||||
legend: LegendEntity[];
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
groupedData?: DataEntity[];
|
||||
legend?: LegendEntity[];
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
totalDebts: number;
|
||||
totalAssets: number;
|
||||
totalTotals: number;
|
||||
|
||||
6
upcoming-release-notes/2302.md
Normal file
6
upcoming-release-notes/2302.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
In custom reports: separating "show offbudget" filter to split out hidden categories from offbudget.
|
||||
Reference in New Issue
Block a user