Update SelectOne and SelectMany

This commit is contained in:
soupette 2019-07-17 12:06:19 +02:00
parent 1157f6ae75
commit 21177a4670
8 changed files with 615 additions and 378 deletions

View File

@ -1,267 +1,69 @@
/**
*
* SelectMany
*
*/
/* eslint-disable */
import React from 'react'; import React from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import { get, isEmpty } from 'lodash';
cloneDeep,
includes,
isArray,
isNull,
isUndefined,
get,
findIndex,
isEmpty,
} from 'lodash';
// Utils. import Select from 'react-select';
import { request, templateObject } from 'strapi-helper-plugin';
// Component. function SelectMany({
import SortableList from './SortableList'; addRelation,
// CSS. mainField,
import styles from './styles.scss'; name,
isLoading,
class SelectMany extends React.PureComponent { onInputChange,
state = { onMenuClose,
isLoading: true, onMenuScrollToBottom,
options: [], options,
start: 0, placeholder,
}; value,
}) {
componentDidMount() { return (
this.getOptions(''); <>
} <Select
id={name}
componentDidUpdate(prevProps, prevState) { filterOption={el => {
if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) { if (isEmpty(value)) {
const values = ( return true;
get(this.props.record, this.props.relation.alias) || []
).map(el => el.id || el._id);
const options = this.state.options.filter(el => {
return !values.includes(el.value.id || el.value._id);
});
this.state.options = options;
}
if (prevState.start !== this.state.start) {
this.getOptions('');
}
}
getOptions = query => {
const params = {
_limit: 20,
_start: this.state.start,
source: this.props.relation.plugin || 'content-manager',
};
// Set `query` parameter if necessary
if (query) {
delete params._limit;
delete params._skip;
params[`${this.props.relation.displayedAttribute}_contains`] = query;
}
// Request URL
const requestUrl = `/content-manager/explorer/${this.props.relation.model ||
this.props.relation.collection}`;
// Call our request helper (see 'utils/request')
return request(requestUrl, {
method: 'GET',
params,
})
.then(response => {
/* eslint-disable indent */
const options = isArray(response)
? response.map(item => ({
value: item,
label: templateObject(
{ mainField: this.props.relation.displayedAttribute },
item
).mainField,
}))
: [
{
value: response,
label: response[this.props.relation.displayedAttribute],
},
];
/* eslint-enable indent */
const newOptions = cloneDeep(this.state.options);
options.map(option => {
// Don't add the values when searching
if (
findIndex(newOptions, o => o.value.id === option.value.id) === -1
) {
return newOptions.push(option);
} }
});
return this.setState({ return value.findIndex(obj => obj.id === el.value.id) === -1;
options: newOptions, }}
isLoading: false, isLoading={isLoading}
}); isMulti
}) isSearchable
.catch(() => { options={options}
strapi.notification.error( onChange={addRelation}
'content-manager.notification.error.relationship.fetch' onInputChange={onInputChange}
); onMenuClose={onMenuClose}
}); onMenuScrollToBottom={onMenuScrollToBottom}
}; placeholder={placeholder}
value={[]}
handleInputChange = value => { />
const clonedOptions = this.state.options; {!isEmpty(value) && (
const filteredValues = clonedOptions.filter(data => <ul>
includes(data.label, value) {value.map(v => (
); <li key={v.id}>{get(v, [mainField], '')}</li>
))}
if (filteredValues.length === 0) { </ul>
return this.getOptions(value); )}
} </>
}; );
handleChange = value => {
// Remove new added value from available option;
this.state.options = this.state.options.filter(
el =>
!((el.value._id || el.value.id) === (value.value.id || value.value._id))
);
this.props.onAddRelationalItem({
key: this.props.relation.alias,
value: value.value,
});
};
handleBottomScroll = () => {
this.setState(prevState => {
return {
start: prevState.start + 1,
};
});
};
handleRemove = index => {
const values = get(this.props.record, this.props.relation.alias);
// Add removed value from available option;
const toAdd = {
value: values[index],
label: templateObject(
{ mainField: this.props.relation.displayedAttribute },
values[index]
).mainField,
};
this.setState(prevState => ({
options: prevState.options.concat([toAdd]),
}));
this.props.onRemoveRelationItem({
key: this.props.relation.alias,
index,
});
};
// Redirect to the edit page
handleClick = (item = {}) => {
this.props.onRedirect({
model: this.props.relation.collection || this.props.relation.model,
id: item.value.id || item.value._id,
source: this.props.relation.plugin,
});
};
render() {
const description = this.props.relation.description ? (
<p>{this.props.relation.description}</p>
) : (
''
);
const value = get(this.props.record, this.props.relation.alias) || [];
/* eslint-disable jsx-a11y/label-has-for */
return (
<div
className={`form-group ${styles.selectMany} ${value.length > 4 &&
styles.selectManyUpdate}`}
>
<label htmlFor={this.props.relation.alias}>
{this.props.relation.alias} <span>({value.length})</span>
</label>
{description}
<Select
className={`${styles.select}`}
id={this.props.relation.alias}
isLoading={this.state.isLoading}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onMenuScrollToBottom={this.handleBottomScroll}
options={this.state.options}
placeholder={
<FormattedMessage id="content-manager.containers.Edit.addAnItem" />
}
/>
<SortableList
items={
/* eslint-disable indent */
isNull(value) || isUndefined(value) || value.size === 0
? null
: value.map(item => {
if (item) {
return {
value: get(item, 'value') || item,
label:
get(item, 'label') ||
templateObject(
{ mainField: this.props.relation.displayedAttribute },
item
).mainField ||
item.id,
};
}
})
}
/* eslint-enable indent */
isDraggingSibling={this.props.isDraggingSibling}
keys={this.props.relation.alias}
moveAttr={this.props.moveAttr}
moveAttrEnd={this.props.moveAttrEnd}
name={this.props.relation.alias}
onRemove={this.handleRemove}
distance={1}
onClick={this.handleClick}
/>
</div>
);
/* eslint-disable jsx-a11y/label-has-for */
}
} }
SelectMany.defaultProps = { SelectMany.defaultProps = {
isDraggingSibling: false, value: null,
moveAttr: () => {},
moveAttrEnd: () => {},
}; };
SelectMany.propTypes = { SelectMany.propTypes = {
isDraggingSibling: PropTypes.bool, addRelation: PropTypes.func.isRequired,
moveAttr: PropTypes.func, mainField: PropTypes.string.isRequired,
moveAttrEnd: PropTypes.func, name: PropTypes.string.isRequired,
onAddRelationalItem: PropTypes.func.isRequired, isLoading: PropTypes.bool.isRequired,
onRedirect: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onRemoveRelationItem: PropTypes.func.isRequired, onMenuClose: PropTypes.func.isRequired,
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired, onMenuScrollToBottom: PropTypes.func.isRequired,
relation: PropTypes.object.isRequired, options: PropTypes.array.isRequired,
placeholder: PropTypes.node.isRequired,
value: PropTypes.array,
}; };
export default SelectMany; export default SelectMany;

