From b117e7346434bfc8edbfa92f5db45f63c57bab08 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:58:42 -0400 Subject: [PATCH] feat: radio and select fields are filterable by options (#3136) --- .../WhereBuilder/Condition/Select/index.tsx | 81 +++++++++++++++++++ .../WhereBuilder/Condition/Select/types.ts | 9 +++ .../elements/WhereBuilder/Condition/index.tsx | 13 ++- .../elements/WhereBuilder/field-types.tsx | 6 +- 4 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/admin/components/elements/WhereBuilder/Condition/Select/index.tsx create mode 100644 src/admin/components/elements/WhereBuilder/Condition/Select/types.ts diff --git a/src/admin/components/elements/WhereBuilder/Condition/Select/index.tsx b/src/admin/components/elements/WhereBuilder/Condition/Select/index.tsx new file mode 100644 index 0000000000..65442ac49d --- /dev/null +++ b/src/admin/components/elements/WhereBuilder/Condition/Select/index.tsx @@ -0,0 +1,81 @@ +import React from 'react'; + +import { useTranslation } from 'react-i18next'; +import ReactSelect from '../../../ReactSelect'; +import { getTranslation } from '../../../../../../utilities/getTranslation'; +import { Props } from './types'; +import { Option, OptionObject } from '../../../../../../fields/config/types'; + +const formatOptions = (options: Option[]): OptionObject[] => options.map((option) => { + if (typeof option === 'object' && (option.value || option.value === '')) { + return option; + } + + return { + label: option, + value: option, + } as OptionObject; +}); + +export const Select: React.FC = ({ onChange, value, options: optionsFromProps, operator }) => { + const { i18n } = useTranslation(); + const [options, setOptions] = React.useState(formatOptions(optionsFromProps)); + + const isMulti = ['in', 'not_in'].includes(operator); + let valueToRender; + + if (isMulti && Array.isArray(value)) { + valueToRender = value.map((val) => { + const matchingOption = options.find((option) => option.value === val); + return { + label: matchingOption ? getTranslation(matchingOption.label, i18n) : val, + value: matchingOption?.value ?? val, + }; + }); + } else if (value) { + const matchingOption = options.find((option) => option.value === value); + valueToRender = { + label: matchingOption ? getTranslation(matchingOption.label, i18n) : value, + value: matchingOption?.value ?? value, + }; + } + + const onSelect = React.useCallback((selectedOption) => { + let newValue; + if (!selectedOption) { + newValue = null; + } else if (isMulti) { + if (Array.isArray(selectedOption)) { + newValue = selectedOption.map((option) => option.value); + } else { + newValue = []; + } + } else { + newValue = selectedOption.value; + } + + onChange(newValue); + }, [ + isMulti, + onChange, + ]); + + React.useEffect(() => { + setOptions(formatOptions(optionsFromProps)); + }, [optionsFromProps]); + + React.useEffect(() => { + if (!isMulti && Array.isArray(value)) { + onChange(value[0]); + } + }, [isMulti, onChange, value]); + + return ( + ({ ...option, label: getTranslation(option.label, i18n) }))} + isMulti={isMulti} + /> + ); +}; diff --git a/src/admin/components/elements/WhereBuilder/Condition/Select/types.ts b/src/admin/components/elements/WhereBuilder/Condition/Select/types.ts new file mode 100644 index 0000000000..a619645f90 --- /dev/null +++ b/src/admin/components/elements/WhereBuilder/Condition/Select/types.ts @@ -0,0 +1,9 @@ +import { Operator } from '../../../../../../types'; +import { Option } from '../../../../../../fields/config/types'; + +export type Props = { + onChange: (val: string) => void, + value: string, + options: Option[] + operator: Operator +} diff --git a/src/admin/components/elements/WhereBuilder/Condition/index.tsx b/src/admin/components/elements/WhereBuilder/Condition/index.tsx index c98fe84280..d295c849af 100644 --- a/src/admin/components/elements/WhereBuilder/Condition/index.tsx +++ b/src/admin/components/elements/WhereBuilder/Condition/index.tsx @@ -7,6 +7,7 @@ import Date from './Date'; import Number from './Number'; import Text from './Text'; import Relationship from './Relationship'; +import { Select } from './Select'; import useDebounce from '../../../../hooks/useDebounce'; import { FieldCondition } from '../types'; @@ -17,6 +18,7 @@ const valueFields = { Number, Text, Relationship, + Select, }; const baseClass = 'condition'; @@ -56,7 +58,15 @@ const Condition: React.FC = (props) => { }); }, [debouncedValue, dispatch, orIndex, andIndex]); - const ValueComponent = valueFields[activeField?.component] || valueFields.Text; + const booleanSelect = ['exists'].includes(operatorValue) || activeField.props.type === 'checkbox'; + const ValueComponent = booleanSelect ? Select : (valueFields[activeField?.component] || valueFields.Text); + + let selectOptions; + if (booleanSelect) { + selectOptions = ['true', 'false']; + } else if ('options' in activeField?.props) { + selectOptions = activeField.props.options; + } return (
@@ -95,6 +105,7 @@ const Condition: React.FC = (props) => { DefaultComponent={ValueComponent} componentProps={{ ...activeField?.props, + options: selectOptions, operator: operatorValue, value: internalValue, onChange: setInternalValue, diff --git a/src/admin/components/elements/WhereBuilder/field-types.tsx b/src/admin/components/elements/WhereBuilder/field-types.tsx index 08dada4813..9cfb1cd1a4 100644 --- a/src/admin/components/elements/WhereBuilder/field-types.tsx +++ b/src/admin/components/elements/WhereBuilder/field-types.tsx @@ -112,8 +112,12 @@ const fieldTypeConditions = { component: 'Relationship', operators: [...base], }, + radio: { + component: 'Select', + operators: [...base], + }, select: { - component: 'Text', + component: 'Select', operators: [...base], }, checkbox: {