Skip to content
Snippets Groups Projects
Unverified Commit 550ad931 authored by Jed Fox's avatar Jed Fox Committed by GitHub
Browse files

Add support for filtering the rules list (#475)

parent 799f123e
No related branches found
No related tags found
No related merge requests found
import React, { useState, useEffect, useRef, useCallback } from 'react';
import React, {
useState,
useEffect,
useRef,
useCallback,
useMemo
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { format as formatDate, parseISO } from 'date-fns';
......@@ -20,7 +26,8 @@ import {
Text,
Button,
Stack,
ExternalLink
ExternalLink,
Input
} from 'loot-design/src/components/common';
import {
SelectCell,
......@@ -222,6 +229,14 @@ export function ConditionExpression({
);
}
function describeSchedule(schedule, payee) {
if (payee) {
return `${payee.name} (${schedule.next_date})`;
} else {
return `Next: ${schedule.next_date}`;
}
}
function ScheduleValue({ value }) {
let payees = useSelector(state => state.queries.payees);
let byId = getPayeesById(payees);
......@@ -232,12 +247,7 @@ function ScheduleValue({ value }) {
value={value}
field="rule"
data={schedules}
describe={s => {
let payeeId = s._payee;
return byId[payeeId]
? `${byId[payeeId].name} (${s.next_date})`
: `Next: ${s.next_date}`;
}}
describe={schedule => describeSchedule(schedule, byId[schedule._payee])}
/>
);
}
......@@ -495,15 +505,80 @@ function RulesList({
);
}
export default function ManageRules({
isModal,
payeeId,
setLoading = () => {}
}) {
function mapValue(field, value, { payees, categories, accounts }) {
if (!value) return '';
if (field === 'payee') {
return payees.find(p => p.id === value).name;
} else if (field === 'category') {
return categories.find(c => c.id === value).name;
} else if (field === 'account') {
return accounts.find(a => a.id === value).name;
} else {
return value;
}
}
function ruleToString(rule, data) {
let conditions = rule.conditions.flatMap(cond => [
mapField(cond.field),
friendlyOp(cond.op),
cond.op === 'oneOf'
? cond.value.map(v => mapValue(cond.field, v, data)).join(', ')
: mapValue(cond.field, cond.value, data)
]);
let actions = rule.actions.flatMap(action => {
if (action.op === 'set') {
return [
friendlyOp(action.op),
mapField(action.field),
'to',
mapValue(action.field, action.value, data)
];
} else if (action.op === 'link-schedule') {
let schedule = data.schedules.find(s => s.id === action.value);
return [
friendlyOp(action.op),
describeSchedule(
schedule,
data.payees.find(p => p.id === schedule._payee)
)
];
} else {
return [];
}
});
return (
(rule.stage || '') + ' ' + conditions.join(' ') + ' ' + actions.join(' ')
);
}
function ManageRulesContent({ isModal, payeeId, setLoading }) {
let [allRules, setAllRules] = useState(null);
let [rules, setRules] = useState(null);
let [filter, setFilter] = useState('');
let dispatch = useDispatch();
let navigator = useTableNavigator(rules, ['select', 'edit']);
let { data: schedules } = SchedulesQuery.useQuery();
let filterData = useSelector(state => ({
payees: state.queries.payees,
categories: state.queries.categories.list,
accounts: state.queries.accounts,
schedules
}));
let filteredRules = useMemo(
() =>
filter === '' || !rules
? rules
: rules.filter(rule =>
ruleToString(rule, filterData)
.toLowerCase()
.includes(filter.toLowerCase())
),
[rules, filter, filterData]
);
let selectedInst = useSelected('manage-rules', allRules, []);
let [hoveredRule, setHoveredRule] = useState(null);
let tableRef = useRef(null);
......@@ -635,78 +710,113 @@ export default function ManageRules({
return null;
}
let actions = (
<View
style={{
flexDirection: 'row',
alignItems: 'center',
padding: isModal ? '13px 15px' : '0 0 15px',
borderTop: '1px solid ' + colors.border
}}
>
<View
style={{
color: colors.n4,
flexDirection: 'row',
alignItems: 'center',
width: '50%'
}}
>
<Text>
Rules are always run in the order that you see them.{' '}
<ExternalLink
asAnchor={true}
href="https://actualbudget.github.io/docs/Budgeting/rules/"
style={{ color: colors.n4 }}
return (
<SelectedProvider instance={selectedInst}>
<View style={{ overflow: 'hidden' }}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
padding: isModal ? '0 13px 15px' : '0 0 15px',
flexShrink: 0
}}
>
<View
style={{
color: colors.n4,
flexDirection: 'row',
alignItems: 'center',
width: '50%'
}}
>
Learn more
</ExternalLink>
</Text>
<Text>
Rules are always run in the order that you see them.{' '}
<ExternalLink
asAnchor={true}
href="https://actualbudget.github.io/docs/Budgeting/rules/"
style={{ color: colors.n4 }}
>
Learn more
</ExternalLink>
</Text>
</View>
<View style={{ flex: 1 }} />
<Input
placeholder="Filter rules..."
value={filter}
onChange={e => {
setFilter(e.target.value);
navigator.onEdit(null);
}}
style={{
width: 350,
borderColor: isModal ? null : 'transparent',
backgroundColor: isModal ? null : colors.n11,
':focus': isModal
? null
: {
backgroundColor: 'white',
'::placeholder': { color: colors.n8 }
}
}}
/>
</View>
<View style={{ flex: 1 }}>
<RulesHeader />
<SimpleTable
ref={tableRef}
data={filteredRules}
navigator={navigator}
loadMore={loadMore}
// Hide the last border of the item in the table
style={{ marginBottom: -1 }}
>
<RulesList
rules={filteredRules}
selectedItems={selectedInst.items}
navigator={navigator}
hoveredRule={hoveredRule}
onHover={onHover}
onEditRule={onEditRule}
/>
</SimpleTable>
</View>
<View
style={{
paddingBlock: 15,
paddingInline: isModal ? 13 : 0,
borderTop: isModal && '1px solid ' + colors.border,
flexShrink: 0
}}
>
<Stack direction="row" align="center" justify="flex-end" spacing={2}>
{selectedInst.items.size > 0 && (
<Button onClick={onDeleteSelected}>
Delete {selectedInst.items.size} rules
</Button>
)}
<Button primary onClick={onCreateRule}>
Create new rule
</Button>
</Stack>
</View>
</View>
<View style={{ flex: 1 }} />
<Stack direction="row" align="center" justify="flex-end" spacing={2}>
{selectedInst.items.size > 0 && (
<Button onClick={onDeleteSelected}>
Delete {selectedInst.items.size} rules
</Button>
)}
<Button primary onClick={onCreateRule}>
Create new rule
</Button>
</Stack>
</View>
</SelectedProvider>
);
}
export default function ManageRules({
isModal,
payeeId,
setLoading = () => {}
}) {
return (
<SchedulesQuery.Provider>
<SelectedProvider instance={selectedInst}>
<View style={{ overflow: 'hidden' }}>
{!isModal && actions}
<View style={{ flex: 1 }}>
<RulesHeader />
<SimpleTable
ref={tableRef}
data={rules}
navigator={navigator}
loadMore={loadMore}
// Hide the last border of the item in the table
style={{ marginBottom: -1 }}
>
<RulesList
rules={rules}
selectedItems={selectedInst.items}
navigator={navigator}
hoveredRule={hoveredRule}
onHover={onHover}
onEditRule={onEditRule}
/>
</SimpleTable>
</View>
{isModal && actions}
</View>
</SelectedProvider>
<ManageRulesContent
isModal={isModal}
payeeId={payeeId}
setLoading={setLoading}
/>
</SchedulesQuery.Provider>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment