fix(ui): adds multi select inputs for text fields in where builder (#12054)
### What? The `in` & `not_in` operators were not properly working for `text` fields as this operator requires an array of values for it's input. ### How? Conditionally renders a multi select input for `text` fields when filtering by `in` & `not_in` operators.
This commit is contained in:
@@ -96,7 +96,7 @@ export const DefaultFilter: React.FC<Props> = ({
|
||||
field={internalField?.field as TextFieldClient}
|
||||
onChange={onChange}
|
||||
operator={operator}
|
||||
value={value as string}
|
||||
value={value as string | string[]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,77 @@ import React from 'react'
|
||||
import type { TextFilterProps as Props } from './types.js'
|
||||
|
||||
import { useTranslation } from '../../../../providers/Translation/index.js'
|
||||
import { ReactSelect } from '../../../ReactSelect/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'condition-value-text'
|
||||
|
||||
export const Text: React.FC<Props> = ({ disabled, onChange, value }) => {
|
||||
export const Text: React.FC<Props> = (props) => {
|
||||
const {
|
||||
disabled,
|
||||
field: { hasMany },
|
||||
onChange,
|
||||
operator,
|
||||
value,
|
||||
} = props
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
const isMulti = ['in', 'not_in'].includes(operator) || hasMany
|
||||
|
||||
const [valueToRender, setValueToRender] = React.useState<
|
||||
{ id: string; label: string; value: { value: string } }[]
|
||||
>([])
|
||||
|
||||
const onSelect = React.useCallback(
|
||||
(selectedOption) => {
|
||||
let newValue
|
||||
if (!selectedOption) {
|
||||
newValue = []
|
||||
} else if (isMulti) {
|
||||
if (Array.isArray(selectedOption)) {
|
||||
newValue = selectedOption.map((option) => option.value?.value || option.value)
|
||||
} else {
|
||||
newValue = [selectedOption.value?.value || selectedOption.value]
|
||||
}
|
||||
}
|
||||
|
||||
onChange(newValue)
|
||||
},
|
||||
[isMulti, onChange],
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (Array.isArray(value)) {
|
||||
setValueToRender(
|
||||
value.map((val, index) => {
|
||||
return {
|
||||
id: `${val}${index}`, // append index to avoid duplicate keys but allow duplicate numbers
|
||||
label: `${val}`,
|
||||
value: {
|
||||
toString: () => `${val}${index}`,
|
||||
value: (val as any)?.value || val,
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
setValueToRender([])
|
||||
}
|
||||
}, [value])
|
||||
|
||||
return isMulti ? (
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
isClearable
|
||||
isCreatable
|
||||
isMulti={isMulti}
|
||||
isSortable
|
||||
onChange={onSelect}
|
||||
options={[]}
|
||||
placeholder={t('general:enterAValue')}
|
||||
value={valueToRender || []}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
className={baseClass}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -5,5 +5,5 @@ import type { DefaultFilterProps } from '../types.js'
|
||||
export type TextFilterProps = {
|
||||
readonly field: TextFieldClient
|
||||
readonly onChange: (val: string) => void
|
||||
readonly value: string
|
||||
readonly value: string | string[]
|
||||
} & DefaultFilterProps
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Page } from '@playwright/test'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { addListFilter } from 'helpers/e2e/addListFilter.js'
|
||||
import { openListFilters } from 'helpers/e2e/openListFilters.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
@@ -2,10 +2,12 @@ import type { Page } from '@playwright/test'
|
||||
import type { GeneratedTypes } from 'helpers/sdk/types.js'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { addListFilter } from 'helpers/e2e/addListFilter.js'
|
||||
import { openListColumns } from 'helpers/e2e/openListColumns.js'
|
||||
import { upsertPreferences } from 'helpers/e2e/preferences.js'
|
||||
import { toggleColumn } from 'helpers/e2e/toggleColumn.js'
|
||||
import path from 'path'
|
||||
import { wait } from 'payload/shared'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import type { PayloadTestSDK } from '../../../helpers/sdk/index.js'
|
||||
@@ -281,4 +283,64 @@ describe('Text', () => {
|
||||
// Verify it does not become editable
|
||||
await expect(field.locator('.multi-value-label__text')).not.toHaveClass(/.*--editable/)
|
||||
})
|
||||
|
||||
test('should filter Text field hasMany: false in the collection list view - in', async () => {
|
||||
await page.goto(url.list)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(2)
|
||||
|
||||
await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Text',
|
||||
operatorLabel: 'is in',
|
||||
value: 'Another text document',
|
||||
})
|
||||
|
||||
await wait(300)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should filter Text field hasMany: false in the collection list view - is not in', async () => {
|
||||
await page.goto(url.list)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(2)
|
||||
|
||||
await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Text',
|
||||
operatorLabel: 'is not in',
|
||||
value: 'Another text document',
|
||||
})
|
||||
|
||||
await wait(300)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should filter Text field hasMany: true in the collection list view - in', async () => {
|
||||
await page.goto(url.list)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(2)
|
||||
|
||||
await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Has Many',
|
||||
operatorLabel: 'is in',
|
||||
value: 'one',
|
||||
})
|
||||
|
||||
await wait(300)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should filter Text field hasMany: true in the collection list view - is not in', async () => {
|
||||
await page.goto(url.list)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(2)
|
||||
|
||||
await addListFilter({
|
||||
page,
|
||||
fieldLabel: 'Has Many',
|
||||
operatorLabel: 'is not in',
|
||||
value: 'four',
|
||||
})
|
||||
|
||||
await wait(300)
|
||||
await expect(page.locator('table >> tbody >> tr')).toHaveCount(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RequiredDataFromCollection } from 'payload/types'
|
||||
import type { RequiredDataFromCollection } from 'payload'
|
||||
|
||||
import type { TextField } from '../../payload-types.js'
|
||||
|
||||
@@ -8,8 +8,10 @@ export const textFieldsSlug = 'text-fields'
|
||||
export const textDoc: RequiredDataFromCollection<TextField> = {
|
||||
text: 'Seeded text document',
|
||||
localizedText: 'Localized text',
|
||||
hasMany: ['one', 'two'],
|
||||
}
|
||||
|
||||
export const anotherTextDoc: RequiredDataFromCollection<TextField> = {
|
||||
text: 'Another text document',
|
||||
hasMany: ['three', 'four'],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user