fix(ui): executes filterOptions on the server (#5335)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { ClientValidate, Field } from '../../fields/config/types.js'
|
import type { ClientValidate, Field } from '../../fields/config/types.js'
|
||||||
|
import type { Where } from '../../types/index.js'
|
||||||
|
|
||||||
export type Data = {
|
export type Data = {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@@ -11,11 +12,16 @@ export type Row = {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FilterOptionsResult = {
|
||||||
|
[relation: string]: Where | boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type FormField = {
|
export type FormField = {
|
||||||
disableFormData?: boolean
|
disableFormData?: boolean
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
errorPaths?: Set<string>
|
errorPaths?: Set<string>
|
||||||
fieldSchema?: Field
|
fieldSchema?: Field
|
||||||
|
filterOptions?: FilterOptionsResult
|
||||||
initialValue: unknown
|
initialValue: unknown
|
||||||
passesCondition?: boolean
|
passesCondition?: boolean
|
||||||
rows?: Row[]
|
rows?: Row[]
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ export type {
|
|||||||
DescriptionComponent,
|
DescriptionComponent,
|
||||||
DescriptionFunction,
|
DescriptionFunction,
|
||||||
} from './forms/FieldDescription.js'
|
} from './forms/FieldDescription.js'
|
||||||
export type { Data, FormField, FormState, Row } from './forms/Form.js'
|
export type { Data, FilterOptionsResult, FormField, FormState, Row } from './forms/Form.js'
|
||||||
export type { LabelProps } from './forms/Label.js'
|
export type { LabelProps } from './forms/Label.js'
|
||||||
export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js'
|
export type { RowLabel, RowLabelComponent } from './forms/RowLabel.js'
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
AdminViewComponent,
|
AdminViewComponent,
|
||||||
AdminViewProps,
|
AdminViewProps,
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ export type ServerOnlyGlobalAdminProperties = keyof Pick<
|
|||||||
export type ServerOnlyLivePreviewProperties = keyof Pick<LivePreviewConfig, 'url'>
|
export type ServerOnlyLivePreviewProperties = keyof Pick<LivePreviewConfig, 'url'>
|
||||||
|
|
||||||
export type ServerOnlyFieldProperties =
|
export type ServerOnlyFieldProperties =
|
||||||
| 'editor'
|
| 'editor' // This is a `richText` only property
|
||||||
|
| 'filterOptions' // This is a `relationship` and `upload` only property
|
||||||
| 'label'
|
| 'label'
|
||||||
| keyof Pick<FieldBase, 'access' | 'defaultValue' | 'hooks' | 'validate'>
|
| keyof Pick<FieldBase, 'access' | 'defaultValue' | 'hooks' | 'validate'>
|
||||||
|
|
||||||
@@ -79,8 +80,8 @@ export const sanitizeField = (f: Field) => {
|
|||||||
'validate',
|
'validate',
|
||||||
'defaultValue',
|
'defaultValue',
|
||||||
'label',
|
'label',
|
||||||
// This is a `richText` only property
|
'filterOptions', // This is a `relationship` and `upload` only property
|
||||||
'editor',
|
'editor', // This is a `richText` only property
|
||||||
// `fields`
|
// `fields`
|
||||||
// `blocks`
|
// `blocks`
|
||||||
// `tabs`
|
// `tabs`
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import type { FilterOptions } from 'payload/types'
|
|
||||||
|
|
||||||
import equal from 'deep-equal'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js'
|
|
||||||
|
|
||||||
import { useAllFormFields } from '../../forms/Form/context.js'
|
|
||||||
import getSiblingData from '../../forms/Form/getSiblingData.js'
|
|
||||||
import reduceFieldsToValues from '../../forms/Form/reduceFieldsToValues.js'
|
|
||||||
import { getFilterOptionsQuery } from '../../forms/fields/getFilterOptionsQuery.js'
|
|
||||||
import { useAuth } from '../../providers/Auth/index.js'
|
|
||||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
|
||||||
|
|
||||||
type Args = {
|
|
||||||
filterOptions: FilterOptions
|
|
||||||
filterOptionsResult: FilterOptionsResult
|
|
||||||
path: string
|
|
||||||
relationTo: string | string[]
|
|
||||||
setFilterOptionsResult: (optionFilters: FilterOptionsResult) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GetFilterOptions = ({
|
|
||||||
filterOptions,
|
|
||||||
filterOptionsResult,
|
|
||||||
path,
|
|
||||||
relationTo,
|
|
||||||
setFilterOptionsResult,
|
|
||||||
}: Args): null => {
|
|
||||||
const [fields] = useAllFormFields()
|
|
||||||
const { id } = useDocumentInfo()
|
|
||||||
const { user } = useAuth()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const data = reduceFieldsToValues(fields, true)
|
|
||||||
const siblingData = getSiblingData(fields, path)
|
|
||||||
|
|
||||||
const getFilterOptions = async () => {
|
|
||||||
const newFilterOptionsResult = await getFilterOptionsQuery(filterOptions, {
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
relationTo,
|
|
||||||
siblingData,
|
|
||||||
user,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!equal(newFilterOptionsResult, filterOptionsResult)) {
|
|
||||||
setFilterOptionsResult(newFilterOptionsResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getFilterOptions()
|
|
||||||
}, [
|
|
||||||
fields,
|
|
||||||
filterOptions,
|
|
||||||
id,
|
|
||||||
relationTo,
|
|
||||||
user,
|
|
||||||
path,
|
|
||||||
filterOptionsResult,
|
|
||||||
setFilterOptionsResult,
|
|
||||||
])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
import type { FilterOptionsResult, SanitizedCollectionConfig } from 'payload/types'
|
||||||
import type React from 'react'
|
import type React from 'react'
|
||||||
import type { HTMLAttributes } from 'react'
|
import type { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
import type { FilterOptionsResult } from '../../forms/fields/Relationship/types.js'
|
|
||||||
|
|
||||||
export type ListDrawerProps = {
|
export type ListDrawerProps = {
|
||||||
collectionSlugs: string[]
|
collectionSlugs: string[]
|
||||||
customHeader?: React.ReactNode
|
customHeader?: React.ReactNode
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export { RenderFields } from '../forms/RenderFields/index.js'
|
|||||||
export { useRowLabel } from '../forms/RowLabel/Context/index.js'
|
export { useRowLabel } from '../forms/RowLabel/Context/index.js'
|
||||||
export { default as FormSubmit } from '../forms/Submit/index.js'
|
export { default as FormSubmit } from '../forms/Submit/index.js'
|
||||||
export { default as Submit } from '../forms/Submit/index.js'
|
export { default as Submit } from '../forms/Submit/index.js'
|
||||||
|
export { buildStateFromSchema } from '../forms/buildStateFromSchema/index.js'
|
||||||
|
export type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js'
|
||||||
export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js'
|
export { default as SectionTitle } from '../forms/fields/Blocks/SectionTitle/index.js'
|
||||||
export { CheckboxInput } from '../forms/fields/Checkbox/Input.js'
|
export { CheckboxInput } from '../forms/fields/Checkbox/Input.js'
|
||||||
export { default as Checkbox } from '../forms/fields/Checkbox/index.js'
|
export { default as Checkbox } from '../forms/fields/Checkbox/index.js'
|
||||||
@@ -38,16 +40,14 @@ export { default as Text } from '../forms/fields/Text/index.js'
|
|||||||
export type { Props as TextFieldProps } from '../forms/fields/Text/types.js'
|
export type { Props as TextFieldProps } from '../forms/fields/Text/types.js'
|
||||||
export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js'
|
export { type TextAreaInputProps, TextareaInput } from '../forms/fields/Textarea/Input.js'
|
||||||
export { default as Textarea } from '../forms/fields/Textarea/index.js'
|
export { default as Textarea } from '../forms/fields/Textarea/index.js'
|
||||||
|
|
||||||
export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js'
|
export { UploadInput, type UploadInputProps } from '../forms/fields/Upload/Input.js'
|
||||||
|
|
||||||
export { default as UploadField } from '../forms/fields/Upload/index.js'
|
export { default as UploadField } from '../forms/fields/Upload/index.js'
|
||||||
|
|
||||||
export { fieldTypes } from '../forms/fields/index.js'
|
export { fieldTypes } from '../forms/fields/index.js'
|
||||||
export { fieldBaseClass } from '../forms/fields/shared.js'
|
export { fieldBaseClass } from '../forms/fields/shared.js'
|
||||||
export { useField } from '../forms/useField/index.js'
|
export { useField } from '../forms/useField/index.js'
|
||||||
export type { FieldType, Options } from '../forms/useField/types.js'
|
export type { FieldType, Options } from '../forms/useField/types.js'
|
||||||
export { default as buildStateFromSchema } from '../forms/utilities/buildStateFromSchema/index.js'
|
|
||||||
export type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js'
|
|
||||||
export { withCondition } from '../forms/withCondition/index.js'
|
export { withCondition } from '../forms/withCondition/index.js'
|
||||||
|
|
||||||
export { buildComponentMap } from '../utilities/buildComponentMap/index.js'
|
export { buildComponentMap } from '../utilities/buildComponentMap/index.js'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ObjectIdImport from 'bson-objectid'
|
|||||||
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types'
|
import { fieldAffectsData, fieldHasSubFields, tabHasName } from 'payload/types'
|
||||||
import { getDefaultValue } from 'payload/utilities'
|
import { getDefaultValue } from 'payload/utilities'
|
||||||
|
|
||||||
|
import { getFilterOptionsQuery } from './getFilterOptionsQuery.js'
|
||||||
import { iterateFields } from './iterateFields.js'
|
import { iterateFields } from './iterateFields.js'
|
||||||
|
|
||||||
const ObjectId = (ObjectIdImport.default ||
|
const ObjectId = (ObjectIdImport.default ||
|
||||||
@@ -92,6 +93,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
|
|
||||||
if (fieldAffectsData(field)) {
|
if (fieldAffectsData(field)) {
|
||||||
const validate = operation === 'update' ? field.validate : undefined
|
const validate = operation === 'update' ? field.validate : undefined
|
||||||
|
|
||||||
const fieldState: FormField = {
|
const fieldState: FormField = {
|
||||||
errorPaths: new Set(),
|
errorPaths: new Set(),
|
||||||
fieldSchema: includeSchema ? field : undefined,
|
fieldSchema: includeSchema ? field : undefined,
|
||||||
@@ -375,6 +377,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'relationship': {
|
case 'relationship': {
|
||||||
|
if (typeof field.filterOptions === 'function') {
|
||||||
|
const query = await getFilterOptionsQuery(field.filterOptions, {
|
||||||
|
id,
|
||||||
|
data: fullData,
|
||||||
|
relationTo: field.relationTo,
|
||||||
|
siblingData: data,
|
||||||
|
user: req.user,
|
||||||
|
})
|
||||||
|
|
||||||
|
fieldState.filterOptions = query
|
||||||
|
}
|
||||||
|
|
||||||
if (field.hasMany) {
|
if (field.hasMany) {
|
||||||
const relationshipValue = Array.isArray(valueWithDefault)
|
const relationshipValue = Array.isArray(valueWithDefault)
|
||||||
? valueWithDefault.map((relationship) => {
|
? valueWithDefault.map((relationship) => {
|
||||||
@@ -433,6 +447,18 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'upload': {
|
case 'upload': {
|
||||||
|
if (typeof field.filterOptions === 'function') {
|
||||||
|
const query = await getFilterOptionsQuery(field.filterOptions, {
|
||||||
|
id,
|
||||||
|
data: fullData,
|
||||||
|
relationTo: field.relationTo,
|
||||||
|
siblingData: data,
|
||||||
|
user: req.user,
|
||||||
|
})
|
||||||
|
|
||||||
|
fieldState.filterOptions = query
|
||||||
|
}
|
||||||
|
|
||||||
const relationshipValue =
|
const relationshipValue =
|
||||||
valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault
|
valueWithDefault && typeof valueWithDefault === 'object' && 'id' in valueWithDefault
|
||||||
? valueWithDefault.id
|
? valueWithDefault.id
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-undef */
|
||||||
import buildStateFromSchema from './index.js'
|
import buildStateFromSchema from './index.js'
|
||||||
|
|
||||||
describe('Form - buildStateFromSchema', () => {
|
describe('Form - buildStateFromSchema', () => {
|
||||||
@@ -5,8 +5,11 @@ export const getFilterOptionsQuery = async (
|
|||||||
options: Omit<FilterOptionsProps, 'relationTo'> & { relationTo: string | string[] },
|
options: Omit<FilterOptionsProps, 'relationTo'> & { relationTo: string | string[] },
|
||||||
): Promise<{ [collection: string]: Where }> => {
|
): Promise<{ [collection: string]: Where }> => {
|
||||||
const { relationTo } = options
|
const { relationTo } = options
|
||||||
|
|
||||||
const relations = Array.isArray(relationTo) ? relationTo : [relationTo]
|
const relations = Array.isArray(relationTo) ? relationTo : [relationTo]
|
||||||
|
|
||||||
const query = {}
|
const query = {}
|
||||||
|
|
||||||
if (typeof filterOptions !== 'undefined') {
|
if (typeof filterOptions !== 'undefined') {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
relations.map(async (relation) => {
|
relations.map(async (relation) => {
|
||||||
@@ -14,9 +17,11 @@ export const getFilterOptionsQuery = async (
|
|||||||
typeof filterOptions === 'function'
|
typeof filterOptions === 'function'
|
||||||
? await filterOptions({ ...options, relationTo: relation })
|
? await filterOptions({ ...options, relationTo: relation })
|
||||||
: filterOptions
|
: filterOptions
|
||||||
|
|
||||||
if (query[relation] === true) {
|
if (query[relation] === true) {
|
||||||
query[relation] = {}
|
query[relation] = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is an ugly way to prevent results from being returned
|
// this is an ugly way to prevent results from being returned
|
||||||
if (query[relation] === false) {
|
if (query[relation] === false) {
|
||||||
query[relation] = { id: { exists: false } }
|
query[relation] = { id: { exists: false } }
|
||||||
@@ -24,5 +29,6 @@ export const getFilterOptionsQuery = async (
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ export type BuildFormStateArgs = {
|
|||||||
schemaPath: string
|
schemaPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
export const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
||||||
const { id, data: fullData = {}, fieldSchema, operation, preferences, req } = args
|
const { id, data: fullData = {}, fieldSchema, operation, preferences, req } = args
|
||||||
|
|
||||||
if (fieldSchema) {
|
if (fieldSchema) {
|
||||||
@@ -49,5 +49,3 @@ const buildStateFromSchema = async (args: Args): Promise<FormState> => {
|
|||||||
|
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildStateFromSchema
|
|
||||||
@@ -7,9 +7,8 @@ import qs from 'qs'
|
|||||||
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'
|
||||||
|
|
||||||
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
||||||
import type { FilterOptionsResult, GetResults, Option, Props, Value } from './types.js'
|
import type { GetResults, Option, Props, Value } from './types.js'
|
||||||
|
|
||||||
import { GetFilterOptions } from '../../../elements/GetFilterOptions/index.js'
|
|
||||||
import ReactSelect from '../../../elements/ReactSelect/index.js'
|
import ReactSelect from '../../../elements/ReactSelect/index.js'
|
||||||
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js'
|
import { useDebouncedCallback } from '../../../hooks/useDebouncedCallback.js'
|
||||||
import { useAuth } from '../../../providers/Auth/index.js'
|
import { useAuth } from '../../../providers/Auth/index.js'
|
||||||
@@ -57,7 +56,6 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
const relationTo = 'relationTo' in props ? props?.relationTo : undefined
|
const relationTo = 'relationTo' in props ? props?.relationTo : undefined
|
||||||
const hasMany = 'hasMany' in props ? props?.hasMany : undefined
|
const hasMany = 'hasMany' in props ? props?.hasMany : undefined
|
||||||
const filterOptions = 'filterOptions' in props ? props?.filterOptions : undefined
|
|
||||||
const sortOptions = 'sortOptions' in props ? props?.sortOptions : undefined
|
const sortOptions = 'sortOptions' in props ? props?.sortOptions : undefined
|
||||||
const isSortable = 'isSortable' in props ? props?.isSortable : true
|
const isSortable = 'isSortable' in props ? props?.isSortable : true
|
||||||
const allowCreate = 'allowCreate' in props ? props?.allowCreate : true
|
const allowCreate = 'allowCreate' in props ? props?.allowCreate : true
|
||||||
@@ -71,7 +69,6 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1)
|
const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1)
|
||||||
const [lastLoadedPage, setLastLoadedPage] = useState<Record<string, number>>({})
|
const [lastLoadedPage, setLastLoadedPage] = useState<Record<string, number>>({})
|
||||||
const [errorLoading, setErrorLoading] = useState('')
|
const [errorLoading, setErrorLoading] = useState('')
|
||||||
const [filterOptionsResult, setFilterOptionsResult] = useState<FilterOptionsResult>()
|
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [hasLoadedFirstPage, setHasLoadedFirstPage] = useState(false)
|
const [hasLoadedFirstPage, setHasLoadedFirstPage] = useState(false)
|
||||||
@@ -87,7 +84,9 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
[validate, required],
|
[validate, required],
|
||||||
)
|
)
|
||||||
|
|
||||||
const { initialValue, path, setValue, showError, value } = useField<Value | Value[]>({
|
const { filterOptions, initialValue, path, setValue, showError, value } = useField<
|
||||||
|
Value | Value[]
|
||||||
|
>({
|
||||||
path: pathFromProps || name,
|
path: pathFromProps || name,
|
||||||
validate: memoizedValidate,
|
validate: memoizedValidate,
|
||||||
})
|
})
|
||||||
@@ -123,7 +122,8 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
if (!errorLoading) {
|
if (!errorLoading) {
|
||||||
await relationsToFetch.reduce(async (priorRelation, relation) => {
|
await relationsToFetch.reduce(async (priorRelation, relation) => {
|
||||||
const relationFilterOption = filterOptionsResult?.[relation]
|
const relationFilterOption = filterOptions?.[relation]
|
||||||
|
|
||||||
let lastLoadedPageToUse
|
let lastLoadedPageToUse
|
||||||
if (search !== searchArg) {
|
if (search !== searchArg) {
|
||||||
lastLoadedPageToUse = 1
|
lastLoadedPageToUse = 1
|
||||||
@@ -246,7 +246,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
lastLoadedPage,
|
lastLoadedPage,
|
||||||
collections,
|
collections,
|
||||||
locale,
|
locale,
|
||||||
filterOptionsResult,
|
filterOptions,
|
||||||
serverURL,
|
serverURL,
|
||||||
sortOptions,
|
sortOptions,
|
||||||
api,
|
api,
|
||||||
@@ -362,7 +362,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
setEnableWordBoundarySearch(!isIdOnly)
|
setEnableWordBoundarySearch(!isIdOnly)
|
||||||
}, [relationTo, collections])
|
}, [relationTo, collections])
|
||||||
|
|
||||||
// When (`relationTo` || `filterOptionsResult` || `locale`) changes, reset component
|
// When (`relationTo` || `filterOptions` || `locale`) changes, reset component
|
||||||
// Note - effect should not run on first run
|
// Note - effect should not run on first run
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (firstRun.current) {
|
if (firstRun.current) {
|
||||||
@@ -374,7 +374,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
setLastFullyLoadedRelation(-1)
|
setLastFullyLoadedRelation(-1)
|
||||||
setLastLoadedPage({})
|
setLastLoadedPage({})
|
||||||
setHasLoadedFirstPage(false)
|
setHasLoadedFirstPage(false)
|
||||||
}, [relationTo, filterOptionsResult, locale])
|
}, [relationTo, filterOptions, locale])
|
||||||
|
|
||||||
const onSave = useCallback<DocumentDrawerProps['onSave']>(
|
const onSave = useCallback<DocumentDrawerProps['onSave']>(
|
||||||
(args) => {
|
(args) => {
|
||||||
@@ -435,15 +435,6 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
>
|
>
|
||||||
{Error}
|
{Error}
|
||||||
{Label}
|
{Label}
|
||||||
<GetFilterOptions
|
|
||||||
{...{
|
|
||||||
filterOptions,
|
|
||||||
filterOptionsResult,
|
|
||||||
path,
|
|
||||||
relationTo,
|
|
||||||
setFilterOptionsResult,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{!errorLoading && (
|
{!errorLoading && (
|
||||||
<div className={`${baseClass}__wrap`}>
|
<div className={`${baseClass}__wrap`}>
|
||||||
<ReactSelect
|
<ReactSelect
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { I18n } from '@payloadcms/translations'
|
import type { I18n } from '@payloadcms/translations'
|
||||||
import type { SanitizedCollectionConfig } from 'payload/types'
|
import type { SanitizedCollectionConfig } from 'payload/types'
|
||||||
import type { SanitizedConfig } from 'payload/types'
|
import type { SanitizedConfig } from 'payload/types'
|
||||||
import type { Where } from 'payload/types'
|
|
||||||
|
|
||||||
import type { FormFieldBase } from '../shared.js'
|
import type { FormFieldBase } from '../shared.js'
|
||||||
|
|
||||||
@@ -59,7 +58,3 @@ export type GetResults = (args: {
|
|||||||
sort?: boolean
|
sort?: boolean
|
||||||
value?: Value | Value[]
|
value?: Value | Value[]
|
||||||
}) => Promise<void>
|
}) => Promise<void>
|
||||||
|
|
||||||
export type FilterOptionsResult = {
|
|
||||||
[relation: string]: Where | boolean
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { SanitizedCollectionConfig, UploadField } from 'payload/types'
|
import type { FilterOptionsResult, SanitizedCollectionConfig, UploadField } from 'payload/types'
|
||||||
|
|
||||||
import { getTranslation } from '@payloadcms/translations'
|
import { getTranslation } from '@payloadcms/translations'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
import type { DocumentDrawerProps } from '../../../elements/DocumentDrawer/types.js'
|
||||||
import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js'
|
import type { ListDrawerProps } from '../../../elements/ListDrawer/types.js'
|
||||||
import type { FilterOptionsResult } from '../Relationship/types.js'
|
|
||||||
|
|
||||||
import { Button } from '../../../elements/Button/index.js'
|
import { Button } from '../../../elements/Button/index.js'
|
||||||
import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js'
|
import { useDocumentDrawer } from '../../../elements/DocumentDrawer/index.js'
|
||||||
@@ -23,7 +22,7 @@ const baseClass = 'upload'
|
|||||||
export type UploadInputProps = FormFieldBase & {
|
export type UploadInputProps = FormFieldBase & {
|
||||||
api?: string
|
api?: string
|
||||||
collection?: SanitizedCollectionConfig
|
collection?: SanitizedCollectionConfig
|
||||||
filterOptions?: UploadField['filterOptions']
|
filterOptions?: FilterOptionsResult
|
||||||
onChange?: (e) => void
|
onChange?: (e) => void
|
||||||
relationTo?: UploadField['relationTo']
|
relationTo?: UploadField['relationTo']
|
||||||
serverURL?: string
|
serverURL?: string
|
||||||
@@ -34,12 +33,12 @@ export type UploadInputProps = FormFieldBase & {
|
|||||||
export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
Description,
|
Description,
|
||||||
|
|
||||||
Error,
|
Error,
|
||||||
Label: LabelFromProps,
|
Label: LabelFromProps,
|
||||||
api = '/api',
|
api = '/api',
|
||||||
className,
|
className,
|
||||||
collection,
|
collection,
|
||||||
|
filterOptions,
|
||||||
label,
|
label,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly,
|
readOnly,
|
||||||
@@ -59,7 +58,6 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
|||||||
const [file, setFile] = useState(undefined)
|
const [file, setFile] = useState(undefined)
|
||||||
const [missingFile, setMissingFile] = useState(false)
|
const [missingFile, setMissingFile] = useState(false)
|
||||||
const [collectionSlugs] = useState([collection?.slug])
|
const [collectionSlugs] = useState([collection?.slug])
|
||||||
const [filterOptionsResult, setFilterOptionsResult] = useState<FilterOptionsResult>()
|
|
||||||
|
|
||||||
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
const [DocumentDrawer, DocumentDrawerToggler, { closeDrawer }] = useDocumentDrawer({
|
||||||
collectionSlug: collectionSlugs[0],
|
collectionSlug: collectionSlugs[0],
|
||||||
@@ -67,7 +65,7 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
|||||||
|
|
||||||
const [ListDrawer, ListDrawerToggler, { closeDrawer: closeListDrawer }] = useListDrawer({
|
const [ListDrawer, ListDrawerToggler, { closeDrawer: closeListDrawer }] = useListDrawer({
|
||||||
collectionSlugs,
|
collectionSlugs,
|
||||||
filterOptions: filterOptionsResult,
|
filterOptions,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -131,15 +129,6 @@ export const UploadInput: React.FC<UploadInputProps> = (props) => {
|
|||||||
width,
|
width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* <GetFilterOptions
|
|
||||||
{...{
|
|
||||||
filterOptions,
|
|
||||||
filterOptionsResult,
|
|
||||||
path,
|
|
||||||
relationTo,
|
|
||||||
setFilterOptionsResult,
|
|
||||||
}}
|
|
||||||
/> */}
|
|
||||||
{Error}
|
{Error}
|
||||||
{Label}
|
{Label}
|
||||||
{collection?.upload && (
|
{collection?.upload && (
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const Upload: React.FC<Props> = (props) => {
|
|||||||
Error,
|
Error,
|
||||||
Label: LabelFromProps,
|
Label: LabelFromProps,
|
||||||
className,
|
className,
|
||||||
filterOptions,
|
|
||||||
label,
|
label,
|
||||||
path: pathFromProps,
|
path: pathFromProps,
|
||||||
readOnly,
|
readOnly,
|
||||||
@@ -45,7 +44,7 @@ const Upload: React.FC<Props> = (props) => {
|
|||||||
[validate, required],
|
[validate, required],
|
||||||
)
|
)
|
||||||
|
|
||||||
const { path, setValue, showError, value } = useField<string>({
|
const { filterOptions, path, setValue, showError, value } = useField<string>({
|
||||||
path: pathFromProps,
|
path: pathFromProps,
|
||||||
validate: memoizedValidate,
|
validate: memoizedValidate,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { FieldPermissions, User } from 'payload/auth'
|
import type { User } from 'payload/auth'
|
||||||
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
|
import type { Locale, SanitizedLocalizationConfig } from 'payload/config'
|
||||||
import type {
|
import type {
|
||||||
ArrayField,
|
ArrayField,
|
||||||
@@ -51,6 +51,21 @@ export type FormFieldBase = {
|
|||||||
validate?: Validate
|
validate?: Validate
|
||||||
width?: string
|
width?: string
|
||||||
} & (
|
} & (
|
||||||
|
| {
|
||||||
|
// For `array` fields
|
||||||
|
label?: RowLabel
|
||||||
|
labels?: ArrayField['labels']
|
||||||
|
maxRows?: ArrayField['maxRows']
|
||||||
|
minRows?: ArrayField['minRows']
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
// For `blocks` fields
|
||||||
|
blocks?: ReducedBlock[]
|
||||||
|
labels?: BlockField['labels']
|
||||||
|
maxRows?: BlockField['maxRows']
|
||||||
|
minRows?: BlockField['minRows']
|
||||||
|
slug?: string
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
// For `code` fields
|
// For `code` fields
|
||||||
editorOptions?: CodeField['admin']['editorOptions']
|
editorOptions?: CodeField['admin']['editorOptions']
|
||||||
@@ -68,11 +83,25 @@ export type FormFieldBase = {
|
|||||||
// For `json` fields
|
// For `json` fields
|
||||||
editorOptions?: JSONField['admin']['editorOptions']
|
editorOptions?: JSONField['admin']['editorOptions']
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
// For `number` fields
|
||||||
|
hasMany?: boolean
|
||||||
|
max?: number
|
||||||
|
maxRows?: number
|
||||||
|
min?: number
|
||||||
|
step?: number
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
// For `radio` fields
|
// For `radio` fields
|
||||||
layout?: 'horizontal' | 'vertical'
|
layout?: 'horizontal' | 'vertical'
|
||||||
options?: Option[]
|
options?: Option[]
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
// For `relationship` fields
|
||||||
|
allowCreate?: RelationshipField['admin']['allowCreate']
|
||||||
|
relationTo?: RelationshipField['relationTo']
|
||||||
|
sortOptions?: RelationshipField['admin']['sortOptions']
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
// For `richText` fields
|
// For `richText` fields
|
||||||
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
|
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
|
||||||
@@ -90,36 +119,6 @@ export type FormFieldBase = {
|
|||||||
// For `upload` fields
|
// For `upload` fields
|
||||||
relationTo?: UploadField['relationTo']
|
relationTo?: UploadField['relationTo']
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
allowCreate?: RelationshipField['admin']['allowCreate']
|
|
||||||
filterOptions?: RelationshipField['filterOptions']
|
|
||||||
// For `relationship` fields
|
|
||||||
relationTo?: RelationshipField['relationTo']
|
|
||||||
sortOptions?: RelationshipField['admin']['sortOptions']
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
blocks?: ReducedBlock[]
|
|
||||||
labels?: BlockField['labels']
|
|
||||||
maxRows?: BlockField['maxRows']
|
|
||||||
minRows?: BlockField['minRows']
|
|
||||||
// For `blocks` fields
|
|
||||||
slug?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
hasMany?: boolean
|
|
||||||
max?: number
|
|
||||||
maxRows?: number
|
|
||||||
min?: number
|
|
||||||
// For `number` fields
|
|
||||||
step?: number
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
label?: RowLabel
|
|
||||||
labels?: ArrayField['labels']
|
|
||||||
maxRows?: ArrayField['maxRows']
|
|
||||||
// For `array` fields
|
|
||||||
minRows?: ArrayField['minRows']
|
|
||||||
}
|
|
||||||
| {
|
| {
|
||||||
tabs?: MappedTab[]
|
tabs?: MappedTab[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
|||||||
|
|
||||||
const { getData, getDataByPath, getSiblingData, setModified } = useForm()
|
const { getData, getDataByPath, getSiblingData, setModified } = useForm()
|
||||||
|
|
||||||
|
const filterOptions = field?.filterOptions
|
||||||
const value = field?.value as T
|
const value = field?.value as T
|
||||||
const initialValue = field?.initialValue as T
|
const initialValue = field?.initialValue as T
|
||||||
const valid = typeof field?.valid === 'boolean' ? field.valid : true
|
const valid = typeof field?.valid === 'boolean' ? field.valid : true
|
||||||
@@ -79,6 +80,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
|||||||
const result: FieldType<T> = useMemo(
|
const result: FieldType<T> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
errorMessage: field?.errorMessage,
|
errorMessage: field?.errorMessage,
|
||||||
|
filterOptions,
|
||||||
formProcessing: processing,
|
formProcessing: processing,
|
||||||
formSubmitted: submitted,
|
formSubmitted: submitted,
|
||||||
initialValue,
|
initialValue,
|
||||||
@@ -106,6 +108,7 @@ export const useField = <T,>(options: Options): FieldType<T> => {
|
|||||||
schemaPath,
|
schemaPath,
|
||||||
readOnly,
|
readOnly,
|
||||||
permissions,
|
permissions,
|
||||||
|
filterOptions,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ClientValidate, FieldPermissions, Row } from 'payload/types'
|
import type { ClientValidate, FieldPermissions, FilterOptionsResult, Row } from 'payload/types'
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
disableFormData?: boolean
|
disableFormData?: boolean
|
||||||
@@ -12,6 +12,7 @@ export type Options = {
|
|||||||
|
|
||||||
export type FieldType<T> = {
|
export type FieldType<T> = {
|
||||||
errorMessage?: string
|
errorMessage?: string
|
||||||
|
filterOptions?: FilterOptionsResult
|
||||||
formProcessing: boolean
|
formProcessing: boolean
|
||||||
formSubmitted: boolean
|
formSubmitted: boolean
|
||||||
initialValue?: T
|
initialValue?: T
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { FormState, SanitizedConfig } from 'payload/types'
|
import type { FormState, SanitizedConfig } from 'payload/types'
|
||||||
|
|
||||||
import type { BuildFormStateArgs } from '../forms/utilities/buildStateFromSchema/index.js'
|
import type { BuildFormStateArgs } from '../forms/buildStateFromSchema/index.js'
|
||||||
|
|
||||||
export const getFormState = async (args: {
|
export const getFormState = async (args: {
|
||||||
apiRoute: SanitizedConfig['routes']['api']
|
apiRoute: SanitizedConfig['routes']['api']
|
||||||
|
|||||||
@@ -21,6 +21,17 @@ export const PostsCollection: CollectionConfig = {
|
|||||||
{
|
{
|
||||||
name: 'relationship',
|
name: 'relationship',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
|
filterOptions: ({ id }) => {
|
||||||
|
return {
|
||||||
|
where: [
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
not_equals: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
relationTo: ['posts'],
|
relationTo: ['posts'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
"./packages/graphql/src"
|
"./packages/graphql/src"
|
||||||
],
|
],
|
||||||
"@payload-config": [
|
"@payload-config": [
|
||||||
"./test/access-control/config.ts"
|
"./test/_community/config.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user