Skip to content

Commit

Permalink
Merge branch 'master' into add_unit_tests_for_goals
Browse files Browse the repository at this point in the history
  • Loading branch information
ACWalker authored Aug 4, 2024
2 parents 052b77c + 63d9547 commit 69fd40d
Show file tree
Hide file tree
Showing 29 changed files with 397 additions and 246 deletions.
8 changes: 8 additions & 0 deletions packages/api/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,11 @@ export function updateRule(rule) {
export function deleteRule(id) {
return send('api/rule-delete', { id });
}

export function holdBudgetForNextMonth(month, amount) {
return send('api/budget-hold-for-next-month', { month, amount });
}

export function resetBudgetHold(month) {
return send('api/budget-reset-hold', { month });
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function ModeButton({
backgroundColor: theme.menuBackground,
marginRight: 5,
fontSize: 'inherit',
...style,
...(selected && {
backgroundColor: theme.buttonPrimaryBackground,
color: theme.buttonPrimaryText,
Expand All @@ -32,7 +33,6 @@ export function ModeButton({
color: theme.buttonPrimaryTextHover,
},
}),
...style,
}}
onClick={onSelect}
>
Expand Down
37 changes: 24 additions & 13 deletions packages/desktop-client/src/components/reports/ReportSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type LocalPrefs } from 'loot-core/types/prefs';

import { styles } from '../../style/styles';
import { theme } from '../../style/theme';
import { Information } from '../alerts';
import { Button } from '../common/Button';
import { Menu } from '../common/Menu';
import { Popover } from '../common/Popover';
Expand All @@ -26,6 +27,7 @@ import { setSessionReport } from './setSessionReport';

type ReportSidebarProps = {
customReportItems: CustomReportEntity;
selectedCategories: CategoryEntity[];
categories: { list: CategoryEntity[]; grouped: CategoryGroupEntity[] };
dateRangeLine: number;
allIntervals: { name: string; pretty: string }[];
Expand Down Expand Up @@ -55,10 +57,12 @@ type ReportSidebarProps = {
defaultModeItems: (graph: string, item: string) => void;
earliestTransaction: string;
firstDayOfWeekIdx: LocalPrefs['firstDayOfWeekIdx'];
isComplexCategoryCondition?: boolean;
};

export function ReportSidebar({
customReportItems,
selectedCategories,
categories,
dateRangeLine,
allIntervals,
Expand All @@ -82,6 +86,7 @@ export function ReportSidebar({
defaultModeItems,
earliestTransaction,
firstDayOfWeekIdx,
isComplexCategoryCondition = false,
}: ReportSidebarProps) {
const [menuOpen, setMenuOpen] = useState(false);
const triggerRef = useRef(null);
Expand Down Expand Up @@ -536,19 +541,25 @@ export function ReportSidebar({
minHeight: 200,
}}
>
<CategorySelector
categoryGroups={categories.grouped.filter(f => {
return customReportItems.showHiddenCategories || !f.hidden
? true
: false;
})}
selectedCategories={customReportItems.selectedCategories || []}
setSelectedCategories={e => {
setSelectedCategories(e);
onReportChange({ type: 'modify' });
}}
showHiddenCategories={customReportItems.showHiddenCategories}
/>
{isComplexCategoryCondition ? (
<Information>
Remove active category filters to show the category selector.
</Information>
) : (
<CategorySelector
categoryGroups={categories.grouped.filter(f => {
return customReportItems.showHiddenCategories || !f.hidden
? true
: false;
})}
selectedCategories={selectedCategories || []}
setSelectedCategories={e => {
setSelectedCategories(e);
onReportChange({ type: 'modify' });
}}
showHiddenCategories={customReportItems.showHiddenCategories}
/>
)}
</View>
</View>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,13 @@ export function SpendingGraph({
data.intervalData && (
<ResponsiveContainer>
<div>
{!compact && <div style={{ marginTop: '15px' }} />}
{!compact && <div style={{ marginTop: '5px' }} />}
<AreaChart
width={width}
height={height}
data={data.intervalData}
margin={{
top: 10,
top: 0,
right: 0,
left: 0,
bottom: 0,
Expand Down
126 changes: 104 additions & 22 deletions packages/desktop-client/src/components/reports/reports/CustomReport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,49 @@ import { createGroupedSpreadsheet } from '../spreadsheets/grouped-spreadsheet';
import { useReport } from '../useReport';
import { fromDateRepr } from '../util';

/**
* Transform `selectedCategories` into `conditions`.
*/
function useSelectedCategories(
conditions: RuleConditionEntity[],
categories: CategoryEntity[],
): CategoryEntity[] {
const existingCategoryCondition = useMemo(
() => conditions.find(({ field }) => field === 'category'),
[conditions],
);

return useMemo(() => {
if (!existingCategoryCondition) {
return categories;
}

switch (existingCategoryCondition.op) {
case 'is':
return categories.filter(
({ id }) => id === existingCategoryCondition.value,
);

case 'isNot':
return categories.filter(
({ id }) => existingCategoryCondition.value !== id,
);

case 'oneOf':
return categories.filter(({ id }) =>
existingCategoryCondition.value.includes(id),
);

case 'notOneOf':
return categories.filter(
({ id }) => !existingCategoryCondition.value.includes(id),
);
}

return categories;
}, [existingCategoryCondition, categories]);
}

export function CustomReport() {
const categories = useCategories();
const { isNarrowWidth } = useResponsive();
Expand Down Expand Up @@ -102,9 +145,65 @@ export function CustomReport() {
}>
>([]);

const [selectedCategories, setSelectedCategories] = useState(
loadReport.selectedCategories,
);
// Complex category conditions are:
// - conditions with multiple "category" fields
// - conditions with "category" field that use "contains", "doesNotContain" or "matches" operations
const isComplexCategoryCondition =
!!conditions.find(
({ field, op }) =>
field === 'category' &&
['contains', 'doesNotContain', 'matches'].includes(op),
) || conditions.filter(({ field }) => field === 'category').length >= 2;

const setSelectedCategories = (newCategories: CategoryEntity[]) => {
const newCategoryIdSet = new Set(newCategories.map(({ id }) => id));
const allCategoryIds = categories.list.map(({ id }) => id);
const allCategoriesSelected = !allCategoryIds.find(
id => !newCategoryIdSet.has(id),
);
const newCondition = {
field: 'category',
op: 'oneOf',
value: newCategories.map(({ id }) => id),
type: 'id',
} satisfies RuleConditionEntity;

const existingCategoryCondition = conditions.find(
({ field }) => field === 'category',
);

// If the existing conditions already have one for "category" - replace it
if (existingCategoryCondition) {
// If we selected all categories - remove the filter (default state)
if (allCategoriesSelected) {
onDeleteFilter(existingCategoryCondition);
return;
}

// Update the "notOneOf" condition if it's already set
if (existingCategoryCondition.op === 'notOneOf') {
onUpdateFilter(existingCategoryCondition, {
...existingCategoryCondition,
value: allCategoryIds.filter(id => !newCategoryIdSet.has(id)),
});
return;
}

// Otherwise use `oneOf` condition
onUpdateFilter(existingCategoryCondition, newCondition);
return;
}

// Don't add a new filter if all categories are selected (default state)
if (allCategoriesSelected) {
return;
}

// If the existing conditions does not have a "category" - append a new one
onApplyFilter(newCondition);
};

const selectedCategories = useSelectedCategories(conditions, categories.list);
const [startDate, setStartDate] = useState(loadReport.startDate);
const [endDate, setEndDate] = useState(loadReport.endDate);
const [mode, setMode] = useState(loadReport.mode);
Expand Down Expand Up @@ -146,12 +245,6 @@ export function CustomReport() {
: loadReport.savedStatus ?? 'new',
);

useEffect(() => {
if (selectedCategories === undefined && categories.list.length !== 0) {
setSelectedCategories(categories.list);
}
}, [categories, selectedCategories]);

useEffect(() => {
async function run() {
onApplyFilter(null);
Expand Down Expand Up @@ -260,7 +353,6 @@ export function CustomReport() {
endDate,
interval,
categories,
selectedCategories,
conditions,
conditionsOp,
showEmpty,
Expand All @@ -276,7 +368,6 @@ export function CustomReport() {
interval,
balanceTypeOp,
categories,
selectedCategories,
conditions,
conditionsOp,
showEmpty,
Expand All @@ -293,7 +384,6 @@ export function CustomReport() {
endDate,
interval,
categories,
selectedCategories,
conditions,
conditionsOp,
showEmpty,
Expand All @@ -315,7 +405,6 @@ export function CustomReport() {
groupBy,
balanceTypeOp,
categories,
selectedCategories,
payees,
accounts,
conditions,
Expand Down Expand Up @@ -348,7 +437,6 @@ export function CustomReport() {
showHiddenCategories,
includeCurrentInterval,
showUncategorized,
selectedCategories,
graphType,
conditions,
conditionsOp,
Expand Down Expand Up @@ -471,13 +559,6 @@ export function CustomReport() {
};

const setReportData = (input: CustomReportEntity) => {
const selectAll: CategoryEntity[] = [];
categories.grouped.map(categoryGroup =>
(categoryGroup.categories || []).map(category =>
selectAll.push(category),
),
);

setStartDate(input.startDate);
setEndDate(input.endDate);
setIsDateStatic(input.isDateStatic);
Expand All @@ -491,7 +572,6 @@ export function CustomReport() {
setShowHiddenCategories(input.showHiddenCategories);
setIncludeCurrentInterval(input.includeCurrentInterval);
setShowUncategorized(input.showUncategorized);
setSelectedCategories(input.selectedCategories || selectAll);
setGraphType(input.graphType);
onApplyFilter(null);
(input.conditions || []).forEach(condition => onApplyFilter(condition));
Expand Down Expand Up @@ -578,6 +658,7 @@ export function CustomReport() {
{!isNarrowWidth && (
<ReportSidebar
customReportItems={customReportItems}
selectedCategories={selectedCategories}
categories={categories}
dateRangeLine={dateRangeLine}
allIntervals={allIntervals}
Expand All @@ -601,6 +682,7 @@ export function CustomReport() {
defaultModeItems={defaultModeItems}
earliestTransaction={earliestTransaction}
firstDayOfWeekIdx={firstDayOfWeekIdx}
isComplexCategoryCondition={isComplexCategoryCondition}
/>
)}
<View
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ export function GetCardData({
endDate,
interval: report.interval,
categories,
selectedCategories: report.selectedCategories ?? categories.list,
conditions: report.conditions ?? [],
conditionsOp: report.conditionsOp,
showEmpty: report.showEmpty,
Expand All @@ -131,7 +130,6 @@ export function GetCardData({
endDate,
interval: report.interval,
categories,
selectedCategories: report.selectedCategories ?? categories.list,
conditions: report.conditions ?? [],
conditionsOp: report.conditionsOp,
showEmpty: report.showEmpty,
Expand Down
Loading

0 comments on commit 69fd40d

Please sign in to comment.