diff --git a/demo/collections/CustomComponents/components/fields/Description/Filter/index.js b/demo/collections/CustomComponents/components/fields/Description/Filter/index.js new file mode 100644 index 0000000000..01bca27921 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Description/Filter/index.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './index.scss'; + +const Filter = ({ onChange, value }) => { + return ( + onChange(e.target.value)} + value={value} + /> + ); +}; + +Filter.defaultProps = { + value: '', +}; + +Filter.propTypes = { + onChange: PropTypes.func.isRequired, + value: PropTypes.string, +}; + +export default Filter; diff --git a/demo/collections/CustomComponents/components/fields/Description/Filter/index.scss b/demo/collections/CustomComponents/components/fields/Description/Filter/index.scss new file mode 100644 index 0000000000..a5fe92fae0 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Description/Filter/index.scss @@ -0,0 +1,3 @@ +.custom-description-filter { + background: lightgray; +} diff --git a/demo/collections/CustomComponents/components/views/List/index.js b/demo/collections/CustomComponents/components/views/List/index.js index 34360e6846..a63ea17ba4 100644 --- a/demo/collections/CustomComponents/components/views/List/index.js +++ b/demo/collections/CustomComponents/components/views/List/index.js @@ -1,18 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import MinimalTemplate from '../../../../../../src/client/components/templates/Minimal'; +import DefaultList from '../../../../../../src/client/components/views/collections/List/Default'; import './index.scss'; const CustomListView = (props) => { - const { collection } = props; - return ( - -

{collection.labels.plural}

+

This is a custom Pages list view

Sup

- + +
); }; diff --git a/demo/collections/CustomComponents/index.js b/demo/collections/CustomComponents/index.js index 328669549e..c3042bde7a 100644 --- a/demo/collections/CustomComponents/index.js +++ b/demo/collections/CustomComponents/index.js @@ -32,6 +32,7 @@ module.exports = { components: { field: path.resolve(__dirname, 'components/fields/Description/Field/index.js'), cell: path.resolve(__dirname, 'components/fields/Description/Cell/index.js'), + filter: path.resolve(__dirname, 'components/fields/Description/Filter/index.js'), }, }, ], diff --git a/src/client/components/elements/ListControls/index.js b/src/client/components/elements/ListControls/index.js index 52ac5eea8a..2cb5f1ef41 100644 --- a/src/client/components/elements/ListControls/index.js +++ b/src/client/components/elements/ListControls/index.js @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import AnimateHeight from 'react-animate-height'; -// import customComponents from '../../customComponents'; import SearchFilter from '../SearchFilter'; import ColumnSelector from '../ColumnSelector'; import WhereBuilder from '../WhereBuilder'; diff --git a/src/client/components/elements/WhereBuilder/Condition/Date/index.js b/src/client/components/elements/WhereBuilder/Condition/Date/index.js index 3b15b7f47c..add2177bc7 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Date/index.js +++ b/src/client/components/elements/WhereBuilder/Condition/Date/index.js @@ -3,11 +3,14 @@ import PropTypes from 'prop-types'; import './index.scss'; +const baseClass = 'condition-value-date'; + const DateField = ({ onChange, value }) => { return ( onChange(e.target.value)} value={value} /> ); diff --git a/src/client/components/elements/WhereBuilder/Condition/Date/index.scss b/src/client/components/elements/WhereBuilder/Condition/Date/index.scss index e69de29bb2..31eabb5710 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Date/index.scss +++ b/src/client/components/elements/WhereBuilder/Condition/Date/index.scss @@ -0,0 +1,6 @@ +@import '../../../../../scss/styles.scss'; +@import '../../../../forms/field-types/shared.scss'; + +.condition-value-date { + @include formInput; +} diff --git a/src/client/components/elements/WhereBuilder/Condition/Number/index.js b/src/client/components/elements/WhereBuilder/Condition/Number/index.js index 9bfae0f09f..186d525019 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Number/index.js +++ b/src/client/components/elements/WhereBuilder/Condition/Number/index.js @@ -3,19 +3,27 @@ import PropTypes from 'prop-types'; import './index.scss'; +const baseClass = 'condition-value-number'; + const NumberField = ({ onChange, value }) => { return ( onChange(e.target.value)} value={value} /> ); }; +NumberField.defaultProps = { + value: null, +}; + NumberField.propTypes = { onChange: PropTypes.func.isRequired, - value: PropTypes.string.isRequired, + value: PropTypes.string, }; export default NumberField; diff --git a/src/client/components/elements/WhereBuilder/Condition/Number/index.scss b/src/client/components/elements/WhereBuilder/Condition/Number/index.scss index e69de29bb2..b9abd953d7 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Number/index.scss +++ b/src/client/components/elements/WhereBuilder/Condition/Number/index.scss @@ -0,0 +1,6 @@ +@import '../../../../../scss/styles.scss'; +@import '../../../../forms/field-types/shared.scss'; + +.condition-value-number { + @include formInput; +} diff --git a/src/client/components/elements/WhereBuilder/Condition/Text/index.js b/src/client/components/elements/WhereBuilder/Condition/Text/index.js index b6818efc30..c9ad5f78bc 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Text/index.js +++ b/src/client/components/elements/WhereBuilder/Condition/Text/index.js @@ -3,19 +3,27 @@ import PropTypes from 'prop-types'; import './index.scss'; +const baseClass = 'condition-value-text'; + const Text = ({ onChange, value }) => { return ( onChange(e.target.value)} value={value} /> ); }; +Text.defaultProps = { + value: '', +}; + Text.propTypes = { onChange: PropTypes.func.isRequired, - value: PropTypes.string.isRequired, + value: PropTypes.string, }; export default Text; diff --git a/src/client/components/elements/WhereBuilder/Condition/Text/index.scss b/src/client/components/elements/WhereBuilder/Condition/Text/index.scss index e69de29bb2..f140514b1f 100644 --- a/src/client/components/elements/WhereBuilder/Condition/Text/index.scss +++ b/src/client/components/elements/WhereBuilder/Condition/Text/index.scss @@ -0,0 +1,6 @@ +@import '../../../../../scss/styles.scss'; +@import '../../../../forms/field-types/shared.scss'; + +.condition-value-text { + @include formInput; +} diff --git a/src/client/components/elements/WhereBuilder/Condition/index.js b/src/client/components/elements/WhereBuilder/Condition/index.js index 7d284e7699..301577ad05 100644 --- a/src/client/components/elements/WhereBuilder/Condition/index.js +++ b/src/client/components/elements/WhereBuilder/Condition/index.js @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; +import RenderCustomComponent from '../../../utilities/RenderCustomComponent'; import ReactSelect from '../../ReactSelect'; import Button from '../../Button'; import Date from './Date'; @@ -23,6 +24,7 @@ const Condition = (props) => { value, orIndex, andIndex, + collectionSlug, } = props; const [activeField, setActiveField] = useState({ operators: [] }); @@ -35,6 +37,8 @@ const Condition = (props) => { } }, [value, fields]); + const ValueComponent = valueFields[activeField.component] || valueFields.Text; + return (
@@ -64,15 +68,18 @@ const Condition = (props) => { />
- console.log('changing value', newValue)} + dispatch({ + type: 'update', + orIndex, + andIndex, + value: updatedValue || '', + }), + }} />
@@ -125,6 +132,7 @@ Condition.propTypes = { dispatch: PropTypes.func.isRequired, orIndex: PropTypes.number.isRequired, andIndex: PropTypes.number.isRequired, + collectionSlug: PropTypes.string.isRequired, }; export default Condition; diff --git a/src/client/components/elements/WhereBuilder/index.js b/src/client/components/elements/WhereBuilder/index.js index b4634cad1a..e3afcd336c 100644 --- a/src/client/components/elements/WhereBuilder/index.js +++ b/src/client/components/elements/WhereBuilder/index.js @@ -13,6 +13,7 @@ const WhereBuilder = (props) => { const { collection: { fields, + slug, labels: { plural, } = {}, @@ -26,13 +27,15 @@ const WhereBuilder = (props) => { useEffect(() => { setReducedFields(fields.reduce((reduced, field) => { if (typeof fieldTypes[field.type] === 'object') { + const formattedField = { + label: field.label, + value: field.name, + ...fieldTypes[field.type], + }; + return [ ...reduced, - { - label: field.label, - value: field.name, - ...fieldTypes[field.type], - }, + formattedField, ]; } @@ -74,6 +77,7 @@ const WhereBuilder = (props) => { )} { WhereBuilder.propTypes = { handleChange: PropTypes.func.isRequired, collection: PropTypes.shape({ + slug: PropTypes.string, fields: PropTypes.arrayOf( PropTypes.shape({}), ), diff --git a/src/client/components/elements/WhereBuilder/reducer.js b/src/client/components/elements/WhereBuilder/reducer.js index 8e8131e1e4..3929b45a65 100644 --- a/src/client/components/elements/WhereBuilder/reducer.js +++ b/src/client/components/elements/WhereBuilder/reducer.js @@ -51,7 +51,7 @@ const reducer = (state, action = {}) => { newState[orIndex][andIndex].field = field; } - if (value) { + if (value !== undefined) { newState[orIndex][andIndex].value = value; } diff --git a/src/client/components/utilities/RenderCustomComponent/index.js b/src/client/components/utilities/RenderCustomComponent/index.js new file mode 100644 index 0000000000..75b35b38eb --- /dev/null +++ b/src/client/components/utilities/RenderCustomComponent/index.js @@ -0,0 +1,40 @@ +import React, { Suspense } from 'react'; +import PropTypes from 'prop-types'; +import customComponents from '../../customComponents'; +import Loading from '../../elements/Loading'; + +const RenderCustomComponent = (props) => { + const { path, DefaultComponent, componentProps } = props; + + const CustomComponent = path.split('.').reduce((res, prop) => { + if (res) { + return res[prop]; + } + + return false; + }, customComponents); + + if (CustomComponent) { + return ( + }> + + + ); + } + + return ( + + ); +}; + +RenderCustomComponent.propTypes = { + path: PropTypes.string.isRequired, + DefaultComponent: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.node, + PropTypes.element, + ]).isRequired, + componentProps: PropTypes.shape({}).isRequired, +}; + +export default RenderCustomComponent; diff --git a/src/client/components/views/collections/List/Default.js b/src/client/components/views/collections/List/Default.js new file mode 100644 index 0000000000..d29295d53b --- /dev/null +++ b/src/client/components/views/collections/List/Default.js @@ -0,0 +1,161 @@ +import React, { useEffect, useState } from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import queryString from 'qs'; +import PropTypes from 'prop-types'; +import usePayloadAPI from '../../../../hooks/usePayloadAPI'; +import Paginator from '../../../elements/Paginator'; +import ListControls from '../../../elements/ListControls'; +import Pill from '../../../elements/Pill'; +import Button from '../../../elements/Button'; +import SortColumn from '../../../elements/SortColumn'; +import Table from '../../../elements/Table'; + +import './index.scss'; + +const { serverURL, routes: { api, admin } } = PAYLOAD_CONFIG; + +const baseClass = 'collection-list'; + +const DefaultList = (props) => { + const { + collection, + collection: { + fields, + slug, + labels: { + singular: singularLabel, + plural: pluralLabel, + }, + }, + } = props; + + const location = useLocation(); + const [listControls, setListControls] = useState({}); + const [sort, setSort] = useState(null); + const newDocumentURL = `${admin}/collections/${slug}/create`; + + const { page } = queryString.parse(location.search, { ignoreQueryPrefix: true }); + + const apiURL = `${serverURL}${api}/${slug}`; + + const [{ data }, { setParams }] = usePayloadAPI(apiURL, {}); + + useEffect(() => { + const params = {}; + + if (page) params.page = page; + if (sort) params.sort = sort; + if (listControls?.where) params.where = listControls.where; + + setParams(params); + }, [setParams, page, sort, listControls]); + + return ( +
+
+

{pluralLabel}

+ + Create New + +
+ + {(data.docs && data.docs.length > 0) && ( + { + const field = fields.find(fieldToCheck => fieldToCheck.name === col); + return { + accessor: field.name, + components: { + Heading: ( + + ), + renderCell: (rowData, cellData) => { + if (i === 0) { + return ( + <> + + {typeof cellData === 'string' && cellData} + {typeof cellData === 'object' && JSON.stringify(cellData)} + + + ); + } + + return cellData; + }, + }, + }; + })} + /> + )} + {(!data.docs || data.docs.length === 0) && ( +
+

+ No + {' '} + {pluralLabel} + {' '} + found. Either no + {' '} + {pluralLabel} + {' '} + exist yet or none match the filters you've specified above. +

+ +
+ )} +
+ +
+ {data.page} + - + {data.totalPages > 1 ? data.limit : data.totalDocs} + {' '} + of + {' '} + {data.totalDocs} +
+
+ + ); +}; + +DefaultList.propTypes = { + collection: PropTypes.shape({ + labels: PropTypes.shape({ + singular: PropTypes.string, + plural: PropTypes.string, + }), + slug: PropTypes.string, + useAsTitle: PropTypes.string, + fields: PropTypes.arrayOf(PropTypes.shape), + timestamps: PropTypes.bool, + }).isRequired, +}; + +export default DefaultList; diff --git a/src/client/components/views/collections/List/index.js b/src/client/components/views/collections/List/index.js index 6a41b97d85..72e68de086 100644 --- a/src/client/components/views/collections/List/index.js +++ b/src/client/components/views/collections/List/index.js @@ -1,166 +1,12 @@ import React, { useEffect, useState } from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import queryString from 'qs'; import PropTypes from 'prop-types'; +import DefaultList from './Default'; import customComponents from '../../../customComponents'; import { useStepNav } from '../../../elements/StepNav'; -import usePayloadAPI from '../../../../hooks/usePayloadAPI'; -import Paginator from '../../../elements/Paginator'; -import ListControls from '../../../elements/ListControls'; -import Pill from '../../../elements/Pill'; -import Button from '../../../elements/Button'; -import SortColumn from '../../../elements/SortColumn'; -import Table from '../../../elements/Table'; import formatListFields from './formatListFields'; import './index.scss'; -const { serverURL, routes: { api, admin } } = PAYLOAD_CONFIG; - -const baseClass = 'collection-list'; - -const DefaultList = (props) => { - const { - collection, - collection: { - fields, - slug, - labels: { - singular: singularLabel, - plural: pluralLabel, - }, - }, - } = props; - - const location = useLocation(); - const [listControls, setListControls] = useState({}); - const [sort, setSort] = useState(null); - const newDocumentURL = `${admin}/collections/${slug}/create`; - - const { page } = queryString.parse(location.search, { ignoreQueryPrefix: true }); - - const apiURL = `${serverURL}${api}/${slug}`; - - const [{ data }, { setParams }] = usePayloadAPI(apiURL, {}); - - useEffect(() => { - const params = {}; - - if (page) params.page = page; - if (sort) params.sort = sort; - if (listControls?.where) params.where = listControls.where; - - setParams(params); - }, [setParams, page, sort, listControls]); - - return ( -
-
-

{pluralLabel}

- - Create New - -
- - {(data.docs && data.docs.length > 0) && ( -
{ - const field = fields.find(fieldToCheck => fieldToCheck.name === col); - return { - accessor: field.name, - components: { - Heading: ( - - ), - renderCell: (rowData, cellData) => { - if (i === 0) { - return ( - <> - - {typeof cellData === 'string' && cellData} - {typeof cellData === 'object' && JSON.stringify(cellData)} - - - ); - } - - return cellData; - }, - }, - }; - })} - /> - )} - {(!data.docs || data.docs.length === 0) && ( -
-

- No - {' '} - {pluralLabel} - {' '} - found. Either no - {' '} - {pluralLabel} - {' '} - exist yet or none match the filters you've specified above. -

- -
- )} -
- -
- {data.page} - - - {data.totalPages > 1 ? data.limit : data.totalDocs} - {' '} - of - {' '} - {data.totalDocs} -
-
- - ); -}; - -DefaultList.propTypes = { - collection: PropTypes.shape({ - labels: PropTypes.shape({ - singular: PropTypes.string, - plural: PropTypes.string, - }), - slug: PropTypes.string, - useAsTitle: PropTypes.string, - fields: PropTypes.arrayOf(PropTypes.shape), - timestamps: PropTypes.bool, - }).isRequired, -}; - const ListView = (props) => { const { collection,