View File

@ -0,0 +1,267 @@
/**
*
* SelectMany
*
*/
/* eslint-disable */
import React from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import {
cloneDeep,
includes,
isArray,
isNull,
isUndefined,
get,
findIndex,
isEmpty,
} from 'lodash';
// Utils.
import { request, templateObject } from 'strapi-helper-plugin';
// Component.
import SortableList from './SortableList';
// CSS.
import styles from './styles.scss';
class SelectMany extends React.PureComponent {
state = {
isLoading: true,
options: [],
start: 0,
};
componentDidMount() {
this.getOptions('');
}
componentDidUpdate(prevProps, prevState) {
if (isEmpty(prevProps.record) && !isEmpty(this.props.record)) {
const values = (
get(this.props.record, this.props.relation.alias) || []
).map(el => el.id || el._id);
const options = this.state.options.filter(el => {
return !values.includes(el.value.id || el.value._id);
});
this.state.options = options;
}
if (prevState.start !== this.state.start) {
this.getOptions('');
}
}
getOptions = query => {
const params = {
_limit: 20,
_start: this.state.start,
source: this.props.relation.plugin || 'content-manager',
};
// Set `query` parameter if necessary
if (query) {
delete params._limit;
delete params._skip;
params[`${this.props.relation.displayedAttribute}_contains`] = query;
}
// Request URL
const requestUrl = `/content-manager/explorer/${this.props.relation.model ||
this.props.relation.collection}`;
// Call our request helper (see 'utils/request')
return request(requestUrl, {
method: 'GET',
params,
})
.then(response => {
/* eslint-disable indent */
const options = isArray(response)
? response.map(item => ({
value: item,
label: templateObject(
{ mainField: this.props.relation.displayedAttribute },
item
).mainField,
}))
: [
{
value: response,
label: response[this.props.relation.displayedAttribute],
},
];
/* eslint-enable indent */
const newOptions = cloneDeep(this.state.options);
options.map(option => {
// Don't add the values when searching
if (
findIndex(newOptions, o => o.value.id === option.value.id) === -1
) {
return newOptions.push(option);
}
});
return this.setState({
options: newOptions,
isLoading: false,
});
})
.catch(() => {
strapi.notification.error(
'content-manager.notification.error.relationship.fetch'
);
});
};
handleInputChange = value => {
const clonedOptions = this.state.options;
const filteredValues = clonedOptions.filter(data =>
includes(data.label, value)
);
if (filteredValues.length === 0) {
return this.getOptions(value);
}
};
handleChange = value => {
// Remove new added value from available option;
this.state.options = this.state.options.filter(
el =>
!((el.value._id || el.value.id) === (value.value.id || value.value._id))
);
this.props.onAddRelationalItem({
key: this.props.relation.alias,
value: value.value,
});
};
handleBottomScroll = () => {
this.setState(prevState => {
return {
start: prevState.start + 1,
};
});
};
handleRemove = index => {
const values = get(this.props.record, this.props.relation.alias);
// Add removed value from available option;
const toAdd = {
value: values[index],
label: templateObject(
{ mainField: this.props.relation.displayedAttribute },
values[index]
).mainField,
};
this.setState(prevState => ({
options: prevState.options.concat([toAdd]),
}));
this.props.onRemoveRelationItem({
key: this.props.relation.alias,
index,
});
};
// Redirect to the edit page
handleClick = (item = {}) => {
this.props.onRedirect({
model: this.props.relation.collection || this.props.relation.model,
id: item.value.id || item.value._id,
source: this.props.relation.plugin,
});
};
render() {
const description = this.props.relation.description ? (
<p>{this.props.relation.description}</p>
) : (
''
);
const value = get(this.props.record, this.props.relation.alias) || [];
/* eslint-disable jsx-a11y/label-has-for */
return (
<div
className={`form-group ${styles.selectMany} ${value.length > 4 &&
styles.selectManyUpdate}`}
>
<label htmlFor={this.props.relation.alias}>
{this.props.relation.alias} <span>({value.length})</span>
</label>
{description}
<Select
className={`${styles.select}`}
id={this.props.relation.alias}
isLoading={this.state.isLoading}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onMenuScrollToBottom={this.handleBottomScroll}
options={this.state.options}
placeholder={
<FormattedMessage id="content-manager.containers.Edit.addAnItem" />
}
/>
<SortableList
items={
/* eslint-disable indent */
isNull(value) || isUndefined(value) || value.size === 0
? null
: value.map(item => {
if (item) {
return {
value: get(item, 'value') || item,
label:
get(item, 'label') ||
templateObject(
{ mainField: this.props.relation.displayedAttribute },
item
).mainField ||
item.id,
};
}
})
}
/* eslint-enable indent */
isDraggingSibling={this.props.isDraggingSibling}
keys={this.props.relation.alias}
moveAttr={this.props.moveAttr}
moveAttrEnd={this.props.moveAttrEnd}
name={this.props.relation.alias}
onRemove={this.handleRemove}
distance={1}
onClick={this.handleClick}
/>
</div>
);
/* eslint-disable jsx-a11y/label-has-for */
}
}
SelectMany.defaultProps = {
isDraggingSibling: false,
moveAttr: () => {},
moveAttrEnd: () => {},
};
SelectMany.propTypes = {
isDraggingSibling: PropTypes.bool,
moveAttr: PropTypes.func,
moveAttrEnd: PropTypes.func,
onAddRelationalItem: PropTypes.func.isRequired,
onRedirect: PropTypes.func.isRequired,
onRemoveRelationItem: PropTypes.func.isRequired,
record: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired,
relation: PropTypes.object.isRequired,
};
export default SelectMany;

