mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 12:43:09 -05:00
Convert FiltersMenu to Typescript (part 1) (#2231)
* migration work * notes * typecheck * typecheck fixes * fixes * merge fixes * typecheck updates * review fixes
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
import { type RuleConditionEntity } from 'loot-core/types/models';
|
||||
|
||||
import { View } from '../common/View';
|
||||
|
||||
import { FilterExpression } from './FilterExpression';
|
||||
import { CondOpMenu } from './SavedFilters';
|
||||
|
||||
type AppliedFiltersProps = {
|
||||
filters: RuleConditionEntity[];
|
||||
onUpdate: (
|
||||
filter: RuleConditionEntity,
|
||||
newFilter: RuleConditionEntity,
|
||||
) => RuleConditionEntity;
|
||||
onDelete: (filter: RuleConditionEntity) => void;
|
||||
conditionsOp: string;
|
||||
onCondOpChange: () => void;
|
||||
};
|
||||
|
||||
export function AppliedFilters({
|
||||
filters,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
conditionsOp,
|
||||
onCondOpChange,
|
||||
}: AppliedFiltersProps) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<CondOpMenu
|
||||
conditionsOp={conditionsOp}
|
||||
onCondOpChange={onCondOpChange}
|
||||
filters={filters}
|
||||
/>
|
||||
{filters.map((filter: RuleConditionEntity, i: number) => (
|
||||
<FilterExpression
|
||||
key={i}
|
||||
customName={filter.customName}
|
||||
field={filter.field}
|
||||
op={filter.op}
|
||||
value={filter.value}
|
||||
options={filter.options}
|
||||
onChange={newFilter => onUpdate(filter, newFilter)}
|
||||
onDelete={() => onDelete(filter)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
// @ts-strict-ignore
|
||||
import React from 'react';
|
||||
|
||||
import { SvgFilter } from '../../icons/v1';
|
||||
import { Button } from '../common/Button';
|
||||
|
||||
type CompactFiltersButtonProps = {
|
||||
onClick: (newValue) => void;
|
||||
};
|
||||
|
||||
export function CompactFiltersButton({ onClick }: CompactFiltersButtonProps) {
|
||||
export function CompactFiltersButton({ onClick }: { onClick: () => void }) {
|
||||
return (
|
||||
<Button type="bare" onClick={onClick}>
|
||||
<SvgFilter width={15} height={15} />
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import React, { 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 { SvgDelete } from '../../icons/v0';
|
||||
import { type CSSProperties, theme } from '../../style';
|
||||
import { Button } from '../common/Button';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { Value } from '../rules/Value';
|
||||
|
||||
import { FilterEditor } from './FiltersMenu';
|
||||
import { subfieldFromFilter } from './subfieldFromFilter';
|
||||
|
||||
type FilterExpressionProps = {
|
||||
field: string | undefined;
|
||||
customName: string | undefined;
|
||||
op: RuleConditionOp | undefined;
|
||||
value: string | string[] | number | boolean | undefined;
|
||||
options: RuleConditionEntity['options'];
|
||||
style?: CSSProperties;
|
||||
onChange: (cond: RuleConditionEntity) => RuleConditionEntity;
|
||||
onDelete: () => void;
|
||||
};
|
||||
|
||||
export function FilterExpression({
|
||||
field: originalField,
|
||||
customName,
|
||||
op,
|
||||
value,
|
||||
options,
|
||||
style,
|
||||
onChange,
|
||||
onDelete,
|
||||
}: FilterExpressionProps) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const field = subfieldFromFilter({ field: originalField, value });
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.pillBackground,
|
||||
borderRadius: 4,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginRight: 10,
|
||||
marginTop: 10,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
disabled={customName != null}
|
||||
onClick={() => setEditing(true)}
|
||||
style={{ marginRight: -7 }}
|
||||
>
|
||||
<div style={{ paddingBlock: 1, paddingLeft: 5, paddingRight: 2 }}>
|
||||
{customName ? (
|
||||
<Text style={{ color: theme.pageTextPositive }}>{customName}</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text style={{ color: theme.pageTextPositive }}>
|
||||
{mapField(field, options)}
|
||||
</Text>{' '}
|
||||
<Text>{friendlyOp(op, null)}</Text>{' '}
|
||||
<Value
|
||||
value={value}
|
||||
field={field}
|
||||
inline={true}
|
||||
valueIsRaw={op === 'contains' || op === 'doesNotContain'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="bare" onClick={onDelete} aria-label="Delete filter">
|
||||
<SvgDelete
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: 5,
|
||||
marginLeft: 3,
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
{editing && (
|
||||
<FilterEditor
|
||||
field={originalField}
|
||||
op={op}
|
||||
value={
|
||||
field === 'amount' && typeof value === 'number'
|
||||
? integerToCurrency(value)
|
||||
: value
|
||||
}
|
||||
options={options}
|
||||
onSave={onChange}
|
||||
onClose={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
// @ts-strict-ignore
|
||||
import React from 'react';
|
||||
|
||||
import { SvgSettingsSliderAlternate } from '../../icons/v2';
|
||||
import { Button } from '../common/Button';
|
||||
|
||||
type FiltersButtonProps = {
|
||||
onClick: (newValue) => void;
|
||||
};
|
||||
|
||||
export function FiltersButton({ onClick }: FiltersButtonProps) {
|
||||
export function FiltersButton({ onClick }: { onClick: () => void }) {
|
||||
return (
|
||||
<Button type="bare" onClick={onClick} title="Filters">
|
||||
<SvgSettingsSliderAlternate
|
||||
|
||||
@@ -13,17 +13,14 @@ import { send } from 'loot-core/src/platform/client/fetch';
|
||||
import { getMonthYearFormat } from 'loot-core/src/shared/months';
|
||||
import {
|
||||
mapField,
|
||||
friendlyOp,
|
||||
deserializeField,
|
||||
getFieldError,
|
||||
unparse,
|
||||
makeValue,
|
||||
FIELD_TYPES,
|
||||
TYPE_INFO,
|
||||
} from 'loot-core/src/shared/rules';
|
||||
import { titleFirst, integerToCurrency } from 'loot-core/src/shared/util';
|
||||
import { titleFirst } from 'loot-core/src/shared/util';
|
||||
|
||||
import { SvgDelete } from '../../icons/v0';
|
||||
import { theme } from '../../style';
|
||||
import { Button } from '../common/Button';
|
||||
import { HoverTarget } from '../common/HoverTarget';
|
||||
@@ -32,13 +29,15 @@ import { Select } from '../common/Select';
|
||||
import { Stack } from '../common/Stack';
|
||||
import { Text } from '../common/Text';
|
||||
import { View } from '../common/View';
|
||||
import { Value } from '../rules/Value';
|
||||
import { Tooltip } from '../tooltips';
|
||||
import { GenericInput } from '../util/GenericInput';
|
||||
|
||||
import { CompactFiltersButton } from './CompactFiltersButton';
|
||||
import { FiltersButton } from './FiltersButton';
|
||||
import { CondOpMenu } from './SavedFilters';
|
||||
import { OpButton } from './OpButton';
|
||||
import { subfieldFromFilter } from './subfieldFromFilter';
|
||||
import { subfieldToOptions } from './subfieldToOptions';
|
||||
import { updateFilterReducer } from './updateFilterReducer';
|
||||
|
||||
const filterFields = [
|
||||
'date',
|
||||
@@ -52,100 +51,6 @@ const filterFields = [
|
||||
'saved',
|
||||
].map(field => [field, mapField(field)]);
|
||||
|
||||
function subfieldFromFilter({ field, options, value }) {
|
||||
if (field === 'date') {
|
||||
if (value.length === 7) {
|
||||
return 'month';
|
||||
} else if (value.length === 4) {
|
||||
return 'year';
|
||||
}
|
||||
} else if (field === 'amount') {
|
||||
if (options && options.inflow) {
|
||||
return 'amount-inflow';
|
||||
} else if (options && options.outflow) {
|
||||
return 'amount-outflow';
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
function subfieldToOptions(field, subfield) {
|
||||
switch (field) {
|
||||
case 'amount':
|
||||
switch (subfield) {
|
||||
case 'amount-inflow':
|
||||
return { inflow: true };
|
||||
case 'amount-outflow':
|
||||
return { outflow: true };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
case 'date':
|
||||
switch (subfield) {
|
||||
case 'month':
|
||||
return { month: true };
|
||||
case 'year':
|
||||
return { year: true };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function OpButton({ op, selected, style, onClick }) {
|
||||
return (
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
backgroundColor: theme.pillBackground,
|
||||
marginBottom: 5,
|
||||
...style,
|
||||
...(selected && {
|
||||
color: theme.buttonNormalSelectedText,
|
||||
'&,:hover,:active': {
|
||||
backgroundColor: theme.buttonNormalSelectedBackground,
|
||||
color: theme.buttonNormalSelectedText,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{friendlyOp(op)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function updateFilterReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'set-op': {
|
||||
const type = FIELD_TYPES.get(state.field);
|
||||
let value = state.value;
|
||||
if (
|
||||
(type === 'id' || type === 'string') &&
|
||||
(action.op === 'contains' ||
|
||||
action.op === 'is' ||
|
||||
action.op === 'doesNotContain' ||
|
||||
action.op === 'isNot')
|
||||
) {
|
||||
// Clear out the value if switching between contains or
|
||||
// is/oneof for the id or string type
|
||||
value = null;
|
||||
}
|
||||
return { ...state, op: action.op, value };
|
||||
}
|
||||
case 'set-value': {
|
||||
const { value } = makeValue(action.value, {
|
||||
type: FIELD_TYPES.get(state.field),
|
||||
});
|
||||
return { ...state, value };
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function ConfigureField({
|
||||
field,
|
||||
initialSubfield = field,
|
||||
@@ -478,7 +383,7 @@ export function FilterButton({ onApply, compact, hover }) {
|
||||
);
|
||||
}
|
||||
|
||||
function FilterEditor({ field, op, value, options, onSave, onClose }) {
|
||||
export function FilterEditor({ field, op, value, options, onSave, onClose }) {
|
||||
const [state, dispatch] = useReducer(
|
||||
(state, action) => {
|
||||
switch (action.type) {
|
||||
@@ -508,119 +413,3 @@ function FilterEditor({ field, op, value, options, onSave, onClose }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FilterExpression({
|
||||
field: originalField,
|
||||
customName,
|
||||
op,
|
||||
value,
|
||||
options,
|
||||
stage,
|
||||
style,
|
||||
onChange,
|
||||
onDelete,
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const field = subfieldFromFilter({ field: originalField, value });
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: theme.pillBackground,
|
||||
borderRadius: 4,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginRight: 10,
|
||||
marginTop: 10,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
disabled={customName != null}
|
||||
onClick={() => setEditing(true)}
|
||||
style={{ marginRight: -7 }}
|
||||
>
|
||||
<div style={{ paddingBlock: 1, paddingLeft: 5, paddingRight: 2 }}>
|
||||
{customName ? (
|
||||
<Text style={{ color: theme.pageTextPositive }}>{customName}</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text style={{ color: theme.pageTextPositive }}>
|
||||
{mapField(field, options)}
|
||||
</Text>{' '}
|
||||
<Text>{friendlyOp(op, null)}</Text>{' '}
|
||||
<Value
|
||||
value={value}
|
||||
field={field}
|
||||
inline={true}
|
||||
valueIsRaw={op === 'contains' || op === 'doesNotContain'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
<Button type="bare" onClick={onDelete} aria-label="Delete filter">
|
||||
<SvgDelete
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
margin: 5,
|
||||
marginLeft: 3,
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
{editing && (
|
||||
<FilterEditor
|
||||
field={originalField}
|
||||
customName={customName}
|
||||
op={op}
|
||||
value={field === 'amount' ? integerToCurrency(value) : value}
|
||||
options={options}
|
||||
stage={stage}
|
||||
onSave={onChange}
|
||||
onClose={() => setEditing(false)}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export function AppliedFilters({
|
||||
filters,
|
||||
editingFilter,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
conditionsOp,
|
||||
onCondOpChange,
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
flexWrap: 'wrap',
|
||||
}}
|
||||
>
|
||||
<CondOpMenu
|
||||
conditionsOp={conditionsOp}
|
||||
onCondOpChange={onCondOpChange}
|
||||
filters={filters}
|
||||
/>
|
||||
{filters.map((filter, i) => (
|
||||
<FilterExpression
|
||||
key={i}
|
||||
customName={filter.customName}
|
||||
field={filter.field}
|
||||
op={filter.op}
|
||||
value={filter.value}
|
||||
options={filter.options}
|
||||
editing={editingFilter === filter}
|
||||
onChange={newFilter => onUpdate(filter, newFilter)}
|
||||
onDelete={() => onDelete(filter)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
36
packages/desktop-client/src/components/filters/OpButton.tsx
Normal file
36
packages/desktop-client/src/components/filters/OpButton.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
import { friendlyOp } from 'loot-core/src/shared/rules';
|
||||
|
||||
import { type CSSProperties, theme } from '../../style';
|
||||
import { Button } from '../common/Button';
|
||||
|
||||
type OpButtonProps = {
|
||||
op: string;
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
export function OpButton({ op, selected, style, onClick }: OpButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
backgroundColor: theme.pillBackground,
|
||||
marginBottom: 5,
|
||||
...style,
|
||||
...(selected && {
|
||||
color: theme.buttonNormalSelectedText,
|
||||
'&,:hover,:active': {
|
||||
backgroundColor: theme.buttonNormalSelectedBackground,
|
||||
color: theme.buttonNormalSelectedText,
|
||||
},
|
||||
}),
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
{friendlyOp(op)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { FormField, FormLabel } from '../forms';
|
||||
import { FieldSelect } from '../modals/EditRule';
|
||||
import { GenericInput } from '../util/GenericInput';
|
||||
|
||||
import { AppliedFilters } from './FiltersMenu';
|
||||
import { AppliedFilters } from './AppliedFilters';
|
||||
|
||||
function FilterMenu({ onClose, filterId, onFilterMenuSelect }) {
|
||||
return (
|
||||
@@ -285,21 +285,21 @@ function SavedFilterMenuButton({
|
||||
}
|
||||
|
||||
export function CondOpMenu({ conditionsOp, onCondOpChange, filters }) {
|
||||
return (
|
||||
filters.length > 1 && (
|
||||
<Text style={{ color: theme.pageText, marginTop: 11, marginRight: 5 }}>
|
||||
<FieldSelect
|
||||
style={{ display: 'inline-flex' }}
|
||||
fields={[
|
||||
['and', 'all'],
|
||||
['or', 'any'],
|
||||
]}
|
||||
value={conditionsOp}
|
||||
onChange={(name, value) => onCondOpChange(value, filters)}
|
||||
/>
|
||||
of:
|
||||
</Text>
|
||||
)
|
||||
return filters.length > 1 ? (
|
||||
<Text style={{ color: theme.pageText, marginTop: 11, marginRight: 5 }}>
|
||||
<FieldSelect
|
||||
style={{ display: 'inline-flex' }}
|
||||
fields={[
|
||||
['and', 'all'],
|
||||
['or', 'any'],
|
||||
]}
|
||||
value={conditionsOp}
|
||||
onChange={(name, value) => onCondOpChange(value, filters)}
|
||||
/>
|
||||
of:
|
||||
</Text>
|
||||
) : (
|
||||
<View />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { type RuleConditionEntity } from 'loot-core/src/types/models';
|
||||
|
||||
export function subfieldFromFilter({
|
||||
field,
|
||||
options,
|
||||
value,
|
||||
}: RuleConditionEntity) {
|
||||
if (field === 'date') {
|
||||
if (typeof value === 'string') {
|
||||
if (value.length === 7) {
|
||||
return 'month';
|
||||
} else if (value.length === 4) {
|
||||
return 'year';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (field === 'amount') {
|
||||
if (options && options.inflow) {
|
||||
return 'amount-inflow';
|
||||
} else if (options && options.outflow) {
|
||||
return 'amount-outflow';
|
||||
}
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { type RuleConditionEntity } from 'loot-core/src/types/models';
|
||||
|
||||
export function subfieldToOptions(field: string, subfield: string) {
|
||||
let setOptions: RuleConditionEntity['options'];
|
||||
switch (field) {
|
||||
case 'amount':
|
||||
switch (subfield) {
|
||||
case 'amount-inflow':
|
||||
setOptions = { inflow: true };
|
||||
break;
|
||||
case 'amount-outflow':
|
||||
setOptions = { outflow: true };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
switch (subfield) {
|
||||
case 'month':
|
||||
setOptions = { month: true };
|
||||
break;
|
||||
case 'year':
|
||||
setOptions = { year: true };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return setOptions;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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,
|
||||
) {
|
||||
switch (action.type) {
|
||||
case 'set-op': {
|
||||
const type = FIELD_TYPES.get(state.field);
|
||||
let value = state.value;
|
||||
if (
|
||||
(type === 'id' || type === 'string') &&
|
||||
(action.op === 'contains' ||
|
||||
action.op === 'is' ||
|
||||
action.op === 'doesNotContain' ||
|
||||
action.op === 'isNot')
|
||||
) {
|
||||
// Clear out the value if switching between contains or
|
||||
// is/oneof for the id or string type
|
||||
value = null;
|
||||
}
|
||||
return { ...state, op: action.op, value };
|
||||
}
|
||||
case 'set-value': {
|
||||
const { value } = makeValue(action.value, {
|
||||
type: FIELD_TYPES.get(state.field),
|
||||
});
|
||||
return { ...state, value };
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,8 @@ import { Button } from '../common/Button';
|
||||
import { ButtonLink } from '../common/ButtonLink';
|
||||
import { Select } from '../common/Select';
|
||||
import { View } from '../common/View';
|
||||
import { FilterButton, AppliedFilters } from '../filters/FiltersMenu';
|
||||
import { AppliedFilters } from '../filters/AppliedFilters';
|
||||
import { FilterButton } from '../filters/FiltersMenu';
|
||||
|
||||
export function validateStart(allMonths, start, end) {
|
||||
const earliest = allMonths[allMonths.length - 1].name;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { AlignedText } from '../../common/AlignedText';
|
||||
import { Block } from '../../common/Block';
|
||||
import { Text } from '../../common/Text';
|
||||
import { View } from '../../common/View';
|
||||
import { AppliedFilters } from '../../filters/FiltersMenu';
|
||||
import { AppliedFilters } from '../../filters/AppliedFilters';
|
||||
import { PrivacyFilter } from '../../PrivacyFilter';
|
||||
import { ChooseGraph } from '../ChooseGraph';
|
||||
import { Header } from '../Header';
|
||||
|
||||
41
packages/loot-core/src/types/models/rule.d.ts
vendored
41
packages/loot-core/src/types/models/rule.d.ts
vendored
@@ -9,24 +9,31 @@ export interface RuleEntity {
|
||||
tombstone?: boolean;
|
||||
}
|
||||
|
||||
export type RuleConditionOp =
|
||||
| 'is'
|
||||
| 'isNot'
|
||||
| 'oneOf'
|
||||
| 'notOneOf'
|
||||
| 'isapprox'
|
||||
| 'isbetween'
|
||||
| 'gt'
|
||||
| 'gte'
|
||||
| 'lt'
|
||||
| 'lte'
|
||||
| 'contains'
|
||||
| 'doesNotContain';
|
||||
|
||||
export interface RuleConditionEntity {
|
||||
field: unknown;
|
||||
op:
|
||||
| 'is'
|
||||
| 'isNot'
|
||||
| 'oneOf'
|
||||
| 'notOneOf'
|
||||
| 'isapprox'
|
||||
| 'isbetween'
|
||||
| 'gt'
|
||||
| 'gte'
|
||||
| 'lt'
|
||||
| 'lte'
|
||||
| 'contains'
|
||||
| 'doesNotContain';
|
||||
value: unknown;
|
||||
options?: unknown;
|
||||
conditionsOp?: unknown;
|
||||
field?: string;
|
||||
op?: RuleConditionOp;
|
||||
value?: string | string[] | number | boolean;
|
||||
options?: {
|
||||
inflow?: boolean;
|
||||
outflow?: boolean;
|
||||
month?: boolean;
|
||||
year?: boolean;
|
||||
};
|
||||
conditionsOp?: string;
|
||||
type?: string;
|
||||
customName?: string;
|
||||
}
|
||||
|
||||
6
upcoming-release-notes/2231.md
Normal file
6
upcoming-release-notes/2231.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [carkom]
|
||||
---
|
||||
|
||||
Split out mega-file FiltersMenu.jsx into separate elements and converted them all to Typescript.
|
||||
Reference in New Issue
Block a user