diff --git a/packages/ui/src/elements/WhereBuilder/Condition/DefaultFilter/index.tsx b/packages/ui/src/elements/WhereBuilder/Condition/DefaultFilter/index.tsx index e939e475ad..44ba59267e 100644 --- a/packages/ui/src/elements/WhereBuilder/Condition/DefaultFilter/index.tsx +++ b/packages/ui/src/elements/WhereBuilder/Condition/DefaultFilter/index.tsx @@ -96,7 +96,7 @@ export const DefaultFilter: React.FC = ({ field={internalField?.field as TextFieldClient} onChange={onChange} operator={operator} - value={value as string} + value={value as string | string[]} /> ) } diff --git a/packages/ui/src/elements/WhereBuilder/Condition/Text/index.tsx b/packages/ui/src/elements/WhereBuilder/Condition/Text/index.tsx index f4cd8b217a..3c2f2e65d1 100644 --- a/packages/ui/src/elements/WhereBuilder/Condition/Text/index.tsx +++ b/packages/ui/src/elements/WhereBuilder/Condition/Text/index.tsx @@ -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 = ({ disabled, onChange, value }) => { +export const Text: React.FC = (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 ? ( + + ) : ( void - readonly value: string + readonly value: string | string[] } & DefaultFilterProps diff --git a/test/fields/collections/Number/e2e.spec.ts b/test/fields/collections/Number/e2e.spec.ts index 7044a61c2c..321e5d5270 100644 --- a/test/fields/collections/Number/e2e.spec.ts +++ b/test/fields/collections/Number/e2e.spec.ts @@ -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' diff --git a/test/fields/collections/Text/e2e.spec.ts b/test/fields/collections/Text/e2e.spec.ts index c5261ae944..2775f9e648 100644 --- a/test/fields/collections/Text/e2e.spec.ts +++ b/test/fields/collections/Text/e2e.spec.ts @@ -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) + }) }) diff --git a/test/fields/collections/Text/shared.ts b/test/fields/collections/Text/shared.ts index 34c20ba0d4..54eb847fdc 100644 --- a/test/fields/collections/Text/shared.ts +++ b/test/fields/collections/Text/shared.ts @@ -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 = { text: 'Seeded text document', localizedText: 'Localized text', + hasMany: ['one', 'two'], } export const anotherTextDoc: RequiredDataFromCollection = { text: 'Another text document', + hasMany: ['three', 'four'], }