mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 03:32:54 -05:00
Custom Reports convert all jsx files (#2707)
* Overview * Router and Selector * ReportSidebar * ReportTopbar * CustomReport * updates * notes * Patch some TS issues * fix malfunctioning disabled lists * add find --------- Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
This commit is contained in:
@@ -36,7 +36,7 @@ type CategoryAutocompleteItem = CategoryEntity & {
|
||||
group?: CategoryGroupEntity;
|
||||
};
|
||||
|
||||
export type CategoryListProps = {
|
||||
type CategoryListProps = {
|
||||
items: CategoryAutocompleteItem[];
|
||||
getItemProps?: (arg: {
|
||||
item: CategoryAutocompleteItem;
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
SvgViewHide,
|
||||
SvgViewShow,
|
||||
} from '../../icons/v2';
|
||||
import { type CategoryListProps } from '../autocomplete/CategoryAutocomplete';
|
||||
import { Button } from '../common/Button';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
@@ -22,8 +21,8 @@ import { GraphButton } from './GraphButton';
|
||||
|
||||
type CategorySelectorProps = {
|
||||
categoryGroups: Array<CategoryGroupEntity>;
|
||||
selectedCategories: CategoryListProps['items'];
|
||||
setSelectedCategories: (selectedCategories: CategoryEntity[]) => null;
|
||||
selectedCategories: CategoryEntity[];
|
||||
setSelectedCategories: (selectedCategories: CategoryEntity[]) => void;
|
||||
showHiddenCategories?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,11 +32,9 @@ export function Overview() {
|
||||
<View
|
||||
style={{
|
||||
...styles.page,
|
||||
...{
|
||||
padding: 15,
|
||||
paddingTop: 0,
|
||||
minWidth: isNarrowWidth ? null : 700,
|
||||
},
|
||||
padding: 15,
|
||||
paddingTop: 0,
|
||||
minWidth: isNarrowWidth ? undefined : 700,
|
||||
}}
|
||||
>
|
||||
{customReportsFeatureFlag && !isNarrowWidth && (
|
||||
@@ -44,95 +44,118 @@ const groupByOptions = [
|
||||
{ description: 'Interval' },
|
||||
];
|
||||
|
||||
const dateRangeOptions = [
|
||||
export type dateRangeProps = {
|
||||
description: string;
|
||||
name: number | string;
|
||||
type?: string;
|
||||
Daily: boolean;
|
||||
Weekly: boolean;
|
||||
Monthly: boolean;
|
||||
Yearly: boolean;
|
||||
};
|
||||
|
||||
const dateRangeOptions: dateRangeProps[] = [
|
||||
{
|
||||
description: 'This week',
|
||||
type: 'Weeks',
|
||||
name: 0,
|
||||
Yearly: false,
|
||||
Monthly: false,
|
||||
type: 'Weeks',
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: false,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Last week',
|
||||
type: 'Weeks',
|
||||
name: 1,
|
||||
Yearly: false,
|
||||
Monthly: false,
|
||||
type: 'Weeks',
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: false,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'This month',
|
||||
type: 'Months',
|
||||
name: 0,
|
||||
Yearly: false,
|
||||
Monthly: true,
|
||||
type: 'Months',
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Last month',
|
||||
type: 'Months',
|
||||
name: 1,
|
||||
Yearly: false,
|
||||
Monthly: true,
|
||||
type: 'Months',
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Last 3 months',
|
||||
type: 'Months',
|
||||
name: 2,
|
||||
Yearly: false,
|
||||
Monthly: true,
|
||||
type: 'Months',
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Last 6 months',
|
||||
type: 'Months',
|
||||
name: 5,
|
||||
Yearly: false,
|
||||
Monthly: true,
|
||||
type: 'Months',
|
||||
Daily: false,
|
||||
Weekly: false,
|
||||
Monthly: true,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Last 12 months',
|
||||
type: 'Months',
|
||||
name: 11,
|
||||
Yearly: false,
|
||||
Monthly: true,
|
||||
type: 'Months',
|
||||
Daily: false,
|
||||
Weekly: false,
|
||||
Monthly: true,
|
||||
Yearly: false,
|
||||
},
|
||||
{
|
||||
description: 'Year to date',
|
||||
name: 'yearToDate',
|
||||
Yearly: true,
|
||||
Monthly: true,
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: true,
|
||||
},
|
||||
{
|
||||
description: 'Last year',
|
||||
name: 'lastYear',
|
||||
Yearly: true,
|
||||
Monthly: true,
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: true,
|
||||
},
|
||||
{
|
||||
description: 'All time',
|
||||
name: 'allTime',
|
||||
Yearly: true,
|
||||
Monthly: true,
|
||||
Daily: true,
|
||||
Weekly: true,
|
||||
Monthly: true,
|
||||
Yearly: true,
|
||||
},
|
||||
];
|
||||
|
||||
const intervalOptions = [
|
||||
type intervalOptionsProps = {
|
||||
description: string;
|
||||
name: 'Day' | 'Week' | 'Month' | 'Year';
|
||||
format: string;
|
||||
range:
|
||||
| 'dayRangeInclusive'
|
||||
| 'weekRangeInclusive'
|
||||
| 'rangeInclusive'
|
||||
| 'yearRangeInclusive';
|
||||
};
|
||||
|
||||
const intervalOptions: intervalOptionsProps[] = [
|
||||
{
|
||||
description: 'Daily',
|
||||
name: 'Day',
|
||||
@@ -175,15 +198,19 @@ export const ReportOptions = {
|
||||
dateRangeOptions.map(item => [item.description, item.type]),
|
||||
),
|
||||
interval: intervalOptions,
|
||||
intervalMap: new Map(
|
||||
intervalMap: new Map<string, 'Day' | 'Week' | 'Month' | 'Year'>(
|
||||
intervalOptions.map(item => [item.description, item.name]),
|
||||
),
|
||||
intervalFormat: new Map(
|
||||
intervalOptions.map(item => [item.description, item.format]),
|
||||
),
|
||||
intervalRange: new Map(
|
||||
intervalOptions.map(item => [item.description, item.range]),
|
||||
),
|
||||
intervalRange: new Map<
|
||||
string,
|
||||
| 'dayRangeInclusive'
|
||||
| 'weekRangeInclusive'
|
||||
| 'rangeInclusive'
|
||||
| 'yearRangeInclusive'
|
||||
>(intervalOptions.map(item => [item.description, item.range])),
|
||||
};
|
||||
|
||||
export type QueryDataEntity = {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { type CategoryEntity } from 'loot-core/types/models/category';
|
||||
import { type CategoryGroupEntity } from 'loot-core/types/models/category-group';
|
||||
import { type CustomReportEntity } from 'loot-core/types/models/reports';
|
||||
import { type LocalPrefs } from 'loot-core/types/prefs';
|
||||
|
||||
import { theme } from '../../style';
|
||||
import { theme } from '../../style/theme';
|
||||
import { Button } from '../common/Button';
|
||||
import { Menu } from '../common/Menu';
|
||||
import { Select } from '../common/Select';
|
||||
@@ -14,10 +18,42 @@ import { CategorySelector } from './CategorySelector';
|
||||
import { defaultsList } from './disabledList';
|
||||
import { getLiveRange } from './getLiveRange';
|
||||
import { ModeButton } from './ModeButton';
|
||||
import { ReportOptions } from './ReportOptions';
|
||||
import { type dateRangeProps, ReportOptions } from './ReportOptions';
|
||||
import { validateEnd, validateStart } from './reportRanges';
|
||||
import { setSessionReport } from './setSessionReport';
|
||||
|
||||
type ReportSidebarProps = {
|
||||
customReportItems: CustomReportEntity;
|
||||
categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] };
|
||||
dateRangeLine: number;
|
||||
allIntervals: { name: string; pretty: string }[];
|
||||
setDateRange: (value: string) => void;
|
||||
setGraphType: (value: string) => void;
|
||||
setGroupBy: (value: string) => void;
|
||||
setInterval: (value: string) => void;
|
||||
setBalanceType: (value: string) => void;
|
||||
setMode: (value: string) => void;
|
||||
setIsDateStatic: (value: boolean) => void;
|
||||
setShowEmpty: (value: boolean) => void;
|
||||
setShowOffBudget: (value: boolean) => void;
|
||||
setShowHiddenCategories: (value: boolean) => void;
|
||||
setShowUncategorized: (value: boolean) => void;
|
||||
setSelectedCategories: (value: CategoryEntity[]) => void;
|
||||
onChangeDates: (dateStart: string, dateEnd: string) => void;
|
||||
onReportChange: ({
|
||||
savedReport,
|
||||
type,
|
||||
}: {
|
||||
savedReport?: CustomReportEntity;
|
||||
type: string;
|
||||
}) => void;
|
||||
disabledItems: (type: string) => string[];
|
||||
defaultItems: (item: string) => void;
|
||||
defaultModeItems: (graph: string, item: string) => void;
|
||||
earliestTransaction: string;
|
||||
firstDayOfWeekIdx: LocalPrefs['firstDayOfWeekIdx'];
|
||||
};
|
||||
|
||||
export function ReportSidebar({
|
||||
customReportItems,
|
||||
categories,
|
||||
@@ -42,9 +78,9 @@ export function ReportSidebar({
|
||||
defaultModeItems,
|
||||
earliestTransaction,
|
||||
firstDayOfWeekIdx,
|
||||
}) {
|
||||
}: ReportSidebarProps) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const onSelectRange = cond => {
|
||||
const onSelectRange = (cond: string) => {
|
||||
setSessionReport('dateRange', cond);
|
||||
onReportChange({ type: 'modify' });
|
||||
setDateRange(cond);
|
||||
@@ -53,11 +89,11 @@ export function ReportSidebar({
|
||||
);
|
||||
};
|
||||
|
||||
const onChangeMode = cond => {
|
||||
const onChangeMode = (cond: string) => {
|
||||
setSessionReport('mode', cond);
|
||||
onReportChange({ type: 'modify' });
|
||||
setMode(cond);
|
||||
let graph;
|
||||
let graph = '';
|
||||
if (cond === 'time') {
|
||||
if (customReportItems.graphType === 'BarGraph') {
|
||||
setSessionReport('graphType', 'StackedBarGraph');
|
||||
@@ -74,14 +110,14 @@ export function ReportSidebar({
|
||||
defaultModeItems(graph, cond);
|
||||
};
|
||||
|
||||
const onChangeSplit = cond => {
|
||||
const onChangeSplit = (cond: string) => {
|
||||
setSessionReport('groupBy', cond);
|
||||
onReportChange({ type: 'modify' });
|
||||
setGroupBy(cond);
|
||||
defaultItems(cond);
|
||||
};
|
||||
|
||||
const onChangeBalanceType = cond => {
|
||||
const onChangeBalanceType = (cond: string) => {
|
||||
setSessionReport('balanceType', cond);
|
||||
onReportChange({ type: 'modify' });
|
||||
setBalanceType(cond);
|
||||
@@ -187,11 +223,11 @@ export function ReportSidebar({
|
||||
onReportChange({ type: 'modify' });
|
||||
if (
|
||||
ReportOptions.dateRange
|
||||
.filter(int => !int[e])
|
||||
.filter(d => !d[e as keyof dateRangeProps])
|
||||
.map(int => int.description)
|
||||
.includes(customReportItems.dateRange)
|
||||
) {
|
||||
onSelectRange(defaultsList.intervalRange.get(e));
|
||||
onSelectRange(defaultsList.intervalRange.get(e) || '');
|
||||
}
|
||||
}}
|
||||
options={ReportOptions.interval.map(option => [
|
||||
@@ -353,9 +389,11 @@ export function ReportSidebar({
|
||||
onSelectRange(e);
|
||||
}}
|
||||
options={ReportOptions.dateRange
|
||||
.filter(f => f[customReportItems.interval])
|
||||
.filter(
|
||||
f => f[customReportItems.interval as keyof dateRangeProps],
|
||||
)
|
||||
.map(option => [option.description, option.description])}
|
||||
line={dateRangeLine > 0 && dateRangeLine}
|
||||
line={dateRangeLine > 0 ? dateRangeLine : undefined}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
@@ -385,7 +423,9 @@ export function ReportSidebar({
|
||||
value={customReportItems.startDate}
|
||||
defaultLabel={monthUtils.format(
|
||||
customReportItems.startDate,
|
||||
ReportOptions.intervalFormat.get(customReportItems.interval),
|
||||
ReportOptions.intervalFormat.get(
|
||||
customReportItems.interval,
|
||||
) || '',
|
||||
)}
|
||||
options={allIntervals.map(({ name, pretty }) => [name, pretty])}
|
||||
/>
|
||||
@@ -415,7 +455,9 @@ export function ReportSidebar({
|
||||
value={customReportItems.endDate}
|
||||
defaultLabel={monthUtils.format(
|
||||
customReportItems.endDate,
|
||||
ReportOptions.intervalFormat.get(customReportItems.interval),
|
||||
ReportOptions.intervalFormat.get(
|
||||
customReportItems.interval,
|
||||
) || '',
|
||||
)}
|
||||
options={allIntervals.map(({ name, pretty }) => [name, pretty])}
|
||||
/>
|
||||
@@ -443,7 +485,7 @@ export function ReportSidebar({
|
||||
? true
|
||||
: false;
|
||||
})}
|
||||
selectedCategories={customReportItems.selectedCategories}
|
||||
selectedCategories={customReportItems.selectedCategories || []}
|
||||
setSelectedCategories={e => {
|
||||
setSelectedCategories(e);
|
||||
onReportChange({ type: 'modify' });
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
import { type CustomReportEntity } from 'loot-core/types/models/reports';
|
||||
import { type RuleConditionEntity } from 'loot-core/types/models/rule';
|
||||
|
||||
import {
|
||||
SvgCalculator,
|
||||
SvgChart,
|
||||
@@ -18,6 +21,27 @@ import { GraphButton } from './GraphButton';
|
||||
import { SaveReport } from './SaveReport';
|
||||
import { setSessionReport } from './setSessionReport';
|
||||
|
||||
type ReportTopbarProps = {
|
||||
customReportItems: CustomReportEntity;
|
||||
report: CustomReportEntity;
|
||||
savedStatus: string;
|
||||
setGraphType: (value: string) => void;
|
||||
viewLegend: boolean;
|
||||
viewSummary: boolean;
|
||||
viewLabels: boolean;
|
||||
onApplyFilter: (newFilter: RuleConditionEntity) => void;
|
||||
onChangeViews: (viewType: string) => void;
|
||||
onReportChange: ({
|
||||
savedReport,
|
||||
type,
|
||||
}: {
|
||||
savedReport?: CustomReportEntity;
|
||||
type: string;
|
||||
}) => void;
|
||||
isItemDisabled: (type: string) => boolean;
|
||||
defaultItems: (item: string) => void;
|
||||
};
|
||||
|
||||
export function ReportTopbar({
|
||||
customReportItems,
|
||||
report,
|
||||
@@ -29,10 +53,10 @@ export function ReportTopbar({
|
||||
onApplyFilter,
|
||||
onChangeViews,
|
||||
onReportChange,
|
||||
disabledItems,
|
||||
isItemDisabled,
|
||||
defaultItems,
|
||||
}) {
|
||||
const onChangeGraph = cond => {
|
||||
}: ReportTopbarProps) {
|
||||
const onChangeGraph = (cond: string) => {
|
||||
setSessionReport('graphType', cond);
|
||||
onReportChange({ type: 'modify' });
|
||||
setGraphType(cond);
|
||||
@@ -55,7 +79,7 @@ export function ReportTopbar({
|
||||
onChangeGraph('TableGraph');
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
disabled={disabledItems('TableGraph')}
|
||||
disabled={isItemDisabled('TableGraph')}
|
||||
>
|
||||
<SvgQueue width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -73,7 +97,7 @@ export function ReportTopbar({
|
||||
);
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
disabled={disabledItems(
|
||||
disabled={isItemDisabled(
|
||||
customReportItems.mode === 'total' ? 'BarGraph' : 'StackedBarGraph',
|
||||
)}
|
||||
>
|
||||
@@ -86,7 +110,7 @@ export function ReportTopbar({
|
||||
onChangeGraph('LineGraph');
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
disabled={disabledItems('LineGraph')}
|
||||
disabled={isItemDisabled('LineGraph')}
|
||||
>
|
||||
<SvgChart width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -97,7 +121,7 @@ export function ReportTopbar({
|
||||
onChangeGraph('AreaGraph');
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
disabled={disabledItems('AreaGraph')}
|
||||
disabled={isItemDisabled('AreaGraph')}
|
||||
>
|
||||
<SvgChartArea width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -108,7 +132,7 @@ export function ReportTopbar({
|
||||
onChangeGraph('DonutGraph');
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
disabled={disabledItems('DonutGraph')}
|
||||
disabled={isItemDisabled('DonutGraph')}
|
||||
>
|
||||
<SvgChartPie width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -128,7 +152,7 @@ export function ReportTopbar({
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
title="Show Legend"
|
||||
disabled={disabledItems('ShowLegend')}
|
||||
disabled={isItemDisabled('ShowLegend')}
|
||||
>
|
||||
<SvgListBullet width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -149,7 +173,7 @@ export function ReportTopbar({
|
||||
}}
|
||||
style={{ marginRight: 15 }}
|
||||
title="Show Labels"
|
||||
disabled={disabledItems('ShowLabels')}
|
||||
disabled={isItemDisabled('ShowLabels')}
|
||||
>
|
||||
<SvgTag width={15} height={15} />
|
||||
</GraphButton>
|
||||
@@ -165,11 +189,15 @@ export function ReportTopbar({
|
||||
<FilterButton
|
||||
compact
|
||||
hover
|
||||
onApply={e => {
|
||||
setSessionReport('conditions', [...customReportItems.conditions, e]);
|
||||
onApply={(e: RuleConditionEntity) => {
|
||||
setSessionReport('conditions', [
|
||||
...(customReportItems.conditions ?? []),
|
||||
e,
|
||||
]);
|
||||
onApplyFilter(e);
|
||||
onReportChange({ type: 'modify' });
|
||||
}}
|
||||
exclude={[]}
|
||||
/>
|
||||
<View style={{ flex: 1 }} />
|
||||
<SaveReport
|
||||
@@ -17,7 +17,16 @@ const intervalOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
const totalGraphOptions = [
|
||||
type graphOptions = {
|
||||
description: string;
|
||||
disabledSplit: string[];
|
||||
defaultSplit: string;
|
||||
disabledType: string[];
|
||||
defaultType: string;
|
||||
disableLegend?: boolean;
|
||||
disableLabel?: boolean;
|
||||
};
|
||||
const totalGraphOptions: graphOptions[] = [
|
||||
{
|
||||
description: 'TableGraph',
|
||||
disabledSplit: [],
|
||||
@@ -51,7 +60,7 @@ const totalGraphOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
const timeGraphOptions = [
|
||||
const timeGraphOptions: graphOptions[] = [
|
||||
{
|
||||
description: 'TableGraph',
|
||||
disabledSplit: ['Interval'],
|
||||
@@ -94,35 +103,67 @@ const modeOptions = [
|
||||
},
|
||||
];
|
||||
|
||||
export function disabledGraphList(
|
||||
item: string,
|
||||
newGraph: string,
|
||||
type: 'disabledSplit' | 'disabledType',
|
||||
) {
|
||||
const graphList = modeOptions.find(d => d.description === item);
|
||||
if (!graphList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const disabledList = graphList.graphs.find(e => e.description === newGraph);
|
||||
if (!disabledList) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return disabledList[type];
|
||||
}
|
||||
|
||||
export function disabledLegendLabel(
|
||||
item: string,
|
||||
newGraph: string,
|
||||
type: 'disableLegend' | 'disableLabel',
|
||||
) {
|
||||
const graphList = modeOptions.find(d => d.description === item);
|
||||
if (!graphList) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const disableLegendLabel = graphList.graphs.find(
|
||||
e => e.description === newGraph,
|
||||
);
|
||||
if (!disableLegendLabel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return disableLegendLabel[type];
|
||||
}
|
||||
|
||||
export function defaultsGraphList(
|
||||
item: string,
|
||||
newGraph: string,
|
||||
type: 'defaultSplit' | 'defaultType',
|
||||
) {
|
||||
const graphList = modeOptions.find(d => d.description === item);
|
||||
if (!graphList) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const defaultItem = graphList.graphs.find(e => e.description === newGraph);
|
||||
if (!defaultItem) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return defaultItem[type];
|
||||
}
|
||||
|
||||
export const disabledList = {
|
||||
mode: modeOptions,
|
||||
modeGraphsMap: new Map(
|
||||
modeOptions.map(item => [item.description, item.disabledGraph]),
|
||||
),
|
||||
graphSplitMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.disabledSplit])),
|
||||
]),
|
||||
),
|
||||
graphTypeMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.disabledType])),
|
||||
]),
|
||||
),
|
||||
graphLegendMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.disableLegend])),
|
||||
]),
|
||||
),
|
||||
graphLabelsMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.disableLabel])),
|
||||
]),
|
||||
),
|
||||
};
|
||||
|
||||
export const defaultsList = {
|
||||
@@ -130,18 +171,6 @@ export const defaultsList = {
|
||||
modeGraphsMap: new Map(
|
||||
modeOptions.map(item => [item.description, item.defaultGraph]),
|
||||
),
|
||||
graphSplitMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.defaultSplit])),
|
||||
]),
|
||||
),
|
||||
graphTypeMap: new Map(
|
||||
modeOptions.map(item => [
|
||||
item.description,
|
||||
new Map([...item.graphs].map(f => [f.description, f.defaultType])),
|
||||
]),
|
||||
),
|
||||
intervalRange: new Map(
|
||||
intervalOptions.map(item => [item.description, item.defaultRange]),
|
||||
),
|
||||
|
||||
@@ -8,9 +8,9 @@ export function getLiveRange(
|
||||
cond: string,
|
||||
earliestTransaction: string,
|
||||
firstDayOfWeekIdx?: LocalPrefs['firstDayOfWeekIdx'],
|
||||
) {
|
||||
let dateStart;
|
||||
let dateEnd;
|
||||
): [string, string] {
|
||||
let dateStart = earliestTransaction;
|
||||
let dateEnd = monthUtils.currentDay();
|
||||
const rangeName = ReportOptions.dateRangeMap.get(cond);
|
||||
switch (rangeName) {
|
||||
case 'yearToDate':
|
||||
|
||||
@@ -7,7 +7,7 @@ export function validateStart(
|
||||
end: string,
|
||||
interval?: string,
|
||||
firstDayOfWeekIdx?: LocalPrefs['firstDayOfWeekIdx'],
|
||||
) {
|
||||
): [string, string] {
|
||||
let addDays;
|
||||
let dateStart;
|
||||
switch (interval) {
|
||||
@@ -47,7 +47,7 @@ export function validateEnd(
|
||||
end: string,
|
||||
interval?: string,
|
||||
firstDayOfWeekIdx?: LocalPrefs['firstDayOfWeekIdx'],
|
||||
) {
|
||||
): [string, string] {
|
||||
let subDays;
|
||||
let dateEnd;
|
||||
switch (interval) {
|
||||
@@ -98,7 +98,7 @@ function boundedRange(
|
||||
end: string,
|
||||
interval?: string,
|
||||
firstDayOfWeekIdx?: LocalPrefs['firstDayOfWeekIdx'],
|
||||
) {
|
||||
): [string, string] {
|
||||
let latest;
|
||||
switch (interval) {
|
||||
case 'Daily':
|
||||
|
||||
@@ -6,27 +6,44 @@ import * as d from 'date-fns';
|
||||
import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import * as monthUtils from 'loot-core/src/shared/months';
|
||||
import { amountToCurrency } from 'loot-core/src/shared/util';
|
||||
import { type CategoryEntity } from 'loot-core/types/models/category';
|
||||
import {
|
||||
type GroupedEntity,
|
||||
type CustomReportEntity,
|
||||
} from 'loot-core/types/models/reports';
|
||||
import { type RuleConditionEntity } from 'loot-core/types/models/rule';
|
||||
|
||||
import { useAccounts } from '../../../hooks/useAccounts';
|
||||
import { useCategories } from '../../../hooks/useCategories';
|
||||
import { useFilters } from '../../../hooks/useFilters';
|
||||
import { useLocalPref } from '../../../hooks/useLocalPref';
|
||||
import { usePayees } from '../../../hooks/usePayees';
|
||||
import { SvgArrowLeft } from '../../../icons/v1/ArrowLeft';
|
||||
import { useResponsive } from '../../../ResponsiveProvider';
|
||||
import { theme, styles } from '../../../style';
|
||||
import { AlignedText } from '../../common/AlignedText';
|
||||
import { Block } from '../../common/Block';
|
||||
import { Link } from '../../common/Link';
|
||||
import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
import { AppliedFilters } from '../../filters/AppliedFilters';
|
||||
import { PrivacyFilter } from '../../PrivacyFilter';
|
||||
import { ChooseGraph } from '../ChooseGraph';
|
||||
import { defaultsList, disabledList } from '../disabledList';
|
||||
import {
|
||||
defaultsGraphList,
|
||||
defaultsList,
|
||||
disabledGraphList,
|
||||
disabledLegendLabel,
|
||||
disabledList,
|
||||
} from '../disabledList';
|
||||
import { getLiveRange } from '../getLiveRange';
|
||||
import { Header } from '../Header';
|
||||
import { LoadingIndicator } from '../LoadingIndicator';
|
||||
import { ReportLegend } from '../ReportLegend';
|
||||
import { ReportOptions, defaultReport } from '../ReportOptions';
|
||||
import {
|
||||
ReportOptions,
|
||||
defaultReport,
|
||||
type dateRangeProps,
|
||||
} from '../ReportOptions';
|
||||
import { ReportSidebar } from '../ReportSidebar';
|
||||
import { ReportSummary } from '../ReportSummary';
|
||||
import { ReportTopbar } from '../ReportTopbar';
|
||||
@@ -60,20 +77,28 @@ export function CustomReport() {
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const prevUrl = sessionStorage.getItem('url');
|
||||
const prevUrl = sessionStorage.getItem('url') || '';
|
||||
|
||||
sessionStorage.setItem('prevUrl', prevUrl);
|
||||
sessionStorage.setItem('url', location.pathname);
|
||||
|
||||
if (['/reports'].includes(prevUrl)) sessionStorage.clear();
|
||||
|
||||
const session = JSON.parse(sessionStorage.getItem('report'));
|
||||
const reportFromSessionStorage = sessionStorage.getItem('report');
|
||||
const session = reportFromSessionStorage
|
||||
? JSON.parse(reportFromSessionStorage)
|
||||
: {};
|
||||
const combine = location.state
|
||||
? location.state.report ?? defaultReport
|
||||
: defaultReport;
|
||||
const loadReport = { ...combine, ...session };
|
||||
|
||||
const [allIntervals, setAllIntervals] = useState(null);
|
||||
const [allIntervals, setAllIntervals] = useState<
|
||||
Array<{
|
||||
name: string;
|
||||
pretty: string;
|
||||
}>
|
||||
>([]);
|
||||
|
||||
const [selectedCategories, setSelectedCategories] = useState(
|
||||
loadReport.selectedCategories,
|
||||
@@ -98,7 +123,8 @@ export function CustomReport() {
|
||||
const [dateRange, setDateRange] = useState(loadReport.dateRange);
|
||||
const [dataCheck, setDataCheck] = useState(false);
|
||||
const dateRangeLine =
|
||||
ReportOptions.dateRange.filter(f => f[interval]).length - 3;
|
||||
ReportOptions.dateRange.filter(f => f[interval as keyof dateRangeProps])
|
||||
.length - 3;
|
||||
|
||||
const [intervals, setIntervals] = useState(
|
||||
monthUtils.rangeInclusive(startDate, endDate),
|
||||
@@ -122,34 +148,62 @@ export function CustomReport() {
|
||||
useEffect(() => {
|
||||
async function run() {
|
||||
onApplyFilter(null);
|
||||
report.conditions.forEach(condition => onApplyFilter(condition));
|
||||
report.conditions.forEach((condition: RuleConditionEntity) =>
|
||||
onApplyFilter(condition),
|
||||
);
|
||||
const trans = await send('get-earliest-transaction');
|
||||
setEarliestTransaction(trans ? trans.date : monthUtils.currentDay());
|
||||
const format =
|
||||
ReportOptions.intervalMap.get(interval).toLowerCase() + 'FromDate';
|
||||
const currentInterval =
|
||||
monthUtils['current' + ReportOptions.intervalMap.get(interval)]();
|
||||
const earliestInterval = trans
|
||||
? monthUtils[format](d.parseISO(fromDateRepr(trans.date)))
|
||||
: currentInterval;
|
||||
|
||||
const rangeProps =
|
||||
const fromDate =
|
||||
interval === 'Weekly'
|
||||
? [earliestInterval, currentInterval, firstDayOfWeekIdx]
|
||||
: [earliestInterval, currentInterval];
|
||||
const allInter = monthUtils[ReportOptions.intervalRange.get(interval)](
|
||||
...rangeProps,
|
||||
)
|
||||
.map(inter => ({
|
||||
? 'dayFromDate'
|
||||
: (((ReportOptions.intervalMap.get(interval) || 'Day').toLowerCase() +
|
||||
'FromDate') as 'dayFromDate' | 'monthFromDate' | 'yearFromDate');
|
||||
|
||||
const currentDate =
|
||||
interval === 'Weekly'
|
||||
? 'currentDay'
|
||||
: (('current' +
|
||||
(ReportOptions.intervalMap.get(interval) || 'Day')) as
|
||||
| 'currentDay'
|
||||
| 'currentMonth'
|
||||
| 'currentYear');
|
||||
|
||||
const currentInterval =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.currentWeek(firstDayOfWeekIdx)
|
||||
: monthUtils[currentDate]();
|
||||
const earliestInterval =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.weekFromDate(
|
||||
d.parseISO(fromDateRepr(trans.date || monthUtils.currentDay())),
|
||||
firstDayOfWeekIdx,
|
||||
)
|
||||
: monthUtils[fromDate](
|
||||
d.parseISO(fromDateRepr(trans.date || monthUtils.currentDay())),
|
||||
);
|
||||
|
||||
const allIntervals =
|
||||
interval === 'Weekly'
|
||||
? monthUtils.weekRangeInclusive(
|
||||
earliestInterval,
|
||||
currentInterval,
|
||||
firstDayOfWeekIdx,
|
||||
)
|
||||
: monthUtils[
|
||||
ReportOptions.intervalRange.get(interval) || 'rangeInclusive'
|
||||
](earliestInterval, currentInterval);
|
||||
|
||||
const allIntervalsMap = allIntervals
|
||||
.map((inter: string) => ({
|
||||
name: inter,
|
||||
pretty: monthUtils.format(
|
||||
inter,
|
||||
ReportOptions.intervalFormat.get(interval),
|
||||
ReportOptions.intervalFormat.get(interval) || '',
|
||||
),
|
||||
}))
|
||||
.reverse();
|
||||
|
||||
setAllIntervals(allInter);
|
||||
setAllIntervals(allIntervalsMap);
|
||||
|
||||
if (!isDateStatic) {
|
||||
const [dateStart, dateEnd] = getLiveRange(
|
||||
@@ -162,19 +216,32 @@ export function CustomReport() {
|
||||
}
|
||||
}
|
||||
run();
|
||||
}, [interval]);
|
||||
}, [
|
||||
interval,
|
||||
dateRange,
|
||||
firstDayOfWeekIdx,
|
||||
isDateStatic,
|
||||
onApplyFilter,
|
||||
report.conditions,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const rangeProps =
|
||||
interval === 'Weekly'
|
||||
? [startDate, endDate, firstDayOfWeekIdx]
|
||||
: [startDate, endDate];
|
||||
setIntervals(
|
||||
monthUtils[ReportOptions.intervalRange.get(interval)](...rangeProps),
|
||||
);
|
||||
}, [interval, startDate, endDate]);
|
||||
const [start, end] = [startDate, endDate];
|
||||
if (interval === 'Weekly') {
|
||||
setIntervals(
|
||||
monthUtils.weekRangeInclusive(start, end, firstDayOfWeekIdx),
|
||||
);
|
||||
} else {
|
||||
setIntervals(
|
||||
monthUtils[
|
||||
ReportOptions.intervalRange.get(interval) || 'rangeInclusive'
|
||||
](start, end),
|
||||
);
|
||||
}
|
||||
}, [interval, startDate, endDate, firstDayOfWeekIdx]);
|
||||
|
||||
const balanceTypeOp = ReportOptions.balanceTypeMap.get(balanceType);
|
||||
const balanceTypeOp =
|
||||
ReportOptions.balanceTypeMap.get(balanceType) || 'totalDebts';
|
||||
const payees = usePayees();
|
||||
const accounts = useAccounts();
|
||||
|
||||
@@ -198,19 +265,15 @@ export function CustomReport() {
|
||||
startDate,
|
||||
endDate,
|
||||
interval,
|
||||
groupBy,
|
||||
balanceType,
|
||||
balanceTypeOp,
|
||||
categories,
|
||||
selectedCategories,
|
||||
payees,
|
||||
accounts,
|
||||
filters,
|
||||
conditionsOp,
|
||||
showEmpty,
|
||||
showOffBudget,
|
||||
showHiddenCategories,
|
||||
showUncategorized,
|
||||
graphType,
|
||||
firstDayOfWeekIdx,
|
||||
]);
|
||||
|
||||
@@ -241,7 +304,7 @@ export function CustomReport() {
|
||||
endDate,
|
||||
interval,
|
||||
groupBy,
|
||||
balanceType,
|
||||
balanceTypeOp,
|
||||
categories,
|
||||
selectedCategories,
|
||||
payees,
|
||||
@@ -258,8 +321,11 @@ export function CustomReport() {
|
||||
const graphData = useReport('default', getGraphData);
|
||||
const groupedData = useReport('grouped', getGroupData);
|
||||
|
||||
const data = { ...graphData, groupedData };
|
||||
const customReportItems = {
|
||||
const data: GroupedEntity = { ...graphData, groupedData } as GroupedEntity;
|
||||
|
||||
const customReportItems: CustomReportEntity = {
|
||||
id: '',
|
||||
name: '',
|
||||
startDate,
|
||||
endDate,
|
||||
isDateStatic,
|
||||
@@ -276,84 +342,104 @@ export function CustomReport() {
|
||||
graphType,
|
||||
conditions: filters,
|
||||
conditionsOp,
|
||||
data: {},
|
||||
};
|
||||
|
||||
const [scrollWidth, setScrollWidth] = useState(0);
|
||||
const [, setScrollWidth] = useState(0);
|
||||
|
||||
if (!allIntervals || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultModeItems = (graph, item) => {
|
||||
const defaultModeItems = (graph: string, item: string) => {
|
||||
const chooseGraph = graph || graphType;
|
||||
const newGraph = disabledList.modeGraphsMap.get(item).includes(chooseGraph)
|
||||
const newGraph = (disabledList.modeGraphsMap.get(item) || []).includes(
|
||||
chooseGraph,
|
||||
)
|
||||
? defaultsList.modeGraphsMap.get(item)
|
||||
: chooseGraph;
|
||||
if (disabledList.modeGraphsMap.get(item).includes(graphType)) {
|
||||
if ((disabledList.modeGraphsMap.get(item) || []).includes(graphType)) {
|
||||
setSessionReport('graphType', newGraph);
|
||||
setGraphType(newGraph);
|
||||
}
|
||||
|
||||
if (disabledList.graphSplitMap.get(item).get(newGraph).includes(groupBy)) {
|
||||
const cond = defaultsList.graphSplitMap.get(item).get(newGraph);
|
||||
if (
|
||||
(disabledGraphList(item, newGraph, 'disabledSplit') || []).includes(
|
||||
groupBy,
|
||||
)
|
||||
) {
|
||||
const cond = defaultsGraphList(item, newGraph, 'defaultSplit');
|
||||
setSessionReport('groupBy', cond);
|
||||
setGroupBy(cond);
|
||||
}
|
||||
|
||||
if (
|
||||
disabledList.graphTypeMap.get(item).get(newGraph).includes(balanceType)
|
||||
(disabledGraphList(item, newGraph, 'disabledType') || []).includes(
|
||||
balanceType,
|
||||
)
|
||||
) {
|
||||
const cond = defaultsList.graphTypeMap.get(item).get(newGraph);
|
||||
const cond = defaultsGraphList(item, newGraph, 'defaultType');
|
||||
setSessionReport('balanceType', cond);
|
||||
setBalanceType(cond);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultItems = item => {
|
||||
const defaultItems = (item: string) => {
|
||||
const chooseGraph = ReportOptions.groupBy.includes(item) ? graphType : item;
|
||||
if (
|
||||
disabledList.graphSplitMap.get(mode).get(chooseGraph).includes(groupBy)
|
||||
(disabledGraphList(mode, chooseGraph, 'disabledSplit') || []).includes(
|
||||
groupBy,
|
||||
)
|
||||
) {
|
||||
const cond = defaultsList.graphSplitMap.get(mode).get(chooseGraph);
|
||||
const cond = defaultsGraphList(mode, chooseGraph, 'defaultSplit');
|
||||
setSessionReport('groupBy', cond);
|
||||
setGroupBy(cond);
|
||||
}
|
||||
if (
|
||||
disabledList.graphTypeMap.get(mode).get(chooseGraph).includes(balanceType)
|
||||
(disabledGraphList(mode, chooseGraph, 'disabledType') || []).includes(
|
||||
balanceType,
|
||||
)
|
||||
) {
|
||||
const cond = defaultsList.graphTypeMap.get(mode).get(chooseGraph);
|
||||
const cond = defaultsGraphList(mode, chooseGraph, 'defaultType');
|
||||
setSessionReport('balanceType', cond);
|
||||
setBalanceType(cond);
|
||||
}
|
||||
};
|
||||
|
||||
const disabledItems = type => {
|
||||
const isItemDisabled = (type: string) => {
|
||||
switch (type) {
|
||||
case 'ShowLegend': {
|
||||
if (disabledLegendLabel(mode, graphType, 'disableLegend')) {
|
||||
setViewLegendPref(false);
|
||||
}
|
||||
return disabledLegendLabel(mode, graphType, 'disableLegend') || false;
|
||||
}
|
||||
case 'ShowLabels': {
|
||||
if (disabledLegendLabel(mode, graphType, 'disableLabel')) {
|
||||
setViewLabelsPref(false);
|
||||
}
|
||||
return disabledLegendLabel(mode, graphType, 'disableLabel') || false;
|
||||
}
|
||||
default:
|
||||
return (
|
||||
(disabledList.modeGraphsMap.get(mode) || []).includes(type) || false
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const disabledItems = (type: string) => {
|
||||
switch (type) {
|
||||
case 'split':
|
||||
return disabledList.graphSplitMap.get(mode).get(graphType);
|
||||
return disabledGraphList(mode, graphType, 'disabledSplit') || [];
|
||||
case 'type':
|
||||
return graphType === 'BarGraph' && groupBy === 'Interval'
|
||||
? []
|
||||
: disabledList.graphTypeMap.get(mode).get(graphType);
|
||||
case 'ShowLegend': {
|
||||
if (disabledList.graphLegendMap.get(mode).get(graphType)) {
|
||||
setViewLegendPref(false);
|
||||
}
|
||||
return disabledList.graphLegendMap.get(mode).get(graphType);
|
||||
}
|
||||
case 'ShowLabels': {
|
||||
if (disabledList.graphLabelsMap.get(mode).get(graphType)) {
|
||||
setViewLabelsPref(false);
|
||||
}
|
||||
return disabledList.graphLabelsMap.get(mode).get(graphType);
|
||||
}
|
||||
: disabledGraphList(mode, graphType, 'disabledType') || [];
|
||||
default:
|
||||
return disabledList.modeGraphsMap.get(mode).includes(type);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeDates = (dateStart, dateEnd) => {
|
||||
const onChangeDates = (dateStart: string, dateEnd: string) => {
|
||||
setSessionReport('startDate', dateStart);
|
||||
setSessionReport('endDate', dateEnd);
|
||||
setStartDate(dateStart);
|
||||
@@ -361,9 +447,9 @@ export function CustomReport() {
|
||||
onReportChange({ type: 'modify' });
|
||||
};
|
||||
|
||||
const onChangeViews = (viewType, status) => {
|
||||
const onChangeViews = (viewType: string) => {
|
||||
if (viewType === 'viewLegend') {
|
||||
setViewLegendPref(status ?? !viewLegend);
|
||||
setViewLegendPref(!viewLegend);
|
||||
}
|
||||
if (viewType === 'viewSummary') {
|
||||
setViewSummaryPref(!viewSummary);
|
||||
@@ -373,10 +459,12 @@ export function CustomReport() {
|
||||
}
|
||||
};
|
||||
|
||||
const setReportData = input => {
|
||||
const selectAll = [];
|
||||
const setReportData = (input: CustomReportEntity) => {
|
||||
const selectAll: CategoryEntity[] = [];
|
||||
categories.grouped.map(categoryGroup =>
|
||||
categoryGroup.categories.map(category => selectAll.push(category)),
|
||||
(categoryGroup.categories || []).map(category =>
|
||||
selectAll.push(category),
|
||||
),
|
||||
);
|
||||
|
||||
setStartDate(input.startDate);
|
||||
@@ -391,19 +479,20 @@ export function CustomReport() {
|
||||
setShowOffBudget(input.showOffBudget);
|
||||
setShowHiddenCategories(input.showHiddenCategories);
|
||||
setShowUncategorized(input.showUncategorized);
|
||||
setSelectedCategories(input.selectedCategories ?? selectAll);
|
||||
setSelectedCategories(input.selectedCategories || selectAll);
|
||||
setGraphType(input.graphType);
|
||||
onApplyFilter(null);
|
||||
input.conditions.forEach(condition => onApplyFilter(condition));
|
||||
(input.conditions || []).forEach(condition => onApplyFilter(condition));
|
||||
onCondOpChange(input.conditionsOp);
|
||||
};
|
||||
|
||||
const onChangeAppliedFilter = (filter, changedElement) => {
|
||||
onReportChange({ type: 'modify' });
|
||||
return changedElement(filter);
|
||||
};
|
||||
|
||||
const onReportChange = ({ savedReport, type }) => {
|
||||
const onReportChange = ({
|
||||
savedReport,
|
||||
type,
|
||||
}: {
|
||||
savedReport?: CustomReportEntity;
|
||||
type: string;
|
||||
}) => {
|
||||
switch (type) {
|
||||
case 'add-update':
|
||||
setSessionReport('savedStatus', 'saved');
|
||||
@@ -411,7 +500,7 @@ export function CustomReport() {
|
||||
setReport(savedReport);
|
||||
break;
|
||||
case 'rename':
|
||||
setReport({ ...report, name: savedReport.name });
|
||||
setReport({ ...report, name: savedReport?.name || '' });
|
||||
break;
|
||||
case 'modify':
|
||||
if (report.name) {
|
||||
@@ -434,7 +523,7 @@ export function CustomReport() {
|
||||
setSessionReport('savedStatus', 'saved');
|
||||
setSavedStatus('saved');
|
||||
setReport(savedReport);
|
||||
setReportData(savedReport);
|
||||
setReportData(savedReport || report);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -454,7 +543,24 @@ export function CustomReport() {
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Header title="Custom Report:" />
|
||||
<View
|
||||
style={{
|
||||
padding: 10,
|
||||
paddingTop: 0,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
variant="button"
|
||||
type="bare"
|
||||
to="/reports"
|
||||
style={{ marginBottom: '15', alignSelf: 'flex-start' }}
|
||||
>
|
||||
<SvgArrowLeft width={10} height={10} style={{ marginRight: 5 }} />{' '}
|
||||
Back
|
||||
</Link>
|
||||
<View style={styles.veryLargeText}>Custom Report:</View>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
...styles.veryLargeText,
|
||||
@@ -521,17 +627,20 @@ export function CustomReport() {
|
||||
onApplyFilter={onApplyFilter}
|
||||
onChangeViews={onChangeViews}
|
||||
onReportChange={onReportChange}
|
||||
disabledItems={disabledItems}
|
||||
isItemDisabled={isItemDisabled}
|
||||
defaultItems={defaultItems}
|
||||
/>
|
||||
)}
|
||||
{filters && filters.length > 0 && (
|
||||
<View
|
||||
style={{ marginBottom: 10, marginLeft: 5, flexShrink: 0 }}
|
||||
spacing={2}
|
||||
direction="row"
|
||||
justify="flex-start"
|
||||
align="flex-start"
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
marginLeft: 5,
|
||||
flexShrink: 0,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<AppliedFilters
|
||||
filters={filters}
|
||||
@@ -548,13 +657,14 @@ export function CustomReport() {
|
||||
'conditions',
|
||||
filters.filter(f => f !== deletedFilter),
|
||||
);
|
||||
onChangeAppliedFilter(deletedFilter, onDeleteFilter);
|
||||
onDeleteFilter(deletedFilter);
|
||||
onReportChange({ type: 'modify' });
|
||||
}}
|
||||
conditionsOp={conditionsOp}
|
||||
onCondOpChange={filter =>
|
||||
onChangeAppliedFilter(filter, onCondOpChange)
|
||||
}
|
||||
onUpdateChange={onReportChange}
|
||||
onCondOpChange={co => {
|
||||
onCondOpChange(co);
|
||||
onReportChange({ type: 'modify' });
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@@ -615,8 +725,6 @@ export function CustomReport() {
|
||||
balanceType={balanceType}
|
||||
groupBy={groupBy}
|
||||
interval={interval}
|
||||
showEmpty={showEmpty}
|
||||
scrollWidth={scrollWidth}
|
||||
setScrollWidth={setScrollWidth}
|
||||
viewLabels={viewLabels}
|
||||
compact={false}
|
||||
@@ -143,13 +143,13 @@ export function createCustomSpreadsheet({
|
||||
});
|
||||
}
|
||||
|
||||
const rangeProps =
|
||||
const intervals =
|
||||
interval === 'Weekly'
|
||||
? [startDate, endDate, firstDayOfWeekIdx]
|
||||
: [startDate, endDate];
|
||||
const intervals = monthUtils[ReportOptions.intervalRange.get(interval)](
|
||||
...rangeProps,
|
||||
);
|
||||
? monthUtils.weekRangeInclusive(startDate, endDate, firstDayOfWeekIdx)
|
||||
: monthUtils[ReportOptions.intervalRange.get(interval)](
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
|
||||
let totalAssets = 0;
|
||||
let totalDebts = 0;
|
||||
|
||||
@@ -92,13 +92,13 @@ export function createGroupedSpreadsheet({
|
||||
});
|
||||
}
|
||||
|
||||
const rangeProps =
|
||||
const intervals =
|
||||
interval === 'Weekly'
|
||||
? [startDate, endDate, firstDayOfWeekIdx]
|
||||
: [startDate, endDate];
|
||||
const intervals = monthUtils[ReportOptions.intervalRange.get(interval)](
|
||||
...rangeProps,
|
||||
);
|
||||
? monthUtils.weekRangeInclusive(startDate, endDate, firstDayOfWeekIdx)
|
||||
: monthUtils[ReportOptions.intervalRange.get(interval)](
|
||||
startDate,
|
||||
endDate,
|
||||
);
|
||||
|
||||
const groupedData: DataEntity[] = categoryGroup.map(
|
||||
group => {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// @ts-strict-ignore
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
export function useFilters<T>(initialFilters: T[] = []) {
|
||||
import { type RuleConditionEntity } from 'loot-core/types/models/rule';
|
||||
|
||||
export function useFilters<T extends RuleConditionEntity>(
|
||||
initialFilters: T[] = [],
|
||||
) {
|
||||
const [filters, setFilters] = useState<T[]>(initialFilters);
|
||||
const [conditionsOp, setConditionsOp] = useState<'and' | 'or'>('and');
|
||||
const [saved, setSaved] = useState<T[]>(null);
|
||||
|
||||
@@ -60,7 +60,7 @@ export interface SpendingEntity {
|
||||
export interface GroupedEntity {
|
||||
data?: DataEntity[];
|
||||
intervalData: DataEntity[];
|
||||
groupedData?: DataEntity[];
|
||||
groupedData?: DataEntity[] | null;
|
||||
legend?: LegendEntity[];
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
|
||||
6
upcoming-release-notes/2707.md
Normal file
6
upcoming-release-notes/2707.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
Custom reports: convert final jsx files to typescript.
|
||||
Reference in New Issue
Block a user