* fix: css selectors
This commit is contained in:
@@ -24,11 +24,14 @@ const Table: React.FC<Props> = ({ columns, data }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data && data.map((row, rowIndex) => (
|
{data && data.map((row, rowIndex) => (
|
||||||
<tr key={rowIndex}>
|
<tr
|
||||||
|
key={rowIndex}
|
||||||
|
className={`row-${rowIndex + 1}`}
|
||||||
|
>
|
||||||
{columns.map((col, colIndex) => (
|
{columns.map((col, colIndex) => (
|
||||||
<td
|
<td
|
||||||
key={colIndex}
|
key={colIndex}
|
||||||
className={col.accessor}
|
className={`cell-${col.accessor}`}
|
||||||
>
|
>
|
||||||
{col.components.renderCell(row, row[col.accessor])}
|
{col.components.renderCell(row, row[col.accessor])}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<div
|
<div
|
||||||
|
id={`field-${path}`}
|
||||||
className={classes}
|
className={classes}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__error-wrap`}>
|
<div className={`${baseClass}__error-wrap`}>
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ const Blocks: React.FC<Props> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<div
|
<div
|
||||||
|
id={`field-${path}`}
|
||||||
className={classes}
|
className={classes}
|
||||||
>
|
>
|
||||||
<div className={`${baseClass}__error-wrap`}>
|
<div className={`${baseClass}__error-wrap`}>
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name={path}
|
name={path}
|
||||||
id={path}
|
|
||||||
checked={Boolean(value)}
|
checked={Boolean(value)}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -78,11 +78,12 @@ const Code: React.FC<Props> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<Editor
|
<Editor
|
||||||
|
id={`field-${path}`}
|
||||||
value={value as string || ''}
|
value={value as string || ''}
|
||||||
onValueChange={readOnly ? () => null : setValue}
|
onValueChange={readOnly ? () => null : setValue}
|
||||||
highlight={highlighter}
|
highlight={highlighter}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const ConfirmPassword: React.FC = () => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor="confirm-password"
|
htmlFor="field-confirm-password"
|
||||||
label="Confirm Password"
|
label="Confirm Password"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +52,7 @@ const ConfirmPassword: React.FC = () => {
|
|||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
id="confirm-password"
|
id="field-confirm-password"
|
||||||
name="confirm-password"
|
name="confirm-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ const DateTime: React.FC<Props> = (props) => {
|
|||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<div className={`${baseClass}__input-wrapper`}>
|
<div
|
||||||
|
className={`${baseClass}__input-wrapper`}
|
||||||
|
id={`field-${path}`}
|
||||||
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...date}
|
{...date}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
|
|||||||
@@ -70,16 +70,16 @@ const Email: React.FC<Props> = (props) => {
|
|||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={path}
|
||||||
label={label}
|
label={`field-${path}`}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
value={value as string || ''}
|
value={value as string || ''}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
disabled={Boolean(readOnly)}
|
disabled={Boolean(readOnly)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type="email"
|
type="email"
|
||||||
id={path}
|
|
||||||
name={path}
|
name={path}
|
||||||
autoComplete={autoComplete}
|
autoComplete={autoComplete}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ const Group: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id={`field-${path}`}
|
||||||
className={[
|
className={[
|
||||||
'field-type',
|
'field-type',
|
||||||
baseClass,
|
baseClass,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const HiddenInput: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
value={value as string || ''}
|
value={value as string || ''}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
|
|||||||
@@ -79,17 +79,17 @@ const NumberField: React.FC<Props> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
value={typeof value === 'number' ? value : ''}
|
value={typeof value === 'number' ? value : ''}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type="number"
|
type="number"
|
||||||
id={path}
|
|
||||||
name={path}
|
name={path}
|
||||||
step={step}
|
step={step}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -60,17 +60,17 @@ const Password: React.FC<Props> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
value={value as string || ''}
|
value={value as string || ''}
|
||||||
onChange={setValue}
|
onChange={setValue}
|
||||||
disabled={formProcessing}
|
disabled={formProcessing}
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete={autoComplete}
|
autoComplete={autoComplete}
|
||||||
id={path}
|
|
||||||
name={path}
|
name={path}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,34 +81,34 @@ const PointField: React.FC<Props> = (props) => {
|
|||||||
<ul className={`${baseClass}__wrap`}>
|
<ul className={`${baseClass}__wrap`}>
|
||||||
<li>
|
<li>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={`${path}.longitude`}
|
htmlFor={`field-longitude-${path}`}
|
||||||
label={`${label} - Longitude`}
|
label={`${label} - Longitude`}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-longitude-${path}`}
|
||||||
value={(value && typeof value[0] === 'number') ? value[0] : ''}
|
value={(value && typeof value[0] === 'number') ? value[0] : ''}
|
||||||
onChange={(e) => handleChange(e, 0)}
|
onChange={(e) => handleChange(e, 0)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type="number"
|
type="number"
|
||||||
id={`${path}.longitude`}
|
|
||||||
name={`${path}.longitude`}
|
name={`${path}.longitude`}
|
||||||
step={step}
|
step={step}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={`${path}.latitude`}
|
htmlFor={`field-latitude-${path}`}
|
||||||
label={`${label} - Latitude`}
|
label={`${label} - Latitude`}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-latitude-${path}`}
|
||||||
value={(value && typeof value[1] === 'number') ? value[1] : ''}
|
value={(value && typeof value[1] === 'number') ? value[1] : ''}
|
||||||
onChange={(e) => handleChange(e, 1)}
|
onChange={(e) => handleChange(e, 1)}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type="number"
|
type="number"
|
||||||
id={`${path}.latitude`}
|
|
||||||
name={`${path}.latitude`}
|
name={`${path}.latitude`}
|
||||||
step={step}
|
step={step}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -73,11 +73,14 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<ul className={`${baseClass}--group`}>
|
<ul
|
||||||
|
id={`field-${path}`}
|
||||||
|
className={`${baseClass}--group`}
|
||||||
|
>
|
||||||
{options.map((option) => {
|
{options.map((option) => {
|
||||||
let optionValue = '';
|
let optionValue = '';
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const RadioInput: React.FC<Props> = (props) => {
|
|||||||
isSelected && `${baseClass}--is-selected`,
|
isSelected && `${baseClass}--is-selected`,
|
||||||
].filter(Boolean).join(' ');
|
].filter(Boolean).join(' ');
|
||||||
|
|
||||||
const id = `${path}-${option.value}`;
|
const id = `field-${path}-${option.value}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ const Relationship: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id={`field-${path}`}
|
||||||
className={classes}
|
className={classes}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ const RichText: React.FC<Props> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
@@ -270,6 +270,7 @@ const RichText: React.FC<Props> = (props) => {
|
|||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
>
|
>
|
||||||
<Editable
|
<Editable
|
||||||
|
id={`field-${path}`}
|
||||||
className={`${baseClass}__input`}
|
className={`${baseClass}__input`}
|
||||||
renderElement={renderElement}
|
renderElement={renderElement}
|
||||||
renderLeaf={renderLeaf}
|
renderLeaf={renderLeaf}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
id={`field-${path}`}
|
||||||
className={classes}
|
className={classes}
|
||||||
style={{
|
style={{
|
||||||
...style,
|
...style,
|
||||||
|
|||||||
@@ -61,17 +61,17 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
|
id={`field-${path}`}
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
type="text"
|
type="text"
|
||||||
id={path}
|
|
||||||
name={path}
|
name={path}
|
||||||
/>
|
/>
|
||||||
<FieldDescription
|
<FieldDescription
|
||||||
|
|||||||
@@ -62,16 +62,16 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
|||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
/>
|
/>
|
||||||
<Label
|
<Label
|
||||||
htmlFor={path}
|
htmlFor={`field-${path}`}
|
||||||
label={label}
|
label={label}
|
||||||
required={required}
|
required={required}
|
||||||
/>
|
/>
|
||||||
<textarea
|
<textarea
|
||||||
|
id={`field-${path}`}
|
||||||
value={value || ''}
|
value={value || ''}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
id={path}
|
|
||||||
name={path}
|
name={path}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import './index.scss';
|
|||||||
|
|
||||||
type Value = { relationTo: string, value: number | string };
|
type Value = { relationTo: string, value: number | string };
|
||||||
const baseClass = 'relationship-cell';
|
const baseClass = 'relationship-cell';
|
||||||
|
const totalToShow = 3;
|
||||||
|
|
||||||
const RelationshipCell = (props) => {
|
const RelationshipCell = (props) => {
|
||||||
const { field, data: cellData } = props;
|
const { field, data: cellData } = props;
|
||||||
@@ -23,7 +24,7 @@ const RelationshipCell = (props) => {
|
|||||||
const formattedValues: Value[] = [];
|
const formattedValues: Value[] = [];
|
||||||
|
|
||||||
const arrayCellData = Array.isArray(cellData) ? cellData : [cellData];
|
const arrayCellData = Array.isArray(cellData) ? cellData : [cellData];
|
||||||
arrayCellData.slice(0, (arrayCellData.length < 3 ? arrayCellData.length : 3)).forEach((cell) => {
|
arrayCellData.slice(0, (arrayCellData.length < totalToShow ? arrayCellData.length : totalToShow)).forEach((cell) => {
|
||||||
if (typeof cell === 'object' && 'relationTo' in cell && 'value' in cell) {
|
if (typeof cell === 'object' && 'relationTo' in cell && 'value' in cell) {
|
||||||
formattedValues.push(cell);
|
formattedValues.push(cell);
|
||||||
}
|
}
|
||||||
@@ -50,16 +51,17 @@ const RelationshipCell = (props) => {
|
|||||||
const relatedCollection = collections.find(({ slug }) => slug === relationTo);
|
const relatedCollection = collections.find(({ slug }) => slug === relationTo);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
{document && document[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `Untitled - ID: ${value}`}
|
{ document === false && `Untitled - ID: ${value}`}
|
||||||
|
{ document === null && 'Loading...'}
|
||||||
|
{ document && (
|
||||||
|
document[relatedCollection.admin.useAsTitle] ? document[relatedCollection.admin.useAsTitle] : `Untitled - ID: ${value}`
|
||||||
|
)}
|
||||||
{values.length > i + 1 && ', '}
|
{values.length > i + 1 && ', '}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!cellData && !values && hasRequested && (
|
{ Array.isArray(cellData) && cellData.length > totalToShow && ` and ${cellData.length - totalToShow} more` }
|
||||||
<React.Fragment>
|
{ values.length === 0 && `No <${field.label}>`}
|
||||||
{`No <${field.label}>`}
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import querystring from 'qs';
|
|
||||||
import { requests } from '../../../../../../../api';
|
|
||||||
import { useConfig } from '../../../../../../utilities/Config';
|
|
||||||
|
|
||||||
const UploadCell = ({ data, field }) => {
|
|
||||||
const [cell, setCell] = useState<string>();
|
|
||||||
const { routes } = useConfig();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchUpload = async () => {
|
|
||||||
const params = {
|
|
||||||
depth: 0,
|
|
||||||
limit: 1,
|
|
||||||
where: {
|
|
||||||
id: {
|
|
||||||
equals: data.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const url = `${routes.api}/${field.relationTo}`;
|
|
||||||
const query = querystring.stringify(params, { addQueryPrefix: true });
|
|
||||||
const request = await requests.get(`${url}${query}`);
|
|
||||||
const result = await request.json();
|
|
||||||
|
|
||||||
if (result?.docs.length === 1) {
|
|
||||||
setCell(result.docs[0].filename);
|
|
||||||
} else {
|
|
||||||
setCell(`Untitled - ${data}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchUpload();
|
|
||||||
// get the doc
|
|
||||||
}, [data, field.relationTo, routes.api]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
<React.Fragment>
|
|
||||||
<span>
|
|
||||||
{ cell }
|
|
||||||
</span>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UploadCell;
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import relationship from './Relationship';
|
|||||||
import richText from './Richtext';
|
import richText from './Richtext';
|
||||||
import select from './Select';
|
import select from './Select';
|
||||||
import textarea from './Textarea';
|
import textarea from './Textarea';
|
||||||
import upload from './Upload';
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -20,5 +19,5 @@ export default {
|
|||||||
richText,
|
richText,
|
||||||
select,
|
select,
|
||||||
textarea,
|
textarea,
|
||||||
upload,
|
upload: relationship,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react';
|
import React, { createContext, useCallback, useContext, useEffect, useReducer, useRef } from 'react';
|
||||||
import querystring from 'qs';
|
import querystring from 'qs';
|
||||||
import { useConfig } from '../../../../utilities/Config';
|
import { useConfig } from '../../../../utilities/Config';
|
||||||
import { requests } from '../../../../../api';
|
|
||||||
import { TypeWithID } from '../../../../../../collections/config/types';
|
import { TypeWithID } from '../../../../../../collections/config/types';
|
||||||
import { reducer } from './reducer';
|
import { reducer } from './reducer';
|
||||||
import useDebounce from '../../../../../hooks/useDebounce';
|
import useDebounce from '../../../../../hooks/useDebounce';
|
||||||
|
|
||||||
|
// documents are first set to null when requested
|
||||||
|
// set to false when no doc is returned
|
||||||
|
// or set to the document returned
|
||||||
export type Documents = {
|
export type Documents = {
|
||||||
[slug: string]: {
|
[slug: string]: {
|
||||||
[id: string | number]: TypeWithID | null | 'loading'
|
[id: string | number]: TypeWithID | null | false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ export const RelationshipProvider: React.FC<{children?: React.ReactNode}> = ({ c
|
|||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Object.entries(debouncedDocuments).forEach(([slug, docs]) => {
|
Object.entries(debouncedDocuments).forEach(async ([slug, docs]) => {
|
||||||
const idsToLoad: (string | number)[] = [];
|
const idsToLoad: (string | number)[] = [];
|
||||||
|
|
||||||
Object.entries(docs).forEach(([id, value]) => {
|
Object.entries(docs).forEach(([id, value]) => {
|
||||||
@@ -50,12 +52,15 @@ export const RelationshipProvider: React.FC<{children?: React.ReactNode}> = ({ c
|
|||||||
};
|
};
|
||||||
|
|
||||||
const query = querystring.stringify(params, { addQueryPrefix: true });
|
const query = querystring.stringify(params, { addQueryPrefix: true });
|
||||||
requests.get(`${url}${query}`).then(async (res) => {
|
const result = await fetch(`${url}${query}`);
|
||||||
const result = await res.json();
|
if (result.ok) {
|
||||||
if (result.docs) {
|
const json = await result.json();
|
||||||
dispatchDocuments({ type: 'ADD_LOADED', docs: result.docs, relationTo: slug });
|
if (json.docs) {
|
||||||
|
dispatchDocuments({ type: 'ADD_LOADED', docs: json.docs, relationTo: slug, idsToLoad });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dispatchDocuments({ type: 'ADD_LOADED', docs: [], relationTo: slug, idsToLoad });
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [serverURL, api, debouncedDocuments]);
|
}, [serverURL, api, debouncedDocuments]);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type AddLoadedDocuments = {
|
|||||||
type: 'ADD_LOADED',
|
type: 'ADD_LOADED',
|
||||||
relationTo: string,
|
relationTo: string,
|
||||||
docs: TypeWithID[],
|
docs: TypeWithID[],
|
||||||
|
idsToLoad: (string | number)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action = RequestDocuments | AddLoadedDocuments;
|
type Action = RequestDocuments | AddLoadedDocuments;
|
||||||
@@ -34,13 +35,19 @@ export function reducer(state: Documents, action: Action): Documents {
|
|||||||
if (typeof newState[action.relationTo] !== 'object') {
|
if (typeof newState[action.relationTo] !== 'object') {
|
||||||
newState[action.relationTo] = {};
|
newState[action.relationTo] = {};
|
||||||
}
|
}
|
||||||
|
const unreturnedIDs = [...action.idsToLoad];
|
||||||
|
|
||||||
if (Array.isArray(action.docs)) {
|
if (Array.isArray(action.docs)) {
|
||||||
action.docs.forEach((doc) => {
|
action.docs.forEach((doc) => {
|
||||||
|
unreturnedIDs.splice(unreturnedIDs.indexOf(doc.id), 1);
|
||||||
newState[action.relationTo][doc.id] = doc;
|
newState[action.relationTo][doc.id] = doc;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unreturnedIDs.forEach((id) => {
|
||||||
|
newState[action.relationTo][id] = false;
|
||||||
|
});
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const buildColumns = (collection: SanitizedCollectionConfig, columns: string[]):
|
|||||||
),
|
),
|
||||||
renderCell: (rowData, cellData) => (
|
renderCell: (rowData, cellData) => (
|
||||||
<Cell
|
<Cell
|
||||||
|
key={JSON.stringify(cellData)}
|
||||||
field={field}
|
field={field}
|
||||||
colIndex={colIndex}
|
colIndex={colIndex}
|
||||||
collection={collection}
|
collection={collection}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { UploadedFile } from 'express-fileupload';
|
|
||||||
import { Payload } from '../../..';
|
import { Payload } from '../../..';
|
||||||
import { PayloadRequest } from '../../../express/types';
|
import { PayloadRequest } from '../../../express/types';
|
||||||
import { Document } from '../../../types';
|
import { Document } from '../../../types';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { CollectionConfig } from '../../../src/collections/config/types';
|
import type { CollectionConfig } from '../../../src/collections/config/types';
|
||||||
import { buildConfig } from '../buildConfig';
|
import { buildConfig } from '../buildConfig';
|
||||||
import { devUser } from '../../credentials';
|
import { devUser } from '../../credentials';
|
||||||
|
import { mapAsync } from '../../../src/utilities/mapAsync';
|
||||||
|
|
||||||
export const slug = 'fields-relationship';
|
export const slug = 'fields-relationship';
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export interface FieldsRelationship {
|
|||||||
id: string;
|
id: string;
|
||||||
relationship: RelationOne;
|
relationship: RelationOne;
|
||||||
relationshipHasMany: RelationOne[];
|
relationshipHasMany: RelationOne[];
|
||||||
|
relationshipHasManyMultiple: Array<RelationOne | RelationTwo | { relationTo: string, value: string}>;
|
||||||
relationshipMultiple: Array<RelationOne | RelationTwo>;
|
relationshipMultiple: Array<RelationOne | RelationTwo>;
|
||||||
relationshipRestricted: RelationRestricted;
|
relationshipRestricted: RelationRestricted;
|
||||||
relationshipWithTitle: RelationWithTitle;
|
relationshipWithTitle: RelationWithTitle;
|
||||||
@@ -40,7 +42,7 @@ export default buildConfig({
|
|||||||
{
|
{
|
||||||
slug,
|
slug,
|
||||||
admin: {
|
admin: {
|
||||||
defaultColumns: ['relationship', 'relationshipRestricted', 'with-existing-relations'],
|
defaultColumns: ['id', 'relationship', 'relationshipRestricted', 'relationshipHasManyMultiple', 'relationshipWithTitle'],
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@@ -59,6 +61,12 @@ export default buildConfig({
|
|||||||
name: 'relationshipMultiple',
|
name: 'relationshipMultiple',
|
||||||
relationTo: [relationOneSlug, relationTwoSlug],
|
relationTo: [relationOneSlug, relationTwoSlug],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'relationship',
|
||||||
|
name: 'relationshipHasManyMultiple',
|
||||||
|
hasMany: true,
|
||||||
|
relationTo: [relationOneSlug, relationTwoSlug],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
name: 'relationshipRestricted',
|
name: 'relationshipRestricted',
|
||||||
@@ -113,19 +121,27 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await payload.create<RelationOne>({
|
const relationOneIDs = [];
|
||||||
|
await mapAsync([...Array(5)], async () => {
|
||||||
|
const doc = await payload.create<RelationOne>({
|
||||||
collection: relationOneSlug,
|
collection: relationOneSlug,
|
||||||
data: {
|
data: {
|
||||||
name: relationOneSlug,
|
name: relationOneSlug,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
relationOneIDs.push(doc.id);
|
||||||
|
});
|
||||||
|
|
||||||
await payload.create<RelationTwo>({
|
const relationTwoIDs = [];
|
||||||
|
await mapAsync([...Array(11)], async () => {
|
||||||
|
const doc = await payload.create<RelationTwo>({
|
||||||
collection: relationTwoSlug,
|
collection: relationTwoSlug,
|
||||||
data: {
|
data: {
|
||||||
name: relationTwoSlug,
|
name: relationTwoSlug,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
relationTwoIDs.push(doc.id);
|
||||||
|
});
|
||||||
|
|
||||||
// Existing relationships
|
// Existing relationships
|
||||||
const { id: restrictedDocId } = await payload.create<RelationRestricted>({
|
const { id: restrictedDocId } = await payload.create<RelationRestricted>({
|
||||||
@@ -148,5 +164,28 @@ export default buildConfig({
|
|||||||
relationshipWithTitle: relationWithTitleDocId,
|
relationshipWithTitle: relationWithTitleDocId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
await mapAsync([...Array(11)], async () => {
|
||||||
|
await payload.create<FieldsRelationship>({
|
||||||
|
collection: slug,
|
||||||
|
data: {
|
||||||
|
relationship: relationOneDocId,
|
||||||
|
relationshipRestricted: restrictedDocId,
|
||||||
|
relationshipHasManyMultiple: relationOneIDs.map((id) => ({ relationTo: relationOneSlug, value: id })),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await mapAsync([...Array(15)], async () => {
|
||||||
|
const relationOneID = relationOneIDs[Math.floor(Math.random() * 10)];
|
||||||
|
const relationTwoID = relationTwoIDs[Math.floor(Math.random() * 10)];
|
||||||
|
await payload.create<FieldsRelationship>({
|
||||||
|
collection: slug,
|
||||||
|
data: {
|
||||||
|
relationship: relationOneDocId,
|
||||||
|
relationshipRestricted: restrictedDocId,
|
||||||
|
relationshipHasMany: [relationOneID],
|
||||||
|
relationshipHasManyMultiple: [{ relationTo: relationTwoSlug, value: relationTwoID }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,10 +104,9 @@ describe('fields - relationship', () => {
|
|||||||
test('should create relationship', async () => {
|
test('should create relationship', async () => {
|
||||||
await page.goto(url.create);
|
await page.goto(url.create);
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('#field-relationship');
|
||||||
const relationshipField = fields.nth(0);
|
|
||||||
|
|
||||||
await relationshipField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
|
|
||||||
const options = page.locator('.rs__option');
|
const options = page.locator('.rs__option');
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ describe('fields - relationship', () => {
|
|||||||
|
|
||||||
// Select a relationship
|
// Select a relationship
|
||||||
await options.nth(1).click();
|
await options.nth(1).click();
|
||||||
await expect(relationshipField).toContainText(relationOneDoc.id);
|
await expect(field).toContainText(relationOneDoc.id);
|
||||||
|
|
||||||
await saveDocAndAssert(page);
|
await saveDocAndAssert(page);
|
||||||
});
|
});
|
||||||
@@ -123,10 +122,9 @@ describe('fields - relationship', () => {
|
|||||||
test('should create hasMany relationship', async () => {
|
test('should create hasMany relationship', async () => {
|
||||||
await page.goto(url.create);
|
await page.goto(url.create);
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('.field-relationshipHasMany');
|
||||||
const relationshipHasManyField = fields.nth(1);
|
|
||||||
|
|
||||||
await relationshipHasManyField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
|
|
||||||
const options = page.locator('.rs__option');
|
const options = page.locator('.rs__option');
|
||||||
|
|
||||||
@@ -134,16 +132,16 @@ describe('fields - relationship', () => {
|
|||||||
|
|
||||||
// Add one relationship
|
// Add one relationship
|
||||||
await options.locator(`text=${relationOneDoc.id}`).click();
|
await options.locator(`text=${relationOneDoc.id}`).click();
|
||||||
await expect(relationshipHasManyField).toContainText(relationOneDoc.id);
|
await expect(field).toContainText(relationOneDoc.id);
|
||||||
await expect(relationshipHasManyField).not.toContainText(anotherRelationOneDoc.id);
|
await expect(field).not.toContainText(anotherRelationOneDoc.id);
|
||||||
|
|
||||||
// Add second relationship
|
// Add second relationship
|
||||||
await relationshipHasManyField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
await options.locator(`text=${anotherRelationOneDoc.id}`).click();
|
await options.locator(`text=${anotherRelationOneDoc.id}`).click();
|
||||||
await expect(relationshipHasManyField).toContainText(anotherRelationOneDoc.id);
|
await expect(field).toContainText(anotherRelationOneDoc.id);
|
||||||
|
|
||||||
// No options left
|
// No options left
|
||||||
await relationshipHasManyField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
await expect(page.locator('.rs__menu')).toHaveText('No options');
|
await expect(page.locator('.rs__menu')).toHaveText('No options');
|
||||||
|
|
||||||
await saveDocAndAssert(page);
|
await saveDocAndAssert(page);
|
||||||
@@ -152,10 +150,9 @@ describe('fields - relationship', () => {
|
|||||||
test('should create relations to multiple collections', async () => {
|
test('should create relations to multiple collections', async () => {
|
||||||
await page.goto(url.create);
|
await page.goto(url.create);
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('.field-relationshipMultiple');
|
||||||
const relationshipMultipleField = fields.nth(2);
|
|
||||||
|
|
||||||
await relationshipMultipleField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
|
|
||||||
const options = page.locator('.rs__option');
|
const options = page.locator('.rs__option');
|
||||||
|
|
||||||
@@ -163,12 +160,12 @@ describe('fields - relationship', () => {
|
|||||||
|
|
||||||
// Add one relationship
|
// Add one relationship
|
||||||
await options.locator(`text=${relationOneDoc.id}`).click();
|
await options.locator(`text=${relationOneDoc.id}`).click();
|
||||||
await expect(relationshipMultipleField).toContainText(relationOneDoc.id);
|
await expect(field).toContainText(relationOneDoc.id);
|
||||||
|
|
||||||
// Add relationship of different collection
|
// Add relationship of different collection
|
||||||
await relationshipMultipleField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
await options.locator(`text=${relationTwoDoc.id}`).click();
|
await options.locator(`text=${relationTwoDoc.id}`).click();
|
||||||
await expect(relationshipMultipleField).toContainText(relationTwoDoc.id);
|
await expect(field).toContainText(relationTwoDoc.id);
|
||||||
|
|
||||||
await saveDocAndAssert(page);
|
await saveDocAndAssert(page);
|
||||||
});
|
});
|
||||||
@@ -177,11 +174,10 @@ describe('fields - relationship', () => {
|
|||||||
test('should highlight existing relationship', async () => {
|
test('should highlight existing relationship', async () => {
|
||||||
await page.goto(url.edit(docWithExistingRelations.id));
|
await page.goto(url.edit(docWithExistingRelations.id));
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('#field-relationship');
|
||||||
const relationOneField = fields.nth(0);
|
|
||||||
|
|
||||||
// Check dropdown options
|
// Check dropdown options
|
||||||
await relationOneField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
|
|
||||||
await expect(page.locator('.rs__option--is-selected')).toHaveCount(1);
|
await expect(page.locator('.rs__option--is-selected')).toHaveCount(1);
|
||||||
await expect(page.locator('.rs__option--is-selected')).toHaveText(relationOneDoc.id);
|
await expect(page.locator('.rs__option--is-selected')).toHaveText(relationOneDoc.id);
|
||||||
@@ -190,29 +186,31 @@ describe('fields - relationship', () => {
|
|||||||
test('should show untitled ID on restricted relation', async () => {
|
test('should show untitled ID on restricted relation', async () => {
|
||||||
await page.goto(url.edit(docWithExistingRelations.id));
|
await page.goto(url.edit(docWithExistingRelations.id));
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('#field-relationshipRestricted');
|
||||||
const restrictedRelationField = fields.nth(3);
|
|
||||||
|
|
||||||
// Check existing relationship has untitled ID
|
// Check existing relationship has untitled ID
|
||||||
await expect(restrictedRelationField).toContainText(`Untitled - ID: ${restrictedRelation.id}`);
|
await expect(field).toContainText(`Untitled - ID: ${restrictedRelation.id}`);
|
||||||
|
|
||||||
// Check dropdown options
|
// Check dropdown options
|
||||||
await restrictedRelationField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
const options = page.locator('.rs__option');
|
const options = page.locator('.rs__option');
|
||||||
|
|
||||||
await expect(options).toHaveCount(2); // None + 1 Unitled ID
|
await expect(options).toHaveCount(2); // None + 1 Unitled ID
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// test.todo('should paginate within the dropdown');
|
||||||
|
// test.todo('should search within the relationship field');
|
||||||
|
|
||||||
test('should show useAsTitle on relation', async () => {
|
test('should show useAsTitle on relation', async () => {
|
||||||
await page.goto(url.edit(docWithExistingRelations.id));
|
await page.goto(url.edit(docWithExistingRelations.id));
|
||||||
|
|
||||||
const fields = page.locator('.render-fields >> .react-select');
|
const field = page.locator('#field-relationshipWithTitle .react-select');
|
||||||
const relationWithTitleField = fields.nth(4);
|
|
||||||
|
|
||||||
// Check existing relationship for correct title
|
// Check existing relationship for correct title
|
||||||
await expect(relationWithTitleField).toHaveText(relationWithTitle.name);
|
await expect(field).toHaveText(relationWithTitle.name);
|
||||||
|
await page.screenshot({ path: './bad.png' });
|
||||||
|
|
||||||
await relationWithTitleField.click({ delay: 100 });
|
await field.click({ delay: 100 });
|
||||||
const options = page.locator('.rs__option');
|
const options = page.locator('.rs__option');
|
||||||
|
|
||||||
await expect(options).toHaveCount(2); // None + 1 Doc
|
await expect(options).toHaveCount(2); // None + 1 Doc
|
||||||
@@ -220,26 +218,26 @@ describe('fields - relationship', () => {
|
|||||||
|
|
||||||
test('should show id on relation in list view', async () => {
|
test('should show id on relation in list view', async () => {
|
||||||
await page.goto(url.list);
|
await page.goto(url.list);
|
||||||
await wait(1000);
|
await wait(110);
|
||||||
const cells = page.locator('.relationship');
|
const cells = page.locator('.cell-relationship');
|
||||||
const relationship = cells.nth(0);
|
const relationship = cells.nth(0);
|
||||||
await expect(relationship).toHaveText(relationOneDoc.id);
|
await expect(relationship).toHaveText(relationOneDoc.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should show Untitled ID on restricted relation in list view', async () => {
|
||||||
|
await page.goto(url.list);
|
||||||
|
await wait(110);
|
||||||
|
const cells = page.locator('.cell-relationshipRestricted');
|
||||||
|
const relationship = cells.nth(0);
|
||||||
|
await expect(relationship).toContainText('Untitled - ID: ');
|
||||||
|
});
|
||||||
|
|
||||||
test('should show useAsTitle on relation in list view', async () => {
|
test('should show useAsTitle on relation in list view', async () => {
|
||||||
await page.goto(url.list);
|
await page.goto(url.list);
|
||||||
wait(110);
|
await wait(110);
|
||||||
const cells = page.locator('.relationshipWithTitle');
|
const cells = page.locator('.cell-relationshipWithTitle');
|
||||||
const relationship = cells.nth(0);
|
const relationship = cells.nth(0);
|
||||||
await expect(relationship).toHaveText(relationWithTitle.id);
|
await expect(relationship).toHaveText(relationWithTitle.name);
|
||||||
});
|
|
||||||
|
|
||||||
test('should show untitled ID on restricted relation in list view', async () => {
|
|
||||||
await page.goto(url.list);
|
|
||||||
wait(110);
|
|
||||||
const cells = page.locator('.relationship');
|
|
||||||
const relationship = cells.nth(0);
|
|
||||||
await expect(relationship).toHaveText(relationOneDoc.id);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ export async function firstRegister(args: FirstRegisterArgs): Promise<void> {
|
|||||||
const { page, serverURL } = args;
|
const { page, serverURL } = args;
|
||||||
|
|
||||||
await page.goto(`${serverURL}/admin`);
|
await page.goto(`${serverURL}/admin`);
|
||||||
await page.fill('#email', devUser.email);
|
await page.fill('#field-email', devUser.email);
|
||||||
await page.fill('#password', devUser.password);
|
await page.fill('#field-password', devUser.password);
|
||||||
await page.fill('#confirm-password', devUser.password);
|
await page.fill('#field-confirm-password', devUser.password);
|
||||||
await wait(500);
|
await wait(500);
|
||||||
await page.click('[type=submit]');
|
await page.click('[type=submit]');
|
||||||
await page.waitForURL(`${serverURL}/admin`);
|
await page.waitForURL(`${serverURL}/admin`);
|
||||||
@@ -29,8 +29,8 @@ export async function login(args: LoginArgs): Promise<void> {
|
|||||||
const { page, serverURL } = args;
|
const { page, serverURL } = args;
|
||||||
|
|
||||||
await page.goto(`${serverURL}/admin`);
|
await page.goto(`${serverURL}/admin`);
|
||||||
await page.fill('#email', devUser.email);
|
await page.fill('#field-email', devUser.email);
|
||||||
await page.fill('#password', devUser.password);
|
await page.fill('#field-password', devUser.password);
|
||||||
await wait(500);
|
await wait(500);
|
||||||
await page.click('[type=submit]');
|
await page.click('[type=submit]');
|
||||||
await page.waitForURL(`${serverURL}/admin`);
|
await page.waitForURL(`${serverURL}/admin`);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Reference in New Issue
Block a user