merge: relationship options filter
This commit is contained in:
@@ -185,6 +185,10 @@ export const upload = baseField.keys({
|
||||
relationTo: joi.string().required(),
|
||||
name: joi.string().required(),
|
||||
maxDepth: joi.number(),
|
||||
filterOptions: joi.alternatives().try(
|
||||
joi.object(),
|
||||
joi.func(),
|
||||
),
|
||||
});
|
||||
|
||||
export const checkbox = baseField.keys({
|
||||
@@ -208,6 +212,10 @@ export const relationship = baseField.keys({
|
||||
),
|
||||
name: joi.string().required(),
|
||||
maxDepth: joi.number(),
|
||||
filterOptions: joi.alternatives().try(
|
||||
joi.object(),
|
||||
joi.func(),
|
||||
),
|
||||
});
|
||||
|
||||
export const blocks = baseField.keys({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { CSSProperties } from 'react';
|
||||
import { Editor } from 'slate';
|
||||
import { Operation } from '../../types';
|
||||
import { Operation, Where } from '../../types';
|
||||
import { TypeWithID } from '../../collections/config/types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
|
||||
@@ -29,6 +29,16 @@ export type FieldAccess<T extends TypeWithID = any, P = any> = (args: {
|
||||
|
||||
export type Condition<T extends TypeWithID = any, P = any> = (data: Partial<T>, siblingData: Partial<P>) => boolean;
|
||||
|
||||
export type FilterOptionsProps = {
|
||||
id: string | number,
|
||||
user: Partial<User>,
|
||||
data: unknown,
|
||||
siblingData: unknown,
|
||||
relationTo: string | string[],
|
||||
}
|
||||
|
||||
export type FilterOptions = Where | ((options: FilterOptionsProps) => Where);
|
||||
|
||||
type Admin = {
|
||||
position?: 'sidebar';
|
||||
width?: string;
|
||||
@@ -183,6 +193,7 @@ export type UploadField = FieldBase & {
|
||||
type: 'upload'
|
||||
relationTo: string
|
||||
maxDepth?: number
|
||||
filterOptions?: FilterOptions;
|
||||
}
|
||||
|
||||
type CodeAdmin = Admin & {
|
||||
@@ -207,8 +218,19 @@ export type RelationshipField = FieldBase & {
|
||||
relationTo: string | string[];
|
||||
hasMany?: boolean;
|
||||
maxDepth?: number;
|
||||
filterOptions?: FilterOptions;
|
||||
}
|
||||
|
||||
export type ValueWithRelation = {
|
||||
relationTo: string
|
||||
value: string | number
|
||||
}
|
||||
|
||||
export type RelationshipValue = (string | number)
|
||||
| (string | number)[]
|
||||
| ValueWithRelation
|
||||
| ValueWithRelation[]
|
||||
|
||||
type RichTextPlugin = (editor: Editor) => Editor;
|
||||
|
||||
export type RichTextCustomElement = {
|
||||
|
||||
@@ -116,8 +116,8 @@ export default async function performFieldOperations(this: Payload, entityConfig
|
||||
const hookResults = hookPromises.map((promise) => promise());
|
||||
await Promise.all(hookResults);
|
||||
|
||||
validationPromises.forEach((promise) => promise());
|
||||
await Promise.all(validationPromises);
|
||||
const validationResults = validationPromises.map((promise) => promise());
|
||||
await Promise.all(validationResults);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new ValidationError(errors);
|
||||
|
||||
@@ -3,12 +3,14 @@ import {
|
||||
ArrayField,
|
||||
BlockField,
|
||||
CheckboxField,
|
||||
CodeField, DateField,
|
||||
CodeField,
|
||||
DateField,
|
||||
EmailField,
|
||||
NumberField,
|
||||
PointField,
|
||||
RadioField,
|
||||
RelationshipField,
|
||||
RelationshipValue,
|
||||
RichTextField,
|
||||
SelectField,
|
||||
TextareaField,
|
||||
@@ -16,6 +18,9 @@ import {
|
||||
UploadField,
|
||||
Validate,
|
||||
} from './config/types';
|
||||
import { TypeWithID } from '../collections/config/types';
|
||||
import canUseDOM from '../utilities/canUseDOM';
|
||||
import payload from '../index';
|
||||
|
||||
const defaultMessage = 'This field is required.';
|
||||
|
||||
@@ -84,7 +89,11 @@ export const email: Validate<unknown, unknown, EmailField> = (value: string, { r
|
||||
return true;
|
||||
};
|
||||
|
||||
export const textarea: Validate<unknown, unknown, TextareaField> = (value: string, { required, maxLength, minLength }) => {
|
||||
export const textarea: Validate<unknown, unknown, TextareaField> = (value: string, {
|
||||
required,
|
||||
maxLength,
|
||||
minLength,
|
||||
}) => {
|
||||
if (value && maxLength && value.length > maxLength) {
|
||||
return `This value must be shorter than the max length of ${maxLength} characters.`;
|
||||
}
|
||||
@@ -143,14 +152,96 @@ export const date: Validate<unknown, unknown, DateField> = (value, { required })
|
||||
return true;
|
||||
};
|
||||
|
||||
export const upload: Validate<unknown, unknown, UploadField> = (value: string, { required }) => {
|
||||
if (value || !required) return true;
|
||||
return defaultMessage;
|
||||
const validateFilterOptions: Validate = async (value, { filterOptions, id, user, data, siblingData, relationTo }) => {
|
||||
if (!canUseDOM && typeof filterOptions !== 'undefined' && value) {
|
||||
const options: {
|
||||
[collection: string]: (string | number)[]
|
||||
} = {};
|
||||
|
||||
const collections = typeof relationTo === 'string' ? [relationTo] : relationTo;
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
|
||||
await Promise.all(collections.map(async (collection) => {
|
||||
const optionFilter = typeof filterOptions === 'function' ? filterOptions({
|
||||
id,
|
||||
data,
|
||||
siblingData,
|
||||
user,
|
||||
relationTo: collection,
|
||||
}) : filterOptions;
|
||||
|
||||
const valueIDs: (string | number)[] = [];
|
||||
|
||||
values.forEach((val) => {
|
||||
if (typeof val === 'object' && val?.value) {
|
||||
valueIDs.push(val.value);
|
||||
}
|
||||
|
||||
if (typeof val === 'string' || typeof val === 'number') {
|
||||
valueIDs.push(val);
|
||||
}
|
||||
});
|
||||
|
||||
const result = await payload.find<TypeWithID>({
|
||||
collection,
|
||||
depth: 0,
|
||||
where: {
|
||||
and: [
|
||||
{ id: { in: valueIDs } },
|
||||
optionFilter,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
options[collection] = result.docs.map((doc) => doc.id);
|
||||
}));
|
||||
|
||||
const invalidRelationships = values.filter((val) => {
|
||||
let collection: string;
|
||||
let requestedID: string | number;
|
||||
|
||||
if (typeof relationTo === 'string') {
|
||||
collection = relationTo;
|
||||
|
||||
if (typeof val === 'string' || typeof val === 'number') {
|
||||
requestedID = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(relationTo) && typeof val === 'object' && val?.relationTo) {
|
||||
collection = val.relationTo;
|
||||
requestedID = val.value;
|
||||
}
|
||||
|
||||
return options[collection].indexOf(requestedID) === -1;
|
||||
});
|
||||
|
||||
if (invalidRelationships.length > 0) {
|
||||
return invalidRelationships.reduce((err, invalid, i) => {
|
||||
return `${err} ${JSON.stringify(invalid)}${invalidRelationships.length === i + 1 ? ',' : ''} `;
|
||||
}, 'This field has the following invalid selections:') as string;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const relationship: Validate<unknown, unknown, RelationshipField> = (value, { required }) => {
|
||||
if (value || !required) return true;
|
||||
return defaultMessage;
|
||||
export const upload: Validate<unknown, unknown, UploadField> = (value: string, options) => {
|
||||
if (!value && options.required) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return validateFilterOptions(value, options);
|
||||
};
|
||||
|
||||
export const relationship: Validate<unknown, unknown, RelationshipField> = async (value: RelationshipValue, options) => {
|
||||
if ((!value || (Array.isArray(value) && value.length === 0)) && options.required) {
|
||||
return defaultMessage;
|
||||
}
|
||||
|
||||
return validateFilterOptions(value, options);
|
||||
};
|
||||
|
||||
export const array: Validate<unknown, unknown, ArrayField> = (value, { minRows, maxRows, required }) => {
|
||||
|
||||
Reference in New Issue
Block a user