Skip to content

Commit

Permalink
♻️ (typescript) make category and rule types stricter (#3180)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatissJanis authored Aug 3, 2024
1 parent 4389329 commit 14f2994
Show file tree
Hide file tree
Showing 17 changed files with 174 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ export function CoverMenu({
onClose,
}: CoverMenuProps) {
const { grouped: originalCategoryGroups } = useCategories();
let categoryGroups = originalCategoryGroups.filter(g => !g.is_income);
categoryGroups = showToBeBudgeted
? addToBeBudgetedGroup(categoryGroups)
: categoryGroups;
const filteredCategoryGroups = originalCategoryGroups.filter(
g => !g.is_income,
);
const categoryGroups = showToBeBudgeted
? addToBeBudgetedGroup(filteredCategoryGroups)
: filteredCategoryGroups;
const [categoryId, setCategoryId] = useState<string | null>(null);

function submit() {
Expand All @@ -39,7 +41,7 @@ export function CoverMenu({
{node => (
<CategoryAutocomplete
categoryGroups={categoryGroups}
value={categoryGroups.find(g => g.id === categoryId)}
value={categoryGroups.find(g => g.id === categoryId) ?? null}
openOnFocus={true}
onSelect={(id: string | undefined) => setCategoryId(id || null)}
inputProps={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ export function TransferMenu({
onClose,
}: TransferMenuProps) {
const { grouped: originalCategoryGroups } = useCategories();
let categoryGroups = originalCategoryGroups.filter(g => !g.is_income);
if (showToBeBudgeted) {
categoryGroups = addToBeBudgetedGroup(categoryGroups);
}
const filteredCategoryGroups = originalCategoryGroups.filter(
g => !g.is_income,
);
const categoryGroups = showToBeBudgeted
? addToBeBudgetedGroup(filteredCategoryGroups)
: filteredCategoryGroups;

const _initialAmount = integerToCurrency(Math.max(initialAmount, 0));
const [amount, setAmount] = useState<string | null>(null);
Expand Down Expand Up @@ -59,7 +61,7 @@ export function TransferMenu({

<CategoryAutocomplete
categoryGroups={categoryGroups}
value={categoryGroups.find(g => g.id === categoryId)}
value={categoryGroups.find(g => g.id === categoryId) ?? null}
openOnFocus={true}
onSelect={(id: string | undefined) => setCategoryId(id || null)}
inputProps={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ type AppliedFiltersProps = {
) => void;
onDelete: (filter: RuleConditionEntity) => void;
conditionsOp: string;
onConditionsOpChange: (
value: string,
conditions: RuleConditionEntity[],
) => void;
onConditionsOpChange: (value: 'and' | 'or') => void;
};

export function AppliedFilters({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function ConditionsOpMenu({
conditions,
}: {
conditionsOp: string;
onChange: (value: string, conditions: RuleConditionEntity[]) => void;
onChange: (value: 'and' | 'or') => void;
conditions: RuleConditionEntity[];
}) {
return conditions.length > 1 ? (
Expand All @@ -25,7 +25,7 @@ export function ConditionsOpMenu({
['or', 'any'],
]}
value={conditionsOp}
onChange={(name: string, value: string) => onChange(value, conditions)}
onChange={onChange}
/>
of:
</Text>
Expand Down
23 changes: 10 additions & 13 deletions packages/desktop-client/src/components/filters/FilterExpression.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import React, { useRef, useState } from 'react';

import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
import { integerToCurrency } from 'loot-core/src/shared/util';
import {
type RuleConditionOp,
type RuleConditionEntity,
} from 'loot-core/src/types/models';
import { type RuleConditionEntity } from 'loot-core/src/types/models';

import { SvgDelete } from '../../icons/v0';
import { type CSSProperties, theme } from '../../style';
Expand All @@ -20,18 +17,18 @@ import { subfieldFromFilter } from './subfieldFromFilter';

let isDatepickerClick = false;

type FilterExpressionProps = {
field: string | undefined;
customName: string | undefined;
op: RuleConditionOp | undefined;
value: string | string[] | number | boolean | undefined;
options: RuleConditionEntity['options'];
type FilterExpressionProps<T extends RuleConditionEntity> = {
field: T['field'];
customName: T['customName'];
op: T['op'];
value: T['value'];
options: T['options'];
style?: CSSProperties;
onChange: (cond: RuleConditionEntity) => void;
onChange: (cond: T) => void;
onDelete: () => void;
};

export function FilterExpression({
export function FilterExpression<T extends RuleConditionEntity>({
field: originalField,
customName,
op,
Expand All @@ -40,7 +37,7 @@ export function FilterExpression({
style,
onChange,
onDelete,
}: FilterExpressionProps) {
}: FilterExpressionProps<T>) {
const [editing, setEditing] = useState(false);
const triggerRef = useRef(null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function subfieldFromFilter({
field,
options,
value,
}: RuleConditionEntity) {
}: Pick<RuleConditionEntity, 'field' | 'options' | 'value'>) {
if (field === 'date') {
if (typeof value === 'string') {
if (value.length === 7) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { makeValue, FIELD_TYPES } from 'loot-core/src/shared/rules';
import { type RuleConditionEntity } from 'loot-core/src/types/models';

export function updateFilterReducer(
state: { field: string; value: string | string[] | number | boolean | null },
action: RuleConditionEntity,
state: Pick<RuleConditionEntity, 'op' | 'field' | 'value'>,
action: { type: 'set-op' | 'set-value' } & Pick<
RuleConditionEntity,
'op' | 'value'
>,
) {
switch (action.type) {
case 'set-op': {
Expand Down
10 changes: 6 additions & 4 deletions packages/desktop-client/src/components/modals/CoverModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ export function CoverModal({
}: CoverModalProps) {
const { grouped: originalCategoryGroups } = useCategories();
const [categoryGroups, categories] = useMemo(() => {
let expenseGroups = originalCategoryGroups.filter(g => !g.is_income);
expenseGroups = showToBeBudgeted
? addToBeBudgetedGroup(expenseGroups)
: expenseGroups;
const filteredCategoryGroups = originalCategoryGroups.filter(
g => !g.is_income,
);
const expenseGroups = showToBeBudgeted
? addToBeBudgetedGroup(filteredCategoryGroups)
: filteredCategoryGroups;
const expenseCategories = expenseGroups.flatMap(g => g.categories || []);
return [expenseGroups, expenseCategories];
}, [originalCategoryGroups, showToBeBudgeted]);
Expand Down
10 changes: 6 additions & 4 deletions packages/desktop-client/src/components/modals/TransferModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ export function TransferModal({
}: TransferModalProps) {
const { grouped: originalCategoryGroups } = useCategories();
const [categoryGroups, categories] = useMemo(() => {
let expenseGroups = originalCategoryGroups.filter(g => !g.is_income);
expenseGroups = showToBeBudgeted
? addToBeBudgetedGroup(expenseGroups)
: expenseGroups;
const filteredCategoryGroups = originalCategoryGroups.filter(
g => !g.is_income,
);
const expenseGroups = showToBeBudgeted
? addToBeBudgetedGroup(filteredCategoryGroups)
: filteredCategoryGroups;
const expenseCategories = expenseGroups.flatMap(g => g.categories || []);
return [expenseGroups, expenseCategories];
}, [originalCategoryGroups, showToBeBudgeted]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ type UncategorizedGroupEntity = Pick<

const uncategorizedGroup: UncategorizedGroupEntity = {
name: 'Uncategorized & Off Budget',
id: undefined,
id: 'uncategorized',
hidden: false,
categories: [uncategorizedCategory, transferCategory, offBudgetCategory],
};
Expand Down
23 changes: 10 additions & 13 deletions packages/desktop-client/src/hooks/useFilters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-strict-ignore
import { useCallback, useMemo, useState } from 'react';

import { type RuleConditionEntity } from 'loot-core/types/models/rule';
Expand All @@ -8,14 +7,19 @@ export function useFilters<T extends RuleConditionEntity>(
) {
const [conditions, setConditions] = useState<T[]>(initialConditions);
const [conditionsOp, setConditionsOp] = useState<'and' | 'or'>('and');
const [saved, setSaved] = useState<T[]>(null);
const [saved, setSaved] = useState<T[] | null>(null);

const onApply = useCallback(
conditionsOrSavedFilter => {
(
conditionsOrSavedFilter:
| null
| { conditions: T[]; conditionsOp: 'and' | 'or'; id: T[] | null }
| T,
) => {
if (conditionsOrSavedFilter === null) {
setConditions([]);
setSaved(null);
} else if (conditionsOrSavedFilter.conditions) {
} else if ('conditions' in conditionsOrSavedFilter) {
setConditions([...conditionsOrSavedFilter.conditions]);
setConditionsOp(conditionsOrSavedFilter.conditionsOp);
setSaved(conditionsOrSavedFilter.id);
Expand Down Expand Up @@ -45,13 +49,6 @@ export function useFilters<T extends RuleConditionEntity>(
[setConditions],
);

const onConditionsOpChange = useCallback(
condOp => {
setConditionsOp(condOp);
},
[setConditionsOp],
);

return useMemo(
() => ({
conditions,
Expand All @@ -60,15 +57,15 @@ export function useFilters<T extends RuleConditionEntity>(
onApply,
onUpdate,
onDelete,
onConditionsOpChange,
onConditionsOpChange: setConditionsOp,
}),
[
conditions,
saved,
onApply,
onUpdate,
onDelete,
onConditionsOpChange,
setConditionsOp,
conditionsOp,
],
);
Expand Down
23 changes: 18 additions & 5 deletions packages/loot-core/src/mocks/budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { q } from '../shared/query';
import type { Handlers } from '../types/handlers';
import type {
CategoryGroupEntity,
NewCategoryGroupEntity,
NewPayeeEntity,
NewTransactionEntity,
} from '../types/models';
Expand Down Expand Up @@ -618,7 +619,7 @@ export async function createTestBudget(handlers: Handlers) {
}),
);

const categoryGroups: Array<CategoryGroupEntity> = [
const newCategoryGroups: Array<NewCategoryGroupEntity> = [
{
name: 'Usual Expenses',
categories: [
Expand Down Expand Up @@ -652,19 +653,31 @@ export async function createTestBudget(handlers: Handlers) {
],
},
];
const categoryGroups: Array<CategoryGroupEntity> = [];

await runMutator(async () => {
for (const group of categoryGroups) {
group.id = await handlers['category-group-create']({
for (const group of newCategoryGroups) {
const groupId = await handlers['category-group-create']({
name: group.name,
isIncome: group.is_income,
});

categoryGroups.push({
...group,
id: groupId,
categories: [],
});

for (const category of group.categories) {
category.id = await handlers['category-create']({
const categoryId = await handlers['category-create']({
...category,
isIncome: category.is_income ? 1 : 0,
groupId: group.id,
groupId,
});

categoryGroups[categoryGroups.length - 1].categories.push({
...category,
id: categoryId,
});
}
}
Expand Down
8 changes: 6 additions & 2 deletions packages/loot-core/src/types/models/category-group.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { CategoryEntity } from './category';

export interface CategoryGroupEntity {
id?: string;
export interface NewCategoryGroupEntity {
name: string;
is_income?: boolean;
sort_order?: number;
tombstone?: boolean;
hidden?: boolean;
categories?: Omit<CategoryEntity, 'id'>[];
}

export interface CategoryGroupEntity extends NewCategoryGroupEntity {
id: string;
categories?: CategoryEntity[];
}
2 changes: 1 addition & 1 deletion packages/loot-core/src/types/models/category.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface CategoryEntity {
id?: string;
id: string;
name: string;
is_income?: boolean;
cat_group?: string;
Expand Down
4 changes: 2 additions & 2 deletions packages/loot-core/src/types/models/reports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface CustomReportEntity {
selectedCategories?: CategoryEntity[];
graphType: string;
conditions?: RuleConditionEntity[];
conditionsOp: string;
conditionsOp: 'and' | 'or';
data?: GroupedEntity;
tombstone?: boolean;
}
Expand Down Expand Up @@ -143,7 +143,7 @@ export interface CustomReportData {
selected_categories?: CategoryEntity[];
graph_type: string;
conditions?: RuleConditionEntity[];
conditions_op: string;
conditions_op: 'and' | 'or';
metadata?: GroupedEntity;
interval: string;
color_scheme?: string;
Expand Down
Loading

0 comments on commit 14f2994

Please sign in to comment.