feat(ui): allows filtering on group and tab fields from list controls (#6647)

## Description

Adds the ability to filter by fields within a `group` or **named** `tab`
via the list controls.

Note: added missing translations for the `within` and `intersects`
operator options, these are displayed in the filters for `point` and
`JSON` fields.

- [X] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [X] New feature (non-breaking change which adds functionality)

## Checklist:

- [X] Existing test suite passes locally with my changes
This commit is contained in:
Jessica Chowdhury
2024-07-02 13:32:17 -04:00
committed by GitHub
parent 2a2ab53189
commit d4d410141c
40 changed files with 159 additions and 33 deletions

View File

@@ -251,6 +251,8 @@ export const clientTranslationKeys = createClientTranslationKeys([
'operators:isLessThan',
'operators:isGreaterThanOrEqualTo',
'operators:isLessThanOrEqualTo',
'operators:within',
'operators:intersects',
'upload:crop',
'upload:cropToolDescription',

View File

@@ -302,6 +302,7 @@ export const arTranslations: DefaultTranslationsObject = {
contains: 'يحتوي',
equals: 'يساوي',
exists: 'موجود',
intersects: 'يتقاطع',
isGreaterThan: 'أكبر من',
isGreaterThanOrEqualTo: 'أكبر أو يساوي',
isIn: 'موجود في',
@@ -311,6 +312,7 @@ export const arTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'لا يساوي',
isNotIn: 'غير موجود في',
near: 'قريب من',
within: 'في غضون',
},
upload: {
crop: 'محصول',

View File

@@ -305,6 +305,7 @@ export const azTranslations: DefaultTranslationsObject = {
contains: 'daxilində',
equals: 'bərabərdir',
exists: 'mövcuddur',
intersects: 'kəsişir',
isGreaterThan: 'dən böyük',
isGreaterThanOrEqualTo: 'böyük və ya bərabər',
isIn: 'daxildir',
@@ -314,6 +315,7 @@ export const azTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'bərabər deyil',
isNotIn: 'daxil deyil',
near: 'yaxın',
within: 'daxilinde',
},
upload: {
crop: 'Məhsul',

View File

@@ -303,6 +303,7 @@ export const bgTranslations: DefaultTranslationsObject = {
contains: 'съдържа',
equals: 'е равно на',
exists: 'съществува',
intersects: 'пресича',
isGreaterThan: 'е по-голямо от',
isGreaterThanOrEqualTo: 'е по-голямо от или равно на',
isIn: 'е в',
@@ -312,6 +313,7 @@ export const bgTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'не е равно на',
isNotIn: 'не е в',
near: 'близко',
within: 'в рамките на',
},
upload: {
crop: 'Изрязване',

View File

@@ -303,6 +303,7 @@ export const csTranslations: DefaultTranslationsObject = {
contains: 'obsahuje',
equals: 'rovná se',
exists: 'existuje',
intersects: 'protíná se',
isGreaterThan: 'je větší než',
isGreaterThanOrEqualTo: 'je větší nebo rovno',
isIn: 'je v',
@@ -312,6 +313,7 @@ export const csTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'není rovno',
isNotIn: 'není v',
near: 'blízko',
within: 'uvnitř',
},
upload: {
crop: 'Ořez',

View File

@@ -309,6 +309,7 @@ export const deTranslations: DefaultTranslationsObject = {
contains: 'enthält',
equals: 'gleich',
exists: 'existiert',
intersects: 'schneidet sich',
isGreaterThan: 'ist größer als',
isGreaterThanOrEqualTo: 'ist größer oder gleich',
isIn: 'ist drin',
@@ -318,6 +319,7 @@ export const deTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'ist nicht gleich',
isNotIn: 'ist nicht drin',
near: 'in der Nähe',
within: 'innerhalb',
},
upload: {
crop: 'Zuschneiden',

View File

@@ -306,6 +306,7 @@ export const enTranslations = {
contains: 'contains',
equals: 'equals',
exists: 'exists',
intersects: 'intersects',
isGreaterThan: 'is greater than',
isGreaterThanOrEqualTo: 'is greater than or equal to',
isIn: 'is in',
@@ -315,6 +316,7 @@ export const enTranslations = {
isNotEqualTo: 'is not equal to',
isNotIn: 'is not in',
near: 'near',
within: 'within',
},
upload: {
crop: 'Crop',

View File

@@ -308,6 +308,7 @@ export const esTranslations: DefaultTranslationsObject = {
contains: 'contiene',
equals: 'igual',
exists: 'existe',
intersects: 'interseca',
isGreaterThan: 'es mayor que',
isGreaterThanOrEqualTo: 'es mayor o igual que',
isIn: 'está en',
@@ -317,6 +318,7 @@ export const esTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'no es igual a',
isNotIn: 'no está en',
near: 'cerca',
within: 'dentro de',
},
upload: {
crop: 'Cultivo',

View File

@@ -303,6 +303,7 @@ export const faTranslations: DefaultTranslationsObject = {
contains: 'شامل',
equals: 'برابر با',
exists: 'وجود دارد',
intersects: 'تلاقی',
isGreaterThan: 'بزرگتر است از',
isGreaterThanOrEqualTo: 'بزرگتر یا مساوی است',
isIn: 'هست در',
@@ -312,6 +313,7 @@ export const faTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'برابر نیست',
isNotIn: 'در این نیست',
near: 'نزدیک',
within: 'در داخل',
},
upload: {
crop: 'محصول',

View File

@@ -312,6 +312,7 @@ export const frTranslations: DefaultTranslationsObject = {
contains: 'contient',
equals: 'est égal à',
exists: 'existe',
intersects: 'intersecte',
isGreaterThan: 'est supérieur à',
isGreaterThanOrEqualTo: 'est supérieur ou égal à',
isIn: 'est dans',
@@ -321,6 +322,7 @@ export const frTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nest pas égal à',
isNotIn: 'nest pas dans',
near: 'proche',
within: 'dans',
},
upload: {
crop: 'Recadrer',

View File

@@ -298,6 +298,7 @@ export const heTranslations: DefaultTranslationsObject = {
contains: 'מכיל',
equals: 'שווה ל',
exists: 'קיים',
intersects: 'מצטלב',
isGreaterThan: 'גדול מ',
isGreaterThanOrEqualTo: 'גדול או שווה ל',
isIn: 'נמצא ב',
@@ -307,6 +308,7 @@ export const heTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'לא שווה ל',
isNotIn: 'לא נמצא ב',
near: 'קרוב ל',
within: 'בתוך',
},
upload: {
crop: 'חתוך',

View File

@@ -304,6 +304,7 @@ export const hrTranslations: DefaultTranslationsObject = {
contains: 'sadrži',
equals: 'jednako',
exists: 'postoji',
intersects: 'presijeca',
isGreaterThan: 'je veće od',
isGreaterThanOrEqualTo: 'je veće od ili jednako',
isIn: 'je u',
@@ -313,6 +314,7 @@ export const hrTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nije jednako',
isNotIn: 'nije unutra',
near: 'blizu',
within: 'unutar',
},
upload: {
crop: 'Usjev',

View File

@@ -306,6 +306,7 @@ export const huTranslations: DefaultTranslationsObject = {
contains: 'tartalmaz',
equals: 'egyenlő',
exists: 'létezik',
intersects: 'metszéspontokban',
isGreaterThan: 'nagyobb, mint',
isGreaterThanOrEqualTo: 'nagyobb vagy egyenlő, mint',
isIn: 'benne van',
@@ -315,6 +316,7 @@ export const huTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nem egyenlő',
isNotIn: 'nincs benne',
near: 'közel',
within: 'belül',
},
upload: {
crop: 'Termés',

View File

@@ -306,6 +306,7 @@ export const itTranslations: DefaultTranslationsObject = {
contains: 'contiene',
equals: 'uguale',
exists: 'esiste',
intersects: 'interseca',
isGreaterThan: 'è maggiore di',
isGreaterThanOrEqualTo: 'è maggiore o uguale a',
isIn: 'è in',
@@ -315,6 +316,7 @@ export const itTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'non è uguale a',
isNotIn: 'non è in',
near: 'vicino',
within: "all'interno",
},
upload: {
crop: 'Raccolto',

View File

@@ -304,6 +304,7 @@ export const jaTranslations: DefaultTranslationsObject = {
contains: '含む',
equals: '等しい',
exists: '存在す',
intersects: '交差する',
isGreaterThan: 'より大きい',
isGreaterThanOrEqualTo: '以上',
isIn: 'あります',
@@ -313,6 +314,7 @@ export const jaTranslations: DefaultTranslationsObject = {
isNotEqualTo: '等しくない',
isNotIn: '入っていません',
near: '近く',
within: '内で',
},
upload: {
crop: 'クロップ',

View File

@@ -303,6 +303,7 @@ export const koTranslations: DefaultTranslationsObject = {
contains: '포함',
equals: '같음',
exists: '존재',
intersects: '교차합니다',
isGreaterThan: '보다 큼',
isGreaterThanOrEqualTo: '보다 크거나 같음',
isIn: '포함됨',
@@ -312,6 +313,7 @@ export const koTranslations: DefaultTranslationsObject = {
isNotEqualTo: '같지 않음',
isNotIn: '포함되지 않음',
near: '근처',
within: '내에서',
},
upload: {
crop: '자르기',

View File

@@ -307,6 +307,7 @@ export const myTranslations: DefaultTranslationsObject = {
contains: 'ပါဝင်သည်',
equals: 'ညီမျှ',
exists: 'တည်ရှိသည်',
intersects: 'ကြောက်ခြင်း',
isGreaterThan: 'ထက်ကြီးသည်',
isGreaterThanOrEqualTo: 'ထက်ကြီးသည် သို့မဟုတ် ညီမျှသည်',
isIn: 'ရှိ',
@@ -316,6 +317,7 @@ export const myTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'ညီမျှသည်',
isNotIn: 'မဝင်ပါ',
near: 'နီး',
within: 'အတွင်း',
},
upload: {
crop: 'သုန်း',

View File

@@ -304,6 +304,7 @@ export const nbTranslations: DefaultTranslationsObject = {
contains: 'contains',
equals: 'lik',
exists: 'eksisterer',
intersects: 'krysser',
isGreaterThan: 'er større enn',
isGreaterThanOrEqualTo: 'er større enn eller lik',
isIn: 'er i',
@@ -313,6 +314,7 @@ export const nbTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'er ikke lik',
isNotIn: 'er ikke med',
near: 'nær',
within: 'innen',
},
upload: {
crop: 'Beskjær',

View File

@@ -306,6 +306,7 @@ export const nlTranslations: DefaultTranslationsObject = {
contains: 'bevat',
equals: 'is gelijk aan',
exists: 'bestaat',
intersects: 'kruist',
isGreaterThan: 'is groter dan',
isGreaterThanOrEqualTo: 'is groter dan of gelijk aan',
isIn: 'is binnen',
@@ -315,6 +316,7 @@ export const nlTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'is niet gelijk aan',
isNotIn: 'zit er niet in',
near: 'nabij',
within: 'binnen',
},
upload: {
crop: 'Bijsnijden',

View File

@@ -304,6 +304,7 @@ export const plTranslations: DefaultTranslationsObject = {
contains: 'zawiera',
equals: 'równe',
exists: 'istnieje',
intersects: 'przecina się',
isGreaterThan: 'jest większy niż',
isGreaterThanOrEqualTo: 'jest większe lub równe',
isIn: 'jest w',
@@ -313,6 +314,7 @@ export const plTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nie jest równe',
isNotIn: 'nie ma go w',
near: 'blisko',
within: 'w ciągu',
},
upload: {
crop: 'Przytnij',

View File

@@ -305,6 +305,7 @@ export const ptTranslations: DefaultTranslationsObject = {
contains: 'contém',
equals: 'igual',
exists: 'existe',
intersects: 'intersecciona',
isGreaterThan: 'é maior que',
isGreaterThanOrEqualTo: 'é maior ou igual a',
isIn: 'está em',
@@ -314,6 +315,7 @@ export const ptTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'não é igual a',
isNotIn: 'não está em',
near: 'perto',
within: 'dentro',
},
upload: {
crop: 'Cultura',

View File

@@ -308,6 +308,7 @@ export const roTranslations: DefaultTranslationsObject = {
contains: 'conține',
equals: 'egal cu',
exists: 'există',
intersects: 'se intersectează',
isGreaterThan: 'este mai mare decât',
isGreaterThanOrEqualTo: 'este mai mare sau egal cu',
isIn: 'este în',
@@ -317,6 +318,7 @@ export const roTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nu este egal cu',
isNotIn: 'nu este în',
near: 'în apropiere de',
within: 'înăuntru',
},
upload: {
crop: 'Cultură',

View File

@@ -303,6 +303,7 @@ export const rsTranslations: DefaultTranslationsObject = {
contains: 'садржи',
equals: 'једнако',
exists: 'постоји',
intersects: 'preseca',
isGreaterThan: 'је веће од',
isGreaterThanOrEqualTo: 'је веће од или једнако',
isIn: 'је у',
@@ -312,6 +313,7 @@ export const rsTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'није једнако',
isNotIn: 'није у',
near: 'близу',
within: 'unutar',
},
upload: {
crop: 'Исеците слику',

View File

@@ -303,6 +303,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
contains: 'sadrži',
equals: 'jednako',
exists: 'postoji',
intersects: 'seče',
isGreaterThan: 'je veće od',
isGreaterThanOrEqualTo: 'je veće od ili jednako',
isIn: 'je u',
@@ -312,6 +313,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nije jednako',
isNotIn: 'nije unutra',
near: 'blizu',
within: 'unutar',
},
upload: {
crop: 'Isecite sliku',

View File

@@ -307,6 +307,7 @@ export const ruTranslations: DefaultTranslationsObject = {
contains: 'содержит',
equals: 'равно',
exists: 'существует',
intersects: 'пересекает',
isGreaterThan: 'больше чем',
isGreaterThanOrEqualTo: 'больше или равно',
isIn: 'находится',
@@ -316,6 +317,7 @@ export const ruTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'не равно',
isNotIn: 'нет в',
near: 'рядом',
within: 'в пределах',
},
upload: {
crop: 'Обрезать',

View File

@@ -305,6 +305,7 @@ export const skTranslations: DefaultTranslationsObject = {
contains: 'obsahuje',
equals: 'rovná sa',
exists: 'existuje',
intersects: 'pretína sa',
isGreaterThan: 'je väčšie ako',
isGreaterThanOrEqualTo: 'je väčšie alebo rovné',
isIn: 'je v',
@@ -314,6 +315,7 @@ export const skTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'nie je rovné',
isNotIn: 'nie je v',
near: 'blízko',
within: 'vnútri',
},
upload: {
crop: 'Orezať',

View File

@@ -304,6 +304,7 @@ export const svTranslations: DefaultTranslationsObject = {
contains: 'innehåller',
equals: 'likar med',
exists: 'finns',
intersects: 'korsar',
isGreaterThan: 'är större än',
isGreaterThanOrEqualTo: 'är större än eller lika med',
isIn: 'är med',
@@ -313,6 +314,7 @@ export const svTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'är inte lika med',
isNotIn: 'är inte med',
near: 'nära',
within: 'inom',
},
upload: {
crop: 'Skörd',

View File

@@ -300,6 +300,7 @@ export const thTranslations: DefaultTranslationsObject = {
contains: 'มี',
equals: 'เท่ากับ',
exists: 'มีอยู่',
intersects: 'ตัดกัน',
isGreaterThan: 'มากกว่า',
isGreaterThanOrEqualTo: 'มากกว่าหรือเท่ากับ',
isIn: 'อยู่ใน',
@@ -309,6 +310,7 @@ export const thTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'ไม่เท่ากับ',
isNotIn: 'ไม่ได้อยู่ใน',
near: 'ใกล้',
within: 'ภายใน',
},
upload: {
crop: 'พืชผล',

View File

@@ -308,6 +308,7 @@ export const trTranslations: DefaultTranslationsObject = {
contains: 'içerir',
equals: 'eşittir',
exists: 'var',
intersects: 'kesişir',
isGreaterThan: 'şundan büyüktür',
isGreaterThanOrEqualTo: 'büyüktür veya eşittir',
isIn: 'içinde',
@@ -317,6 +318,7 @@ export const trTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'eşit değildir',
isNotIn: 'içinde değil',
near: 'yakın',
within: 'içinde',
},
upload: {
crop: 'Mahsulat',

View File

@@ -304,6 +304,7 @@ export const ukTranslations: DefaultTranslationsObject = {
contains: 'містить',
equals: 'дорівнює',
exists: 'існує',
intersects: 'перетинається',
isGreaterThan: 'більше ніж',
isGreaterThanOrEqualTo: 'більше або дорівнює',
isIn: 'є в',
@@ -313,6 +314,7 @@ export const ukTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'не дорівнює',
isNotIn: 'не в',
near: 'поруч',
within: 'в межах',
},
upload: {
crop: 'Обрізати',

View File

@@ -302,6 +302,7 @@ export const viTranslations: DefaultTranslationsObject = {
contains: 'có chứa',
equals: 'bằng',
exists: 'tồn tại',
intersects: 'giao nhau',
isGreaterThan: 'lớn hơn',
isGreaterThanOrEqualTo: 'lớn hơn hoặc bằng',
isIn: 'có trong',
@@ -311,6 +312,7 @@ export const viTranslations: DefaultTranslationsObject = {
isNotEqualTo: 'không bằng',
isNotIn: 'không có trong',
near: 'gần',
within: 'trong',
},
upload: {
crop: 'Mùa vụ',

View File

@@ -296,6 +296,7 @@ export const zhTranslations: DefaultTranslationsObject = {
contains: '包含',
equals: '等于',
exists: '存在',
intersects: '相交',
isGreaterThan: '大于',
isGreaterThanOrEqualTo: '大于等于',
isIn: '在',
@@ -305,6 +306,7 @@ export const zhTranslations: DefaultTranslationsObject = {
isNotEqualTo: '不等于',
isNotIn: '不在',
near: '附近',
within: '在...之内',
},
upload: {
crop: '作物',

View File

@@ -296,6 +296,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
contains: '包含',
equals: '等於',
exists: '存在',
intersects: '交叉點',
isGreaterThan: '大於',
isGreaterThanOrEqualTo: '大於等於',
isIn: '在',
@@ -305,6 +306,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
isNotEqualTo: '不等於',
isNotIn: '不在',
near: '附近',
within: '在...之內',
},
upload: {
crop: '裁剪',

View File

@@ -19,7 +19,7 @@ export type FieldSelectProps = {
setSelected: (fields: FieldWithPath[]) => void
}
const combineLabel = ({
export const combineLabel = ({
customLabel,
field,
prefix,

View File

@@ -164,6 +164,7 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
<WhereBuilder
collectionPluralLabel={collectionConfig?.labels?.plural}
collectionSlug={collectionConfig.slug}
fieldMap={fieldMap}
key={String(hasWhereParam.current && !searchParams?.where)}
/>
</AnimateHeight>

View File

@@ -164,10 +164,10 @@ export const Condition: React.FC<Props> = (props) => {
options: valueOptions,
relationTo:
internalField?.props?.type === 'relationship' &&
'cellProps' in internalField.props &&
typeof internalField.props.cellProps === 'object' &&
'relationTo' in internalField.props.cellProps
? internalField.props.cellProps?.relationTo
'cellComponentProps' in internalField.props &&
typeof internalField.props.cellComponentProps === 'object' &&
'relationTo' in internalField.props.cellComponentProps
? internalField.props.cellComponentProps?.relationTo
: undefined,
value: internalQueryValue ?? '',
}}

View File

@@ -7,10 +7,10 @@ import React, { useEffect, useState } from 'react'
import type { WhereBuilderProps } from './types.js'
import { useListQuery } from '../../providers/ListQuery/index.js'
import { useLocale } from '../../providers/Locale/index.js'
import { useSearchParams } from '../../providers/SearchParams/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { Button } from '../Button/index.js'
import { useTableColumns } from '../TableColumns/index.js'
import { Condition } from './Condition/index.js'
import './index.scss'
import { reduceFieldMap } from './reduceFieldMap.js'
@@ -26,15 +26,15 @@ export { WhereBuilderProps }
* It is part of the {@link ListControls} component which is used to render the controls (search, filter, where).
*/
export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
const { collectionPluralLabel } = props
const { collectionPluralLabel, fieldMap } = props
const { i18n, t } = useTranslation()
const { columns } = useTableColumns()
const { code: currentLocale } = useLocale()
const [reducedFields, setReducedColumns] = useState(() => reduceFieldMap(columns, i18n))
const [reducedFields, setReducedColumns] = useState(() => reduceFieldMap(fieldMap, i18n))
useEffect(() => {
setReducedColumns(reduceFieldMap(columns, i18n))
}, [columns, i18n])
setReducedColumns(reduceFieldMap(fieldMap, i18n, undefined, undefined, currentLocale))
}, [fieldMap, i18n, currentLocale])
const { searchParams } = useSearchParams()
const { handleWhereChange } = useListQuery()
@@ -74,7 +74,6 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
if (validateWhereQuery(whereFromSearch)) {
return whereFromSearch.or
}
// Transform the where query to be in the right format. This will transform something simple like [text][equals]=example%20post to the right format
const transformedWhere = transformWhereQuery(whereFromSearch)

View File

@@ -1,42 +1,86 @@
'use client'
import type { Column } from '../Table/index.js'
import type { FieldMap } from '../../utilities/buildComponentMap.js'
import { createNestedClientFieldPath } from '../../forms/Form/createNestedFieldPath.js'
import { combineLabel } from '../FieldSelect/index.js'
import fieldTypes from './field-types.js'
export const reduceFieldMap = (fieldMap: Column[], i18n) =>
fieldMap.reduce((reduced, field) => {
export const reduceFieldMap = (
fieldMap: FieldMap,
i18n,
labelPrefix?: string,
pathPrefix?: string,
locale?: string,
) => {
return fieldMap.reduce((reduced, field) => {
if (field.disableListFilter) return reduced
if (field.type === 'tabs' && 'tabs' in field.fieldComponentProps) {
const tabs = field.fieldComponentProps.tabs
tabs.forEach((tab) => {
if (tab.name && typeof tab.label === 'string' && tab.fieldMap) {
reduced.push(...reduceFieldMap(tab.fieldMap, i18n, tab.label, tab.name))
}
})
return reduced
}
if (field.type === 'group' && 'fieldMap' in field.fieldComponentProps) {
reduced.push(
...reduceFieldMap(
field.fieldComponentProps.fieldMap,
i18n,
field.fieldComponentProps.label as string,
field.name,
),
)
return reduced
}
if (typeof fieldTypes[field.type] === 'object') {
const operatorKeys = new Set()
const operators = fieldTypes[field.type].operators.reduce((acc, operator) => {
if (!operatorKeys.has(operator.value)) {
operatorKeys.add(operator.value)
return [
...acc,
{
...operator,
label: i18n.t(`operators:${operator.label}`),
},
]
acc.push({
...operator,
label: i18n.t(`operators:${operator.label}`),
})
}
return acc
}, [])
const localizedLabel =
locale && typeof field.fieldComponentProps.label === 'object'
? field.fieldComponentProps.label[locale]
: field.fieldComponentProps.label
const formattedLabel = labelPrefix
? combineLabel({
field,
prefix: labelPrefix,
})
: localizedLabel
const formattedValue = pathPrefix
? createNestedClientFieldPath(pathPrefix, field)
: field.name
const formattedField = {
label: field.Label,
value: field.name,
label: formattedLabel,
value: formattedValue,
...fieldTypes[field.type],
operators,
props: {
...field,
...(field?.cellProps || {}),
...(field?.cellComponentProps || {}),
},
}
if (field.admin?.disableListFilter) return reduced
return [...reduced, formattedField]
reduced.push(formattedField)
return reduced
}
return reduced
}, [])
}

View File

@@ -1,8 +1,11 @@
import type { Field, Operator, SanitizedCollectionConfig, Where } from 'payload'
import type { FieldMap } from '../../utilities/buildComponentMap.js'
export type WhereBuilderProps = {
collectionPluralLabel: SanitizedCollectionConfig['labels']['plural']
collectionSlug: SanitizedCollectionConfig['slug']
fieldMap?: FieldMap
}
export type FieldCondition = {

View File

@@ -246,6 +246,14 @@ describe('admin2', () => {
await page.locator('.where-builder__add-first-filter').click()
const conditionField = page.locator('.condition__field')
await conditionField.click()
const dropdownFieldOption = conditionField.locator('.rs__option', {
hasText: exactText('ID'),
})
await dropdownFieldOption.click()
await expect(page.locator('.condition__field')).toContainText('ID')
const operatorField = page.locator('.condition__operator')
const valueField = page.locator('.condition__value input')
@@ -256,7 +264,9 @@ describe('admin2', () => {
await valueField.fill(id)
await expect(page.locator(tableRowLocator)).toHaveCount(1)
const tableRows = page.locator(tableRowLocator)
await expect(tableRows).toHaveCount(1)
const firstId = await page.locator(tableRowLocator).first().locator('.cell-id').innerText()
expect(firstId).toEqual(`ID: ${id}`)
@@ -288,12 +298,15 @@ describe('admin2', () => {
await filterField.click()
// select new filter field of Number
const dropdownFieldOptions = filterField.locator('.rs__option')
await dropdownFieldOptions.locator('text=Number').click()
const dropdownFieldOption = filterField.locator('.rs__option', {
hasText: exactText('Status'),
})
await dropdownFieldOption.click()
await expect(filterField).toContainText('Status')
// expect operator & value field to reset (be empty)
await expect(operatorField.locator('.rs__placeholder')).toContainText('Select a value')
await expect(valueField).toHaveValue('')
await expect(page.locator('.condition__value input')).toHaveValue('')
})
test('should accept where query from valid URL where parameter', async () => {