View File

@ -1,135 +1,53 @@
import React, { useState, useEffect, useRef } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { get, isNull } from 'lodash';
import { Link } from 'react-router-dom';
import { get, isEmpty, isNull } from 'lodash';
import { request } from 'strapi-helper-plugin';
import Select from 'react-select'; import Select from 'react-select';
import pluginId from '../../pluginId';
import { Nav, Wrapper } from './components';
function SelectOne({ function SelectOne({
description,
label,
mainField, mainField,
name, name,
isLoading,
onChange, onChange,
pathname, onInputChange,
search, onMenuClose,
targetModel, onMenuScrollToBottom,
plugin, options,
placeholder,
value, value,
}) { }) {
const [state, setState] = useState({
_q: '',
_limit: 8,
_start: 0,
source: isEmpty(plugin) ? 'content-manager' : plugin,
});
const [options, setOptions] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const ref = useRef();
ref.current = async (uid, params = state) => {
try {
const requestUrl = `/${pluginId}/explorer/${uid}`;
if (isEmpty(params._q)) {
delete params._q;
}
const data = await request(requestUrl, {
method: 'GET',
params: params,
});
setOptions(
data.map(obj => {
return { value: obj, label: obj[mainField] };
})
);
setIsLoading(false);
} catch (err) {
console.log({ err });
strapi.notification.error('notification.error');
}
};
useEffect(() => {
ref.current(targetModel);
}, [ref, targetModel, state._start]);
const nextSearch = `${pathname}${search}`;
const to = `/plugins/${pluginId}/${targetModel}/${
value ? value.id : null
}?redirectUrl=${nextSearch}`;
const link =
value === null ||
value === undefined ||
['role', 'permission'].includes(targetModel) ? null : (
<Link to={to}>
<FormattedMessage id="content-manager.containers.Edit.seeDetails" />
</Link>
);
const onInputChange = inputValue => {
setState(prevState => ({ ...prevState, _q: inputValue }));
return inputValue;
};
const onMenuScrollToBottom = () => {
setState(prevState => ({ ...prevState, _start: prevState._start + 1 }));
};
return ( return (
<Wrapper className="form-group"> <Select
<Nav> id={name}
<label htmlFor={name}>{label}</label> isLoading={isLoading}
{link} isClearable
</Nav> options={options}
{!isEmpty(description) && <p>{description}</p>} onChange={onChange}
<Select onInputChange={onInputChange}
id={name} onMenuClose={onMenuClose}
isLoading={isLoading} onMenuScrollToBottom={onMenuScrollToBottom}
isClearable placeholder={placeholder}
options={options} value={
onChange={value => { isNull(value) ? null : { label: get(value, [mainField], ''), value }
onChange({ target: { name, value: value ? value.value : value } }); }
}} />
onInputChange={onInputChange}
onMenuClose={() => {
setState(prevState => ({ ...prevState, _start: 0 }));
}}
onMenuScrollToBottom={onMenuScrollToBottom}
value={
isNull(value) ? null : { label: get(value, [mainField], ''), value }
}
/>
</Wrapper>
); );
} }
SelectOne.defaultProps = { SelectOne.defaultProps = {
description: '',
label: '',
plugin: '',
value: null, value: null,
}; };
SelectOne.propTypes = { SelectOne.propTypes = {
description: PropTypes.string,
label: PropTypes.string,
mainField: PropTypes.string.isRequired, mainField: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
isLoading: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired, onInputChange: PropTypes.func.isRequired,
plugin: PropTypes.string, onMenuClose: PropTypes.func.isRequired,
search: PropTypes.string.isRequired, onMenuScrollToBottom: PropTypes.func.isRequired,
targetModel: PropTypes.string.isRequired, options: PropTypes.array.isRequired,
placeholder: PropTypes.node.isRequired,
value: PropTypes.object, value: PropTypes.object,
}; };

View File

@ -0,0 +1,51 @@
import styled from 'styled-components';
const Wrapper = styled.div`
position: relative;
label {
font-size: 1.3rem;
font-weight: 500;
margin-top: 3px;
}
nav + div {
height: 34px;
margin: 3px 0 26px;
> div {
box-shadow: none !important;
border-color: #e3e9f3 !important;
> span:first-of-type {
> div:first-of-type {
color: #9ea7b8;
}
}
> span:last-of-type {
span {
border-color: #b3b5b9 transparent transparent;
}
}
}
}
`;
const Nav = styled.nav`
display: flex;
justify-content: space-between;
a {
color: #007eff !important;
font-size: 1.3rem;
padding-top: 3px;
&:hover {
text-decoration: underline !important;
cursor: pointer;
}
}
`;
export { Nav, Wrapper };

View File

@ -0,0 +1,180 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { isEmpty } from 'lodash';
import { request } from 'strapi-helper-plugin';
import pluginId from '../../pluginId';
import SelectOne from '../SelectOne';
import SelectMany from '../SelectMany';
import { Nav, Wrapper } from './components';
function SelectWrapper({
addRelation,
label,
mainField,
name,
onChange,
pathname,
relationType,
search,
targetModel,
plugin,
value,
}) {
const [state, setState] = useState({
_q: '',
_limit: 8,
_start: 0,
source: isEmpty(plugin) ? 'content-manager' : plugin,
});
const [options, setOptions] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const ref = useRef();
const startRef = useRef();
startRef.current = state._start;
ref.current = async (uid, params = state) => {
try {
const requestUrl = `/${pluginId}/explorer/${uid}`;
if (isEmpty(params._q)) {
delete params._q;
}
const data = await request(requestUrl, {
method: 'GET',
params: params,
});
const formattedData = data.map(obj => {
return { value: obj, label: obj[mainField] };
});
if (!isEmpty(params._q)) {
setOptions(formattedData);
return;
}
setOptions(prevState =>
prevState.concat(formattedData).filter((obj, index) => {
const objIndex = prevState.findIndex(
el => el.value.id === obj.value.id
);
if (objIndex === -1) {
return true;
}
return (
prevState.findIndex(el => el.value.id === obj.value.id) === index
);
})
);
setIsLoading(false);
} catch (err) {
strapi.notification.error('notification.error');
}
};
useEffect(() => {
ref.current(targetModel);
return () => {};
}, [ref, targetModel, state._start, state._q]);
const onInputChange = inputValue => {
setState(prevState => {
if (prevState._q === inputValue) {
return prevState;
}
return { ...prevState, _q: inputValue };
});
return inputValue;
};
const onMenuScrollToBottom = () => {
setState(prevState => ({ ...prevState, _start: prevState._start + 1 }));
};
const isSingle = [
'oneWay',
'oneToOne',
'manyToOne',
'oneToManyMorph',
'oneToOneMorph',
].includes(relationType);
const nextSearch = `${pathname}${search}`;
const to = `/plugins/${pluginId}/${targetModel}/${
value ? value.id : null
}?redirectUrl=${nextSearch}`;
const link =
value === null ||
value === undefined ||
['role', 'permission'].includes(targetModel) ? null : (
<Link to={to}>
<FormattedMessage id="content-manager.containers.Edit.seeDetails" />
</Link>
);
const Component = isSingle ? SelectOne : SelectMany;
return (
<Wrapper className="form-group">
<Nav>
<label htmlFor={name}>{label}</label>
{isSingle && link}
</Nav>
<Component
addRelation={value => {
addRelation({ target: { name, value } });
}}
id={name}
isLoading={isLoading}
isClearable
mainField={mainField}
name={name}
options={options}
onChange={value => {
onChange({ target: { name, value: value ? value.value : value } });
}}
onInputChange={onInputChange}
onMenuClose={() => {
setState(prevState => ({ ...prevState, _q: '', _start: 0 }));
}}
onMenuScrollToBottom={onMenuScrollToBottom}
placeholder={
<FormattedMessage id={`${pluginId}.containers.Edit.addAnItem`} />
}
value={value}
/>
<div style={{ marginBottom: 26 }} />
</Wrapper>
);
}
SelectWrapper.defaultProps = {
description: '',
label: '',
plugin: '',
value: null,
};
SelectWrapper.propTypes = {
addRelation: PropTypes.func.isRequired,
description: PropTypes.string,
label: PropTypes.string,
mainField: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
plugin: PropTypes.string,
relationType: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
targetModel: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
};
export default SelectWrapper;

