feat: show fields inside groups as separate columns in the list view (#7355)
## Description Group fields are shown as one column, this PR changes this so that the individual field is now shown separately. Before change: <img width="1227" alt="before change" src="https://github.com/user-attachments/assets/dfae58fd-8ad2-4329-84fd-ed1d4eb20854"> After change: <img width="1229" alt="after change" src="https://github.com/user-attachments/assets/d4fd78bb-c474-436e-a0f5-cac4638b91a4"> - [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] I have added tests that prove my fix is effective or that my feature works - [X] Existing test suite passes locally with my changes - [ ] I have made corresponding changes to the documentation --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
c772a3207c
commit
2a41d3fbb1
@@ -1,6 +1,6 @@
|
||||
import type { ArrayFieldClient, BlocksFieldClient, ClientConfig, ClientField } from 'payload'
|
||||
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
import { fieldShouldBeLocalized, groupHasName } from 'payload/shared'
|
||||
|
||||
import { fieldHasChanges } from './fieldHasChanges.js'
|
||||
import { getFieldsForRowComparison } from './getFieldsForRowComparison.js'
|
||||
@@ -114,25 +114,37 @@ export function countChangedFields({
|
||||
|
||||
// Fields that have nested fields and nest their fields' data.
|
||||
case 'group': {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
if (groupHasName(field)) {
|
||||
if (locales && fieldShouldBeLocalized({ field, parentIsLocalized })) {
|
||||
locales.forEach((locale) => {
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name]?.[locale],
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name]?.[locale],
|
||||
})
|
||||
})
|
||||
} else {
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name]?.[locale],
|
||||
comparison: comparison?.[field.name],
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name]?.[locale],
|
||||
version: version?.[field.name],
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Unnamed group field: data is NOT nested under `field.name`
|
||||
count += countChangedFields({
|
||||
comparison: comparison?.[field.name],
|
||||
comparison,
|
||||
config,
|
||||
fields: field.fields,
|
||||
locales,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
version: version?.[field.name],
|
||||
version,
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
@@ -29,6 +29,7 @@ export {
|
||||
fieldIsVirtual,
|
||||
fieldShouldBeLocalized,
|
||||
fieldSupportsMany,
|
||||
groupHasName,
|
||||
optionIsObject,
|
||||
optionIsValue,
|
||||
optionsAreObjects,
|
||||
|
||||
@@ -770,7 +770,7 @@ export type NamedGroupFieldClient = {
|
||||
export type UnnamedGroupFieldClient = {
|
||||
admin?: AdminClient & Pick<UnnamedGroupField['admin'], 'hideGutter'>
|
||||
fields: ClientField[]
|
||||
} & Omit<FieldBaseClient, 'required'> &
|
||||
} & Omit<FieldBaseClient, 'name' | 'required'> &
|
||||
Pick<UnnamedGroupField, 'label' | 'type'>
|
||||
|
||||
export type GroupFieldClient = NamedGroupFieldClient | UnnamedGroupFieldClient
|
||||
@@ -1960,6 +1960,12 @@ export function tabHasName<TField extends ClientTab | Tab>(tab: TField): tab is
|
||||
return 'name' in tab
|
||||
}
|
||||
|
||||
export function groupHasName(
|
||||
group: Partial<NamedGroupFieldClient>,
|
||||
): group is NamedGroupFieldClient {
|
||||
return 'name' in group
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a field has localized: true set. This does not check if a field *should*
|
||||
* be localized. To check if a field should be localized, use `fieldShouldBeLocalized`.
|
||||
|
||||
510
packages/payload/src/utilities/flattenTopLevelFields.spec.ts
Normal file
510
packages/payload/src/utilities/flattenTopLevelFields.spec.ts
Normal file
@@ -0,0 +1,510 @@
|
||||
import { I18nClient } from '@payloadcms/translations'
|
||||
import { ClientField } from '../fields/config/client.js'
|
||||
import flattenFields from './flattenTopLevelFields.js'
|
||||
|
||||
describe('flattenFields', () => {
|
||||
const i18n: I18nClient = {
|
||||
t: (value: string) => value,
|
||||
language: 'en',
|
||||
dateFNS: {} as any,
|
||||
dateFNSKey: 'en-US',
|
||||
fallbackLanguage: 'en',
|
||||
translations: {},
|
||||
}
|
||||
|
||||
const baseField: ClientField = {
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
}
|
||||
|
||||
describe('basic flattening', () => {
|
||||
it('should return flat list for top-level fields', () => {
|
||||
const fields = [baseField]
|
||||
const result = flattenFields(fields)
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('group flattening', () => {
|
||||
it('should flatten fields inside group with accessor and labelWithPrefix with moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
label: 'Meta Info',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('slug')
|
||||
expect(result[0].accessor).toBe('meta-slug')
|
||||
expect(result[0].labelWithPrefix).toBe('Meta Info > Slug')
|
||||
})
|
||||
|
||||
it('should NOT flatten fields inside group without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'meta',
|
||||
label: 'Meta Info',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields)
|
||||
|
||||
// Should return the group as a top-level item, not the inner field
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('meta')
|
||||
expect('fields' in result[0]).toBe(true)
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
})
|
||||
|
||||
it('should correctly handle deeply nested group fields with and without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'outer',
|
||||
label: 'Outer',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'inner',
|
||||
label: 'Inner',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'deep',
|
||||
label: 'Deep Field',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const hoisted = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(hoisted).toHaveLength(1)
|
||||
expect(hoisted[0].name).toBe('deep')
|
||||
expect(hoisted[0].accessor).toBe('outer-inner-deep')
|
||||
expect(hoisted[0].labelWithPrefix).toBe('Outer > Inner > Deep Field')
|
||||
|
||||
const nonHoisted = flattenFields(fields)
|
||||
|
||||
expect(nonHoisted).toHaveLength(1)
|
||||
expect(nonHoisted[0].name).toBe('outer')
|
||||
expect('fields' in nonHoisted[0]).toBe(true)
|
||||
expect('accessor' in nonHoisted[0]).toBe(false)
|
||||
expect('labelWithPrefix' in nonHoisted[0]).toBe(false)
|
||||
})
|
||||
|
||||
it('should hoist fields from unnamed group if moveSubFieldsToTop is true', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Unnamed group',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'insideUnnamedGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const withExtract = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
// Should keep the group as a single top-level field
|
||||
expect(withExtract).toHaveLength(1)
|
||||
expect(withExtract[0].type).toBe('text')
|
||||
expect(withExtract[0].accessor).toBeUndefined()
|
||||
expect(withExtract[0].labelWithPrefix).toBeUndefined()
|
||||
|
||||
const withoutExtract = flattenFields(fields)
|
||||
|
||||
expect(withoutExtract).toHaveLength(1)
|
||||
expect(withoutExtract[0].type).toBe('group')
|
||||
expect(withoutExtract[0].accessor).toBeUndefined()
|
||||
expect(withoutExtract[0].labelWithPrefix).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should hoist using deepest named group only if parents are unnamed', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Outer',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Middle',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'namedGroup',
|
||||
label: 'Named Group',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
label: 'Inner',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedField',
|
||||
label: 'Nested Field',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const hoistedResult = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(hoistedResult).toHaveLength(1)
|
||||
expect(hoistedResult[0].name).toBe('nestedField')
|
||||
expect(hoistedResult[0].accessor).toBe('namedGroup-nestedField')
|
||||
expect(hoistedResult[0].labelWithPrefix).toBe('Named Group > Nested Field')
|
||||
|
||||
const nonHoistedResult = flattenFields(fields)
|
||||
|
||||
expect(nonHoistedResult).toHaveLength(1)
|
||||
expect(nonHoistedResult[0].type).toBe('group')
|
||||
expect('fields' in nonHoistedResult[0]).toBe(true)
|
||||
expect('accessor' in nonHoistedResult[0]).toBe(false)
|
||||
expect('labelWithPrefix' in nonHoistedResult[0]).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('array and block edge cases', () => {
|
||||
it('should NOT flatten fields in arrays or blocks with moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'items',
|
||||
label: 'Items',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'label',
|
||||
label: 'Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'layout',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'block',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'content',
|
||||
label: 'Content',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('items')
|
||||
expect(result[1].name).toBe('layout')
|
||||
})
|
||||
|
||||
it('should NOT flatten fields in arrays or blocks without moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'things',
|
||||
label: 'Things',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'thingLabel',
|
||||
label: 'Thing Label',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'contentBlocks',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'content',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('things')
|
||||
expect(result[1].name).toBe('contentBlocks')
|
||||
})
|
||||
|
||||
it('should not hoist group fields nested inside arrays', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'array',
|
||||
name: 'arrayField',
|
||||
label: 'Array Field',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInArray',
|
||||
label: 'Group In Array',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInArrayGroup',
|
||||
label: 'Nested In Array Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('arrayField')
|
||||
})
|
||||
|
||||
it('should not hoist group fields nested inside blocks', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'blocks',
|
||||
name: 'blockField',
|
||||
blocks: [
|
||||
{
|
||||
slug: 'exampleBlock',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInBlock',
|
||||
label: 'Group In Block',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInBlockGroup',
|
||||
label: 'Nested In Block Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('blockField')
|
||||
})
|
||||
})
|
||||
|
||||
describe('row and collapsible behavior', () => {
|
||||
it('should recursively flatten collapsible fields regardless of moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nickname',
|
||||
label: 'Nickname',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const defaultResult = flattenFields(fields)
|
||||
const hoistedResult = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
|
||||
for (const result of [defaultResult, hoistedResult]) {
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].name).toBe('nickname')
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should recursively flatten row fields regardless of moveSubFieldsToTop', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'firstName',
|
||||
label: 'First Name',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'lastName',
|
||||
label: 'Last Name',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const defaultResult = flattenFields(fields)
|
||||
const hoistedResult = flattenFields(fields, { moveSubFieldsToTop: true })
|
||||
|
||||
for (const result of [defaultResult, hoistedResult]) {
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0].name).toBe('firstName')
|
||||
expect(result[1].name).toBe('lastName')
|
||||
expect('accessor' in result[0]).toBe(false)
|
||||
expect('labelWithPrefix' in result[0]).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('should hoist named group fields inside rows', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInRow',
|
||||
label: 'Group In Row',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInRowGroup',
|
||||
label: 'Nested In Row Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInRow-nestedInRowGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Row > Nested In Row Group')
|
||||
})
|
||||
|
||||
it('should hoist named group fields inside collapsibles', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInCollapsible',
|
||||
label: 'Group In Collapsible',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInCollapsibleGroup',
|
||||
label: 'Nested In Collapsible Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInCollapsible-nestedInCollapsibleGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Collapsible > Nested In Collapsible Group')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tab integration', () => {
|
||||
it('should hoist named group fields inside tabs when moveSubFieldsToTop is true', () => {
|
||||
const fields: ClientField[] = [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Tab One',
|
||||
fields: [
|
||||
{
|
||||
type: 'group',
|
||||
name: 'groupInTab',
|
||||
label: 'Group In Tab',
|
||||
fields: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'nestedInTabGroup',
|
||||
label: 'Nested In Tab Group',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const result = flattenFields(fields, {
|
||||
moveSubFieldsToTop: true,
|
||||
i18n,
|
||||
})
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].accessor).toBe('groupInTab-nestedInTabGroup')
|
||||
expect(result[0].labelWithPrefix).toBe('Group In Tab > Nested In Tab Group')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,8 @@
|
||||
// @ts-strict-ignore
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
|
||||
import type { ClientTab } from '../admin/fields/Tabs.js'
|
||||
import type { ClientField } from '../fields/config/client.js'
|
||||
import type {
|
||||
@@ -18,38 +22,153 @@ import {
|
||||
} from '../fields/config/types.js'
|
||||
|
||||
type FlattenedField<TField> = TField extends ClientField
|
||||
? FieldAffectingDataClient | FieldPresentationalOnlyClient
|
||||
: FieldAffectingData | FieldPresentationalOnly
|
||||
? { accessor?: string; labelWithPrefix?: string } & (
|
||||
| FieldAffectingDataClient
|
||||
| FieldPresentationalOnlyClient
|
||||
)
|
||||
: { accessor?: string; labelWithPrefix?: string } & (FieldAffectingData | FieldPresentationalOnly)
|
||||
|
||||
type TabType<TField> = TField extends ClientField ? ClientTab : Tab
|
||||
|
||||
/**
|
||||
* Flattens a collection's fields into a single array of fields, as long
|
||||
* as the fields do not affect data.
|
||||
* Options to control how fields are flattened.
|
||||
*/
|
||||
type FlattenFieldsOptions = {
|
||||
/**
|
||||
* i18n context used for translating `label` values via `getTranslation`.
|
||||
*/
|
||||
i18n?: I18nClient
|
||||
/**
|
||||
* If true, presentational-only fields (like UI fields) will be included
|
||||
* in the output. Otherwise, they will be skipped.
|
||||
* Default: false.
|
||||
*/
|
||||
keepPresentationalFields?: boolean
|
||||
/**
|
||||
* A label prefix to prepend to translated labels when building `labelWithPrefix`.
|
||||
* Used recursively when flattening nested fields.
|
||||
*/
|
||||
labelPrefix?: string
|
||||
/**
|
||||
* If true, nested fields inside `group` fields will be lifted to the top level
|
||||
* and given contextual `accessor` and `labelWithPrefix` values.
|
||||
* Default: false.
|
||||
*/
|
||||
moveSubFieldsToTop?: boolean
|
||||
/**
|
||||
* A path prefix to prepend to field names when building the `accessor`.
|
||||
* Used recursively when flattening nested fields.
|
||||
*/
|
||||
pathPrefix?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a collection's fields into a single array of fields, optionally
|
||||
* extracting nested fields in group fields.
|
||||
*
|
||||
* @param fields
|
||||
* @param keepPresentationalFields if true, will skip flattening fields that are presentational only
|
||||
* @param fields - Array of fields to flatten
|
||||
* @param options - Options to control the flattening behavior
|
||||
*/
|
||||
function flattenFields<TField extends ClientField | Field>(
|
||||
fields: TField[],
|
||||
keepPresentationalFields?: boolean,
|
||||
options?: boolean | FlattenFieldsOptions,
|
||||
): FlattenedField<TField>[] {
|
||||
const normalizedOptions: FlattenFieldsOptions =
|
||||
typeof options === 'boolean' ? { keepPresentationalFields: options } : (options ?? {})
|
||||
|
||||
const {
|
||||
i18n,
|
||||
keepPresentationalFields,
|
||||
labelPrefix,
|
||||
moveSubFieldsToTop = false,
|
||||
pathPrefix,
|
||||
} = normalizedOptions
|
||||
|
||||
return fields.reduce<FlattenedField<TField>[]>((acc, field) => {
|
||||
if (fieldAffectsData(field) || (keepPresentationalFields && fieldIsPresentationalOnly(field))) {
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
} else if (fieldHasSubFields(field)) {
|
||||
acc.push(...flattenFields(field.fields as TField[], keepPresentationalFields))
|
||||
if (fieldHasSubFields(field)) {
|
||||
if (field.type === 'group') {
|
||||
if (moveSubFieldsToTop && 'fields' in field) {
|
||||
const isNamedGroup = 'name' in field && typeof field.name === 'string' && !!field.name
|
||||
|
||||
const translatedLabel =
|
||||
'label' in field && field.label && i18n
|
||||
? getTranslation(field.label as string, i18n)
|
||||
: undefined
|
||||
|
||||
const labelWithPrefix =
|
||||
isNamedGroup && labelPrefix && translatedLabel
|
||||
? `${labelPrefix} > ${translatedLabel}`
|
||||
: (labelPrefix ?? translatedLabel)
|
||||
|
||||
const nameWithPrefix =
|
||||
isNamedGroup && field.name
|
||||
? pathPrefix
|
||||
? `${pathPrefix}-${field.name as string}`
|
||||
: (field.name as string)
|
||||
: pathPrefix
|
||||
|
||||
acc.push(
|
||||
...flattenFields(field.fields as TField[], {
|
||||
i18n,
|
||||
keepPresentationalFields,
|
||||
labelPrefix: isNamedGroup ? labelWithPrefix : labelPrefix,
|
||||
moveSubFieldsToTop,
|
||||
pathPrefix: isNamedGroup ? nameWithPrefix : pathPrefix,
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
// Just keep the group as-is
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
}
|
||||
} else if (['collapsible', 'row'].includes(field.type)) {
|
||||
// Recurse into row and collapsible
|
||||
acc.push(...flattenFields(field.fields as TField[], options))
|
||||
} else {
|
||||
// Do not hoist fields from arrays & blocks
|
||||
acc.push(field as FlattenedField<TField>)
|
||||
}
|
||||
} else if (
|
||||
fieldAffectsData(field) ||
|
||||
(keepPresentationalFields && fieldIsPresentationalOnly(field))
|
||||
) {
|
||||
// Ignore nested `id` fields when inside nested structure
|
||||
if (field.name === 'id' && labelPrefix !== undefined) {
|
||||
return acc
|
||||
}
|
||||
|
||||
const translatedLabel =
|
||||
'label' in field && field.label && i18n ? getTranslation(field.label, i18n) : undefined
|
||||
|
||||
const name = 'name' in field ? field.name : undefined
|
||||
|
||||
const isHoistingFromGroup = pathPrefix !== undefined || labelPrefix !== undefined
|
||||
|
||||
acc.push({
|
||||
...(field as FlattenedField<TField>),
|
||||
...(moveSubFieldsToTop &&
|
||||
isHoistingFromGroup && {
|
||||
accessor: pathPrefix && name ? `${pathPrefix}-${name}` : (name ?? ''),
|
||||
labelWithPrefix:
|
||||
labelPrefix && translatedLabel
|
||||
? `${labelPrefix} > ${translatedLabel}`
|
||||
: (labelPrefix ?? translatedLabel),
|
||||
}),
|
||||
})
|
||||
} else if (field.type === 'tabs' && 'tabs' in field) {
|
||||
return [
|
||||
...acc,
|
||||
...field.tabs.reduce<FlattenedField<TField>[]>((tabFields, tab: TabType<TField>) => {
|
||||
if (tabHasName(tab)) {
|
||||
return [...tabFields, { ...tab, type: 'tab' } as unknown as FlattenedField<TField>]
|
||||
} else {
|
||||
return [
|
||||
...tabFields,
|
||||
...flattenFields(tab.fields as TField[], keepPresentationalFields),
|
||||
{
|
||||
...tab,
|
||||
type: 'tab',
|
||||
...(moveSubFieldsToTop && { labelPrefix }),
|
||||
} as unknown as FlattenedField<TField>,
|
||||
]
|
||||
} else {
|
||||
return [...tabFields, ...flattenFields<TField>(tab.fields as TField[], options)]
|
||||
}
|
||||
}, []),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { SanitizedCollectionConfig } from 'payload'
|
||||
import type { SanitizedCollectionConfig, StaticLabel } from 'payload'
|
||||
|
||||
import { fieldIsHiddenOrDisabled, fieldIsID } from 'payload/shared'
|
||||
import React, { useId, useMemo } from 'react'
|
||||
@@ -53,6 +53,15 @@ export const ColumnSelector: React.FC<Props> = ({ collectionSlug }) => {
|
||||
{filteredColumns.map((col, i) => {
|
||||
const { accessor, active, field } = col
|
||||
|
||||
const label =
|
||||
'labelWithPrefix' in field && field.labelWithPrefix !== undefined
|
||||
? field.labelWithPrefix
|
||||
: 'label' in field && field.label !== undefined
|
||||
? field.label
|
||||
: 'name' in field && field.name !== undefined
|
||||
? field.name
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<Pill
|
||||
alignIcon="left"
|
||||
@@ -63,14 +72,12 @@ export const ColumnSelector: React.FC<Props> = ({ collectionSlug }) => {
|
||||
draggable
|
||||
icon={active ? <XIcon /> : <PlusIcon />}
|
||||
id={accessor}
|
||||
key={`${collectionSlug}-${field && 'name' in field ? field?.name : i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
|
||||
key={`${collectionSlug}-${accessor}-${i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
|
||||
onClick={() => {
|
||||
void toggleColumn(accessor)
|
||||
}}
|
||||
>
|
||||
{col.CustomLabel ?? (
|
||||
<FieldLabel label={field && 'label' in field && field.label} unstyled />
|
||||
)}
|
||||
{col.CustomLabel ?? <FieldLabel label={label as StaticLabel} unstyled />}
|
||||
</Pill>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { I18nClient } from '@payloadcms/translations'
|
||||
import type { ClientField } from 'payload'
|
||||
|
||||
import { fieldAffectsData, flattenTopLevelFields } from 'payload/shared'
|
||||
@@ -6,9 +7,13 @@ import { fieldAffectsData, flattenTopLevelFields } from 'payload/shared'
|
||||
export const getTextFieldsToBeSearched = (
|
||||
listSearchableFields: string[],
|
||||
fields: ClientField[],
|
||||
i18n: I18nClient,
|
||||
): ClientField[] => {
|
||||
if (listSearchableFields) {
|
||||
const flattenedFields = flattenTopLevelFields(fields) as ClientField[]
|
||||
const flattenedFields = flattenTopLevelFields(fields, {
|
||||
i18n,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as ClientField[]
|
||||
|
||||
return flattenedFields.filter(
|
||||
(field) => fieldAffectsData(field) && listSearchableFields.includes(field.name),
|
||||
|
||||
@@ -86,6 +86,7 @@ export const ListControls: React.FC<ListControlsProps> = (props) => {
|
||||
const listSearchableFields = getTextFieldsToBeSearched(
|
||||
collectionConfig.admin.listSearchableFields,
|
||||
collectionConfig.fields,
|
||||
i18n,
|
||||
)
|
||||
|
||||
const searchLabelTranslated = useRef(
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { GroupFieldClientComponent } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import { groupHasName } from 'payload/shared'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { useCollapsible } from '../../elements/Collapsible/provider.js'
|
||||
@@ -16,9 +17,9 @@ import { useField } from '../../forms/useField/index.js'
|
||||
import { withCondition } from '../../forms/withCondition/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { mergeFieldStyles } from '../mergeFieldStyles.js'
|
||||
import './index.scss'
|
||||
import { useRow } from '../Row/provider.js'
|
||||
import { fieldBaseClass } from '../shared/index.js'
|
||||
import './index.scss'
|
||||
import { useTabs } from '../Tabs/provider.js'
|
||||
import { GroupProvider, useGroup } from './provider.js'
|
||||
|
||||
@@ -27,7 +28,7 @@ const baseClass = 'group-field'
|
||||
export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
const {
|
||||
field,
|
||||
field: { name, admin: { className, description, hideGutter } = {}, fields, label },
|
||||
field: { admin: { className, description, hideGutter } = {}, fields, label },
|
||||
indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
@@ -37,7 +38,8 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
schemaPath: schemaPathFromProps,
|
||||
} = props
|
||||
|
||||
const schemaPath = schemaPathFromProps ?? name
|
||||
const schemaPath =
|
||||
schemaPathFromProps ?? (field.type === 'group' && groupHasName(field) ? field.name : path)
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const { isWithinCollapsible } = useCollapsible()
|
||||
@@ -106,7 +108,7 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
)}
|
||||
{BeforeInput}
|
||||
{/* Render an unnamed group differently */}
|
||||
{name ? (
|
||||
{groupHasName(field) ? (
|
||||
<RenderFields
|
||||
fields={fields}
|
||||
margins="small"
|
||||
|
||||
@@ -43,7 +43,9 @@ const getInitialDrawerData = ({
|
||||
fields: ClientField[]
|
||||
segments: string[]
|
||||
}) => {
|
||||
const flattenedFields = flattenTopLevelFields(fields)
|
||||
const flattenedFields = flattenTopLevelFields(fields, {
|
||||
keepPresentationalFields: true,
|
||||
})
|
||||
|
||||
const path = segments[0]
|
||||
|
||||
|
||||
@@ -3,13 +3,20 @@ import type { ClientCollectionConfig, ClientField } from 'payload'
|
||||
|
||||
import { flattenTopLevelFields } from 'payload/shared'
|
||||
|
||||
import { useTranslation } from '../providers/Translation/index.js'
|
||||
|
||||
export const useUseTitleField = (collection: ClientCollectionConfig): ClientField => {
|
||||
const {
|
||||
admin: { useAsTitle },
|
||||
fields,
|
||||
} = collection
|
||||
|
||||
const topLevelFields = flattenTopLevelFields(fields) as ClientField[]
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const topLevelFields = flattenTopLevelFields(fields, {
|
||||
i18n,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as ClientField[]
|
||||
|
||||
return topLevelFields?.find((field) => 'name' in field && field.name === useAsTitle)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from '../../exports/client/index.js'
|
||||
import { hasOptionLabelJSXElement } from '../../utilities/hasOptionLabelJSXElement.js'
|
||||
import { filterFields } from './filterFields.js'
|
||||
import { findValueInDoc } from './findValueInDoc.js'
|
||||
|
||||
type Args = {
|
||||
beforeRows?: Column[]
|
||||
@@ -70,15 +71,17 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
} = args
|
||||
|
||||
// clientFields contains the fake `id` column
|
||||
let sortedFieldMap = flattenTopLevelFields(
|
||||
filterFields(clientCollectionConfig.fields),
|
||||
true,
|
||||
) as ClientField[]
|
||||
let sortedFieldMap = flattenTopLevelFields(filterFields(clientCollectionConfig.fields), {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as ClientField[]
|
||||
|
||||
let _sortedFieldMap = flattenTopLevelFields(
|
||||
filterFields(collectionConfig.fields),
|
||||
true,
|
||||
) as Field[] // TODO: think of a way to avoid this additional flatten
|
||||
let _sortedFieldMap = flattenTopLevelFields(filterFields(collectionConfig.fields), {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as Field[] // TODO: think of a way to avoid this additional flatten
|
||||
|
||||
// place the `ID` field first, if it exists
|
||||
// do the same for the `useAsTitle` field with precedence over the `ID` field
|
||||
@@ -103,8 +106,9 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
|
||||
const sortFieldMap = (fieldMap, sortTo) =>
|
||||
fieldMap?.sort((a, b) => {
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
|
||||
const getAccessor = (field) => field.accessor ?? ('name' in field ? field.name : undefined)
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === getAccessor(a))
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === getAccessor(b))
|
||||
|
||||
if (aIndex === -1 && bIndex === -1) {
|
||||
return 0
|
||||
@@ -134,12 +138,15 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
return acc
|
||||
}
|
||||
|
||||
const _field = _sortedFieldMap.find(
|
||||
(f) => 'name' in field && 'name' in f && f.name === field.name,
|
||||
)
|
||||
const accessor = (field as any).accessor ?? ('name' in field ? field.name : undefined)
|
||||
|
||||
const _field = _sortedFieldMap.find((f) => {
|
||||
const fAccessor = (f as any).accessor ?? ('name' in f ? f.name : undefined)
|
||||
return fAccessor === accessor
|
||||
})
|
||||
|
||||
const columnPreference = columnPreferences?.find(
|
||||
(preference) => field && 'name' in field && preference.accessor === field.name,
|
||||
(preference) => field && 'name' in field && preference.accessor === accessor,
|
||||
)
|
||||
|
||||
let active = false
|
||||
@@ -147,9 +154,7 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
if (columnPreference) {
|
||||
active = columnPreference.active
|
||||
} else if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||
active = columns.find(
|
||||
(column) => field && 'name' in field && column.accessor === field.name,
|
||||
)?.active
|
||||
active = columns.find((column) => column.accessor === accessor)?.active
|
||||
} else if (activeColumnsIndices.length < 4) {
|
||||
active = true
|
||||
}
|
||||
@@ -197,12 +202,22 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
field.type &&
|
||||
(field.type === 'array' || field.type === 'group' || field.type === 'blocks')
|
||||
|
||||
const label =
|
||||
field && 'labelWithPrefix' in field && field.labelWithPrefix !== undefined
|
||||
? field.labelWithPrefix
|
||||
: 'label' in field
|
||||
? field.label
|
||||
: undefined
|
||||
|
||||
// Convert accessor to dot notation specifically for SortColumn sorting behavior
|
||||
const dotAccessor = accessor?.replace(/-/g, '.')
|
||||
|
||||
const Heading = (
|
||||
<SortColumn
|
||||
disable={fieldAffectsDataSubFields || fieldIsPresentationalOnly(field) || undefined}
|
||||
Label={CustomLabel}
|
||||
label={field && 'label' in field ? (field.label as StaticLabel) : undefined}
|
||||
name={'name' in field ? field.name : undefined}
|
||||
label={label as StaticLabel}
|
||||
name={dotAccessor}
|
||||
{...(sortColumnProps || {})}
|
||||
/>
|
||||
)
|
||||
@@ -216,7 +231,7 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
}
|
||||
|
||||
const column: Column = {
|
||||
accessor: 'name' in field ? field.name : undefined,
|
||||
accessor,
|
||||
active,
|
||||
CustomLabel,
|
||||
field,
|
||||
@@ -227,7 +242,7 @@ export const buildColumnState = (args: Args): Column[] => {
|
||||
|
||||
const cellClientProps: DefaultCellComponentProps = {
|
||||
...baseCellClientProps,
|
||||
cellData: 'name' in field ? doc[field.name] : undefined,
|
||||
cellData: 'name' in field ? findValueInDoc(doc, field.name) : undefined,
|
||||
link: isLinkedColumn,
|
||||
rowData: doc,
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
// eslint-disable-next-line payload/no-imports-from-exports-dir
|
||||
} from '../../exports/client/index.js'
|
||||
import { filterFields } from './filterFields.js'
|
||||
import { findValueInDoc } from './findValueInDoc.js'
|
||||
|
||||
type Args = {
|
||||
beforeRows?: Column[]
|
||||
@@ -65,9 +66,17 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
} = args
|
||||
|
||||
// clientFields contains the fake `id` column
|
||||
let sortedFieldMap = flattenTopLevelFields(filterFields(fields), true) as ClientField[]
|
||||
let sortedFieldMap = flattenTopLevelFields(filterFields(fields), {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as ClientField[]
|
||||
|
||||
let _sortedFieldMap = flattenTopLevelFields(filterFields(fields), true) as Field[] // TODO: think of a way to avoid this additional flatten
|
||||
let _sortedFieldMap = flattenTopLevelFields(filterFields(fields), {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
}) as Field[] // TODO: think of a way to avoid this additional flatten
|
||||
|
||||
// place the `ID` field first, if it exists
|
||||
// do the same for the `useAsTitle` field with precedence over the `ID` field
|
||||
@@ -92,8 +101,9 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
|
||||
const sortFieldMap = (fieldMap, sortTo) =>
|
||||
fieldMap?.sort((a, b) => {
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === a.name)
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === b.name)
|
||||
const getAccessor = (field) => field.accessor ?? ('name' in field ? field.name : undefined)
|
||||
const aIndex = sortTo.findIndex((column) => 'name' in a && column.accessor === getAccessor(a))
|
||||
const bIndex = sortTo.findIndex((column) => 'name' in b && column.accessor === getAccessor(b))
|
||||
|
||||
if (aIndex === -1 && bIndex === -1) {
|
||||
return 0
|
||||
@@ -123,12 +133,15 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
return acc
|
||||
}
|
||||
|
||||
const _field = _sortedFieldMap.find(
|
||||
(f) => 'name' in field && 'name' in f && f.name === field.name,
|
||||
)
|
||||
const accessor = (field as any).accessor ?? ('name' in field ? field.name : undefined)
|
||||
|
||||
const _field = _sortedFieldMap.find((f) => {
|
||||
const fAccessor = (f as any).accessor ?? ('name' in f ? f.name : undefined)
|
||||
return fAccessor === accessor
|
||||
})
|
||||
|
||||
const columnPreference = columnPreferences?.find(
|
||||
(preference) => field && 'name' in field && preference.accessor === field.name,
|
||||
(preference) => field && 'name' in field && preference.accessor === accessor,
|
||||
)
|
||||
|
||||
let active = false
|
||||
@@ -136,9 +149,7 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
if (columnPreference) {
|
||||
active = columnPreference.active
|
||||
} else if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||
active = columns.find(
|
||||
(column) => field && 'name' in field && column.accessor === field.name,
|
||||
)?.active
|
||||
active = columns.find((column) => column.accessor === accessor)?.active
|
||||
} else if (activeColumnsIndices.length < 4) {
|
||||
active = true
|
||||
}
|
||||
@@ -179,18 +190,28 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
field.type &&
|
||||
(field.type === 'array' || field.type === 'group' || field.type === 'blocks')
|
||||
|
||||
const label =
|
||||
'labelWithPrefix' in field && field.labelWithPrefix !== undefined
|
||||
? field.labelWithPrefix
|
||||
: 'label' in field
|
||||
? field.label
|
||||
: undefined
|
||||
|
||||
// Convert accessor to dot notation specifically for SortColumn sorting behavior
|
||||
const dotAccessor = accessor?.replace(/-/g, '.')
|
||||
|
||||
const Heading = (
|
||||
<SortColumn
|
||||
disable={fieldAffectsDataSubFields || fieldIsPresentationalOnly(field) || undefined}
|
||||
Label={CustomLabel}
|
||||
label={field && 'label' in field ? (field.label as StaticLabel) : undefined}
|
||||
name={'name' in field ? field.name : undefined}
|
||||
label={label as StaticLabel}
|
||||
name={dotAccessor}
|
||||
{...(sortColumnProps || {})}
|
||||
/>
|
||||
)
|
||||
|
||||
const column: Column = {
|
||||
accessor: 'name' in field ? field.name : undefined,
|
||||
accessor,
|
||||
active,
|
||||
CustomLabel,
|
||||
field,
|
||||
@@ -212,7 +233,7 @@ export const buildPolymorphicColumnState = (args: Args): Column[] => {
|
||||
|
||||
const cellClientProps: DefaultCellComponentProps = {
|
||||
...baseCellClientProps,
|
||||
cellData: 'name' in field ? doc[field.name] : undefined,
|
||||
cellData: 'name' in field ? findValueInDoc(doc, field.name) : undefined,
|
||||
link: isLinkedColumn,
|
||||
rowData: doc,
|
||||
}
|
||||
|
||||
20
packages/ui/src/providers/TableColumns/findValueInDoc.tsx
Normal file
20
packages/ui/src/providers/TableColumns/findValueInDoc.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export const findValueInDoc = (doc: Record<string, any>, targetName: string): any => {
|
||||
if (!doc || typeof doc !== 'object') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (targetName in doc) {
|
||||
return doc[targetName]
|
||||
}
|
||||
|
||||
for (const key in doc) {
|
||||
if (typeof doc[key] === 'object' && doc[key] !== null) {
|
||||
const result = findValueInDoc(doc[key], targetName)
|
||||
if (result !== undefined) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
@@ -118,9 +118,15 @@ export const renderTable = ({
|
||||
|
||||
const columns = columnsFromArgs
|
||||
? columnsFromArgs?.filter((column) =>
|
||||
flattenTopLevelFields(fields, true)?.some(
|
||||
(field) => 'name' in field && field.name === column.accessor,
|
||||
),
|
||||
flattenTopLevelFields(fields, {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
})?.some((field) => {
|
||||
const accessor =
|
||||
'accessor' in field ? field.accessor : 'name' in field ? field.name : undefined
|
||||
return accessor === column.accessor
|
||||
}),
|
||||
)
|
||||
: getInitialColumns(fields, useAsTitle, [])
|
||||
|
||||
@@ -139,9 +145,15 @@ export const renderTable = ({
|
||||
} else {
|
||||
const columns = columnsFromArgs
|
||||
? columnsFromArgs?.filter((column) =>
|
||||
flattenTopLevelFields(clientCollectionConfig.fields, true)?.some(
|
||||
(field) => 'name' in field && field.name === column.accessor,
|
||||
),
|
||||
flattenTopLevelFields(clientCollectionConfig.fields, {
|
||||
i18n,
|
||||
keepPresentationalFields: true,
|
||||
moveSubFieldsToTop: true,
|
||||
})?.some((field) => {
|
||||
const accessor =
|
||||
'accessor' in field ? field.accessor : 'name' in field ? field.name : undefined
|
||||
return accessor === column.accessor
|
||||
}),
|
||||
)
|
||||
: getInitialColumns(
|
||||
filterFields(clientCollectionConfig.fields),
|
||||
|
||||
Reference in New Issue
Block a user