Skip to content

Commit

Permalink
Display rules with splits on rules page (#2368)
Browse files Browse the repository at this point in the history
* Show splits on rules page

* Add `ActionExpression` for 'allocate'-type actions

* Add release notes

* Fix type errors
  • Loading branch information
jfdoming authored Feb 21, 2024
1 parent 8568aeb commit 0d1e6f2
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 21 deletions.
9 changes: 3 additions & 6 deletions packages/desktop-client/src/components/modals/EditRule.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
makeValue,
FIELD_TYPES,
TYPE_INFO,
ALLOCATION_METHODS,
} from 'loot-core/src/shared/rules';
import {
integerToCurrency,
Expand Down Expand Up @@ -329,11 +330,7 @@ const parentOnlyFields = ['amount', 'cleared', 'account', 'date'];
const splitActionFields = actionFields.filter(
([field]) => !parentOnlyFields.includes(field),
);
const splitAmountTypes = [
['fixed-amount', 'a fixed amount'],
['fixed-percent', 'a fixed percentage'],
['remainder', 'an equal portion of the remainder'],
];
const allocationMethodOptions = Object.entries(ALLOCATION_METHODS);
function ActionEditor({ action, editorStyle, onChange, onDelete, onAdd }) {
const {
field,
Expand Down Expand Up @@ -379,7 +376,7 @@ function ActionEditor({ action, editorStyle, onChange, onDelete, onAdd }) {
</View>

<SplitAmountMethodSelect
options={splitAmountTypes}
options={allocationMethodOptions}
value={options.method}
onChange={onChange}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import React from 'react';

import { mapField, friendlyOp } from 'loot-core/src/shared/rules';
import {
mapField,
friendlyOp,
ALLOCATION_METHODS,
} from 'loot-core/src/shared/rules';
import {
type SetSplitAmountRuleActionEntity,
type LinkScheduleRuleActionEntity,
type RuleActionEntity,
type SetRuleActionEntity,
Expand Down Expand Up @@ -40,6 +45,8 @@ export function ActionExpression({ style, ...props }: ActionExpressionProps) {
>
{props.op === 'set' ? (
<SetActionExpression {...props} />
) : props.op === 'set-split-amount' ? (
<SetSplitAmountActionExpression {...props} />
) : props.op === 'link-schedule' ? (
<LinkScheduleActionExpression {...props} />
) : null}
Expand All @@ -63,6 +70,29 @@ function SetActionExpression({
);
}

function SetSplitAmountActionExpression({
op,
value,
options,
}: SetSplitAmountRuleActionEntity) {
const method = options?.method;
if (!method) {
return null;
}

return (
<>
<Text>{friendlyOp(op)}</Text>{' '}
<Text style={valueStyle}>{ALLOCATION_METHODS[method]}</Text>
{method !== 'remainder' && ': '}
{method === 'fixed-amount' && (
<Value style={valueStyle} value={value} field="amount" />
)}
{method === 'fixed-percent' && <Text style={valueStyle}>{value}%</Text>}
</>
);
}

function LinkScheduleActionExpression({
op,
value,
Expand Down
63 changes: 55 additions & 8 deletions packages/desktop-client/src/components/rules/RuleRow.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// @ts-strict-ignore
import React, { memo } from 'react';

import { v4 as uuid } from 'uuid';

import { friendlyOp } from 'loot-core/src/shared/rules';
import { type RuleEntity } from 'loot-core/src/types/models';

import { useSelectedDispatch } from '../../hooks/useSelected';
import { SvgRightArrow2 } from '../../icons/v0';
import { theme } from '../../style';
import { styles, theme } from '../../style';
import { Button } from '../common/Button';
import { Stack } from '../common/Stack';
import { Text } from '../common/Text';
Expand All @@ -30,6 +32,17 @@ export const RuleRow = memo(
const borderColor = selected ? theme.tableBorderSelected : 'none';
const backgroundFocus = hovered;

const actionSplits = rule.actions.reduce(
(acc, action) => {
const splitIndex = action['options']?.splitIndex ?? 0;
acc[splitIndex] = acc[splitIndex] ?? { id: uuid(), actions: [] };
acc[splitIndex].actions.push(action);
return acc;
},
[] as { id: string; actions: RuleEntity['actions'] }[],
);
const hasSplits = actionSplits.length > 1;

return (
<Row
height="auto"
Expand Down Expand Up @@ -103,13 +116,47 @@ export const RuleRow = memo(
style={{ flex: 1, alignItems: 'flex-start' }}
data-testid="actions"
>
{rule.actions.map((action, i) => (
<ActionExpression
key={i}
{...action}
style={i !== 0 && { marginTop: 3 }}
/>
))}
{hasSplits
? actionSplits.map((split, i) => (
<View
key={split.id}
style={{
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
marginTop: i > 0 ? 3 : 0,
padding: '5px',
borderColor: theme.tableBorder,
borderWidth: '1px',
borderRadius: '5px',
}}
>
<Text
style={{
...styles.verySmallText,
color: theme.pageTextLight,
marginBottom: 6,
}}
>
{i ? `Split ${i}` : 'Before split'}
</Text>
{split.actions.map((action, j) => (
<ActionExpression
key={j}
{...action}
style={j !== 0 && { marginTop: 3 }}
/>
))}
</View>
))
: rule.actions.map((action, i) => (
<ActionExpression
key={i}
{...action}
style={i !== 0 && { marginTop: 3 }}
/>
))}
</View>
</Stack>
</Field>
Expand Down
10 changes: 9 additions & 1 deletion packages/loot-core/src/server/accounts/transaction-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,15 @@ export async function applyActions(
}

try {
if (action.op === 'link-schedule') {
if (action.op === 'set-split-amount') {
return new Action(
action.op,
null,
action.value,
action.options,
FIELD_TYPES,
);
} else if (action.op === 'link-schedule') {
return new Action(action.op, null, action.value, null, FIELD_TYPES);
}

Expand Down
18 changes: 13 additions & 5 deletions packages/loot-core/src/server/rules/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,23 @@ function validateRule(rule: Partial<RuleEntity>) {
);

const actionErrors = runValidation(rule.actions, action =>
action.op === 'link-schedule'
? new Action(action.op, null, action.value, null, ruleFieldTypes)
: new Action(
action.op === 'set-split-amount'
? new Action(
action.op,
action.field,
null,
action.value,
action.options,
ruleFieldTypes,
),
)
: action.op === 'link-schedule'
? new Action(action.op, null, action.value, null, ruleFieldTypes)
: new Action(
action.op,
action.field,
action.value,
action.options,
ruleFieldTypes,
),
);

if (conditionErrors || actionErrors) {
Expand Down
8 changes: 8 additions & 0 deletions packages/loot-core/src/shared/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export const FIELD_TYPES = new Map(
}),
);

export const ALLOCATION_METHODS = {
'fixed-amount': 'a fixed amount',
'fixed-percent': 'a fixed percent',
remainder: 'an equal portion of the remainder',
};

export function mapField(field, opts?) {
opts = opts || {};

Expand Down Expand Up @@ -113,6 +119,8 @@ export function friendlyOp(op, type?) {
return 'is false';
case 'set':
return 'set';
case 'set-split-amount':
return 'allocate';
case 'link-schedule':
return 'link schedule';
case 'and':
Expand Down
1 change: 1 addition & 0 deletions packages/loot-core/src/types/models/rule.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface RuleConditionEntity {

export type RuleActionEntity =
| SetRuleActionEntity
| SetSplitAmountRuleActionEntity
| LinkScheduleRuleActionEntity;

export interface SetRuleActionEntity {
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/2368.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [jfdoming]
---

Show rules with splits on rules overview page

0 comments on commit 0d1e6f2

Please sign in to comment.