View File

@ -39,7 +39,7 @@ const getInputType = (type = '') => {
} }
}; };
function Inputs({ keys, layout, modifiedData, name, onChange }) { function Inputs({ autoFocus, keys, layout, modifiedData, name, onChange }) {
const attribute = get(layout, ['schema', 'attributes', name], {}); const attribute = get(layout, ['schema', 'attributes', name], {});
const { model, collection } = attribute; const { model, collection } = attribute;
const isMedia = const isMedia =
@ -68,6 +68,7 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) {
return ( return (
<InputsIndex <InputsIndex
{...metadata} {...metadata}
autoFocus={autoFocus}
inputDescription={description} inputDescription={description}
inputStyle={inputStyle} inputStyle={inputStyle}
customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }} customInputs={{ json: InputJSONWithErrors, wysiwyg: WysiwygWithErrors }}
@ -82,7 +83,12 @@ function Inputs({ keys, layout, modifiedData, name, onChange }) {
); );
} }
Inputs.defaultProps = {
autoFocus: false,
};
Inputs.propTypes = { Inputs.propTypes = {
autoFocus: PropTypes.bool,
keys: PropTypes.string.isRequired, keys: PropTypes.string.isRequired,
layout: PropTypes.object.isRequired, layout: PropTypes.object.isRequired,
modifiedData: PropTypes.object.isRequired, modifiedData: PropTypes.object.isRequired,

View File

@ -15,7 +15,6 @@ import {
import pluginId from '../../pluginId'; import pluginId from '../../pluginId';
import Container from '../../components/Container'; import Container from '../../components/Container';
import SelectOne from '../../components/SelectOne';
import { LinkWrapper, MainWrapper, SubWrapper } from './components'; import { LinkWrapper, MainWrapper, SubWrapper } from './components';
import Group from './Group'; import Group from './Group';
@ -23,6 +22,7 @@ import Inputs from './Inputs';
import init, { setDefaultForm } from './init'; import init, { setDefaultForm } from './init';
import reducer, { initialState } from './reducer'; import reducer, { initialState } from './reducer';
import SelectWrapper from '../../components/SelectWrapper';
const getRequestUrl = path => `/${pluginId}/explorer/${path}`; const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
@ -371,9 +371,10 @@ function EditView({
return ( return (
<div key={key} className="row"> <div key={key} className="row">
{fieldsRow.map(({ name }) => { {fieldsRow.map(({ name }, index) => {
return ( return (
<Inputs <Inputs
autoFocus={key === 0 && index === 0}
key={name} key={name}
keys={name} keys={name}
layout={layout} layout={layout}
@ -413,22 +414,19 @@ function EditView({
{} {}
); );
const value = get(modifiedData, [relationName], null); const value = get(modifiedData, [relationName], null);
const Component = [
'oneWay',
'oneToOne',
'manyToOne',
'oneToManyMorph',
'oneToOneMorph',
].includes(relation.relationType)
? SelectOne
: // eslint-disable-next-line react/display-name
() => <div>SelectMany</div>;
return ( return (
<Component <SelectWrapper
key={relationName}
{...relation} {...relation}
{...relationMetas} {...relationMetas}
key={relationName}
addRelation={({ target: { name, value } }) => {
dispatch({
type: 'ADD_RELATION',
keys: name.split('.'),
value,
});
}}
name={relationName} name={relationName}
onChange={({ target: { name, value } }) => { onChange={({ target: { name, value } }) => {
dispatch({ dispatch({
@ -438,6 +436,7 @@ function EditView({
}); });
}} }}
pathname={pathname} pathname={pathname}
relationType={relation.relationType}
search={search} search={search}
value={value} value={value}
/> />

View File

@ -44,6 +44,20 @@ function reducer(state, action) {
: { _temp__id: 0 }, : { _temp__id: 0 },
]); ]);
}); });
case 'ADD_RELATION':
return state.updateIn(['modifiedData', ...action.keys], list => {
if (!action.value) {
return list;
}
const el = action.value[0].value;
if (list) {
return list.push(fromJS(el));
}
return fromJS([el]);
});
case 'GET_DATA_SUCCEEDED': case 'GET_DATA_SUCCEEDED':
return state return state
.update('initialData', () => fromJS(action.data)) .update('initialData', () => fromJS(action.data))