Skip to content
Open
4 changes: 4 additions & 0 deletions packages/next/src/views/List/handleGroupBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
PaginatedDocs,
PayloadRequest,
SanitizedCollectionConfig,
SanitizedFieldsPermissions,
SelectType,
ViewTypes,
Where,
Expand All @@ -28,6 +29,7 @@ export const handleGroupBy = async ({
customCellProps,
drawerSlug,
enableRowSelections,
fieldPermissions,
query,
req,
select,
Expand All @@ -44,6 +46,7 @@ export const handleGroupBy = async ({
customCellProps?: Record<string, any>
drawerSlug?: string
enableRowSelections?: boolean
fieldPermissions?: SanitizedFieldsPermissions
query?: ListQuery
req: PayloadRequest
select?: SelectType
Expand Down Expand Up @@ -183,6 +186,7 @@ export const handleGroupBy = async ({
data: groupData,
drawerSlug,
enableRowSelections,
fieldPermissions,
groupByFieldPath,
groupByValue: serializableValue,
heading: heading || req.i18n.t('general:noValue'),
Expand Down
3 changes: 3 additions & 0 deletions packages/next/src/views/List/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const renderListView = async (
collectionConfig: clientCollectionConfig,
collectionSlug,
columns: collectionPreferences?.columns,
fieldPermissions: permissions?.collections?.[collectionSlug]?.fields,
i18n,
})

Expand All @@ -259,6 +260,7 @@ export const renderListView = async (
customCellProps,
drawerSlug,
enableRowSelections,
fieldPermissions: permissions?.collections?.[collectionSlug]?.fields,
query,
req,
select,
Expand Down Expand Up @@ -293,6 +295,7 @@ export const renderListView = async (
data,
drawerSlug,
enableRowSelections,
fieldPermissions: permissions?.collections?.[collectionSlug]?.fields,
i18n: req.i18n,
orderableFieldName: collectionConfig.orderable === true ? '_order' : undefined,
payload: req.payload,
Expand Down
14 changes: 13 additions & 1 deletion packages/ui/src/elements/GroupByBuilder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './index.scss'
import React, { useMemo } from 'react'

import { SelectInput } from '../../fields/Select/Input.js'
import { useAuth } from '../../providers/Auth/index.js'
import { useListQuery } from '../../providers/ListQuery/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { reduceFieldsToOptions } from '../../utilities/reduceFieldsToOptions.js'
Expand Down Expand Up @@ -41,8 +42,19 @@ const supportedFieldTypes: Field['type'][] = [

export const GroupByBuilder: React.FC<Props> = ({ collectionSlug, fields }) => {
const { i18n, t } = useTranslation()
const { permissions } = useAuth()

const reducedFields = useMemo(() => reduceFieldsToOptions({ fields, i18n }), [fields, i18n])
const fieldPermissions = permissions?.collections?.[collectionSlug]?.fields

const reducedFields = useMemo(
() =>
reduceFieldsToOptions({
fieldPermissions,
fields,
i18n,
}),
[fields, fieldPermissions, i18n],
)

const { query, refineListData } = useListQuery()

Expand Down
19 changes: 16 additions & 3 deletions packages/ui/src/elements/WhereBuilder/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import React, { useMemo } from 'react'

import type { AddCondition, RemoveCondition, UpdateCondition, WhereBuilderProps } from './types.js'

import { useAuth } from '../../providers/Auth/index.js'
import { useListQuery } from '../../providers/ListQuery/index.js'
import { useTranslation } from '../../providers/Translation/index.js'
import { reduceFieldsToOptions } from '../../utilities/reduceFieldsToOptions.js'
Expand All @@ -24,10 +25,22 @@ 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, fields, renderedFilters, resolvedFilterOptions } = props
const { collectionPluralLabel, collectionSlug, fields, renderedFilters, resolvedFilterOptions } =
props
const { i18n, t } = useTranslation()

const reducedFields = useMemo(() => reduceFieldsToOptions({ fields, i18n }), [fields, i18n])
const { permissions } = useAuth()

const fieldPermissions = permissions?.collections?.[collectionSlug]?.fields

const reducedFields = useMemo(
() =>
reduceFieldsToOptions({
fieldPermissions,
fields,
i18n,
}),
[fieldPermissions, fields, i18n],
)

const { handleWhereChange, query } = useListQuery()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type {
ClientField,
Field,
SanitizedFieldPermissions,
SanitizedFieldsPermissions,
} from 'payload'

import { fieldAffectsData, fieldIsHiddenOrDisabled, fieldIsID } from 'payload/shared'

export const filterFieldsWithPermissions = <T extends ClientField | Field>({
fieldPermissions,
fields,
}: {
fieldPermissions?: SanitizedFieldPermissions | SanitizedFieldsPermissions
fields: T[]
}): T[] => {
const shouldSkipField = (field: T): boolean =>
(field.type !== 'ui' && fieldIsHiddenOrDisabled(field) && !fieldIsID(field)) ||
field?.admin?.disableListColumn === true

return (fields ?? []).reduce<T[]>((acc, field) => {
if (shouldSkipField(field)) {
return acc
}

// handle tabs
if (field.type === 'tabs' && 'tabs' in field) {
const formattedField: T = {
...field,
tabs: field.tabs.map((tab) => ({
...tab,
fields: filterFieldsWithPermissions({
fieldPermissions:
typeof fieldPermissions === 'boolean'
? fieldPermissions
: 'name' in tab && tab.name
? fieldPermissions[tab.name]?.fields || fieldPermissions[tab.name]
: fieldPermissions,
fields: tab.fields,
}),
})),
}
acc.push(formattedField)
return acc
}

// handle fields with subfields (row, group, collapsible, etc.)
if ('fields' in field && Array.isArray(field.fields)) {
const formattedField: T = {
...field,
fields: filterFieldsWithPermissions({
fieldPermissions:
typeof fieldPermissions === 'boolean'
? fieldPermissions
: 'name' in field && field.name
? fieldPermissions[field.name]?.fields || fieldPermissions[field.name]
: fieldPermissions,
fields: field.fields as T[],
}),
}
acc.push(formattedField)
return acc
}

if (fieldPermissions === true) {
acc.push(field)
return acc
}

if (fieldAffectsData(field)) {
if (fieldPermissions[field.name] === true || fieldPermissions[field.name]?.read) {
acc.push(field)
}
return acc
}

// leaf
acc.push(field)
return acc
}, [])
}
33 changes: 21 additions & 12 deletions packages/ui/src/providers/TableColumns/buildColumnState/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
Payload,
PayloadRequest,
SanitizedCollectionConfig,
SanitizedFieldsPermissions,
ServerComponentProps,
StaticLabel,
ViewTypes,
Expand All @@ -32,7 +33,7 @@ import {
SortColumn,
// eslint-disable-next-line payload/no-imports-from-exports-dir -- MUST reference the exports dir: https://github.com/payloadcms/payload/issues/12002#issuecomment-2791493587
} from '../../../exports/client/index.js'
import { filterFields } from './filterFields.js'
import { filterFieldsWithPermissions } from './filterFieldsWithPermissions.js'
import { isColumnActive } from './isColumnActive.js'
import { renderCell } from './renderCell.js'
import { sortFieldMap } from './sortFieldMap.js'
Expand All @@ -45,6 +46,7 @@ export type BuildColumnStateArgs = {
enableLinkedCell?: boolean
enableRowSelections: boolean
enableRowTypes?: boolean
fieldPermissions?: SanitizedFieldsPermissions
i18n: I18nClient
payload: Payload
req?: PayloadRequest
Expand Down Expand Up @@ -79,6 +81,7 @@ export const buildColumnState = (args: BuildColumnStateArgs): Column[] => {
docs,
enableLinkedCell = true,
enableRowSelections,
fieldPermissions,
i18n,
payload,
req,
Expand All @@ -89,17 +92,23 @@ export const buildColumnState = (args: BuildColumnStateArgs): Column[] => {
} = args

// clientFields contains the fake `id` column
let sortedFieldMap = flattenTopLevelFields(filterFields(clientFields), {
i18n,
keepPresentationalFields: true,
moveSubFieldsToTop: true,
}) as ClientField[]

let _sortedFieldMap = flattenTopLevelFields(filterFields(serverFields), {
i18n,
keepPresentationalFields: true,
moveSubFieldsToTop: true,
}) as Field[] // TODO: think of a way to avoid this additional flatten
let sortedFieldMap = flattenTopLevelFields(
filterFieldsWithPermissions({ fieldPermissions, fields: clientFields }),
{
i18n,
keepPresentationalFields: true,
moveSubFieldsToTop: true,
},
) as ClientField[]

let _sortedFieldMap = flattenTopLevelFields(
filterFieldsWithPermissions({ fieldPermissions, fields: serverFields }),
{
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
Expand Down
10 changes: 9 additions & 1 deletion packages/ui/src/utilities/buildTableState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
Where,
} from 'payload'

import { APIError, canAccessAdmin, formatErrors } from 'payload'
import { APIError, canAccessAdmin, formatErrors, getAccessResults } from 'payload'
import { isNumber } from 'payload/shared'

import { getClientConfig } from './getClientConfig.js'
Expand Down Expand Up @@ -100,6 +100,8 @@ const buildTableState = async (
user,
})

const permissions = await getAccessResults({ req })

let collectionConfig: SanitizedCollectionConfig
let clientCollectionConfig: ClientCollectionConfig

Expand Down Expand Up @@ -204,10 +206,16 @@ const buildTableState = async (
collectionConfig: clientCollectionConfig,
collectionSlug,
columns: columnsFromArgs,
fieldPermissions: Array.isArray(collectionSlug)
? undefined
: permissions?.collections?.[collectionSlug]?.fields,
i18n: req.i18n,
}),
data,
enableRowSelections,
fieldPermissions: Array.isArray(collectionSlug)
? undefined
: permissions?.collections?.[collectionSlug]?.fields,
i18n: req.i18n,
orderableFieldName,
payload,
Expand Down
18 changes: 14 additions & 4 deletions packages/ui/src/utilities/getColumns.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import type { I18nClient } from '@payloadcms/translations'
import type { ClientCollectionConfig, ClientConfig, ColumnPreference } from 'payload'
import type {
ClientCollectionConfig,
ClientConfig,
ColumnPreference,
SanitizedFieldsPermissions,
} from 'payload'

import { flattenTopLevelFields } from 'payload'
import { fieldAffectsData } from 'payload/shared'

import { filterFields } from '../providers/TableColumns/buildColumnState/filterFields.js'
import { filterFieldsWithPermissions } from '../providers/TableColumns/buildColumnState/filterFieldsWithPermissions.js'
import { getInitialColumns } from '../providers/TableColumns/getInitialColumns.js'

export const getColumns = ({
clientConfig,
collectionConfig,
collectionSlug,
columns,
fieldPermissions,
i18n,
}: {
clientConfig: ClientConfig
collectionConfig?: ClientCollectionConfig
collectionSlug: string | string[]
columns: ColumnPreference[]
fieldPermissions: SanitizedFieldsPermissions
i18n: I18nClient
}) => {
const isPolymorphic = Array.isArray(collectionSlug)
Expand All @@ -30,7 +37,10 @@ export const getColumns = ({
(each) => each.slug === collection,
)

for (const field of filterFields(clientCollectionConfig.fields)) {
for (const field of filterFieldsWithPermissions({
fieldPermissions,
fields: clientCollectionConfig.fields,
})) {
if (fieldAffectsData(field)) {
if (fields.some((each) => fieldAffectsData(each) && each.name === field.name)) {
continue
Expand All @@ -55,7 +65,7 @@ export const getColumns = ({
}),
)
: getInitialColumns(
isPolymorphic ? fields : filterFields(fields),
isPolymorphic ? fields : filterFieldsWithPermissions({ fieldPermissions, fields }),
collectionConfig?.admin?.useAsTitle,
isPolymorphic ? [] : collectionConfig?.admin?.defaultColumns,
)
Expand Down
Loading
Loading