Relation modal ip

This commit is contained in:
Virginie Ky 2019-07-22 18:32:25 +02:00
parent 9fc54cd8f9
commit a312d6da15
16 changed files with 867 additions and 23 deletions

View File

@ -14,6 +14,13 @@ const colors = {
mediumGrey: '#f2f3f4',
lightGrey: '#E9EAEB',
},
// Specific colors for relations
relations: {
boxBkgd: '#fcfcfc',
boxShadow: '#cad2df',
headerBkgd: 'rgba(16,22,34,.04)',
},
};
export default colors;

View File

@ -0,0 +1,85 @@
import styled from 'styled-components';
const StyledFeaturePicker = styled.div`
> div {
width: 100%;
> button {
position: relative;
width: 100%;
font-size: 1.4rem;
line-height: 3.6rem;
background-color: transparent;
border: none;
cursor: pointer;
text-transform: capitalize;
color: black;
&:focus,
&:active,
&:hover,
&:visited {
background-color: transparent !important;
box-shadow: none;
color: black;
}
&:after {
position: absolute;
top: calc(50% - 0.1rem);
right: 10px;
}
> p {
margin-top: -1px;
margin-bottom: 0;
padding: 0 20px 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
> i {
margin-right: 8px;
}
span {
font-style: italic;
font-weight: 500;
}
}
}
// Dropdown List
> button + div {
max-width: 100%;
max-height: 180px;
width: 100%;
margin-top: -4px;
padding: 0;
border-radius: 0;
overflow: scroll;
> button {
height: 3.6rem;
border-bottom: 1px solid #f6f6f6;
font-size: 1.3rem;
font-family: Lato;
font-weight: 400;
font-family: Lato;
text-transform: capitalize;
-webkit-font-smoothing: antialiased;
cursor: pointer;
&:focus,
&:active {
outline: 0;
background-color: rgb(255, 255, 255) !important;
color: rgba(50, 55, 64, 0.75);
}
> p {
color: rgba(50, 55, 64, 0.75);
line-height: 3rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
i {
margin-right: 10px;
}
}
}
}
}
`;
export default StyledFeaturePicker;

View File

@ -0,0 +1,67 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import StyledFeaturePicker from './StyledFeaturePicker';
const FeaturePicker = ({ features, onClick, plugin, selectFeature }) => {
const [isOpen, setOpen] = React.useState(false);
console.log(selectFeature);
return (
<StyledFeaturePicker>
<Dropdown
isOpen={isOpen}
toggle={() => {
setOpen(!isOpen);
}}
>
<DropdownToggle caret>
<p>
<i className="fa fa-caret-square-o-right" />
{selectFeature}
{!!plugin && <span>&nbsp;({plugin})</span>}
</p>
</DropdownToggle>
<DropdownMenu>
{features.map(feature => {
return (
<DropdownItem key={feature.name} onClick={() => onClick(feature)}>
<p>
<i className="fa fa-caret-square-o-right" />
{feature.name}
{!!feature.source && (
<span style={{ fontStyle: 'italic' }}>
&nbsp;({feature.source})
</span>
)}
</p>
</DropdownItem>
);
})}
</DropdownMenu>
</Dropdown>
</StyledFeaturePicker>
);
};
FeaturePicker.defaultProps = {
features: [],
onClick: () => {},
plugin: null,
selectFeature: '',
};
FeaturePicker.propTypes = {
features: PropTypes.array,
onClick: PropTypes.func,
plugin: PropTypes.string,
selectFeature: PropTypes.string,
};
export default FeaturePicker;

View File

@ -0,0 +1,28 @@
import React from 'react';
import Enzyme from 'enzyme';
import FeaturePicker from '../index';
describe('<FeaturePicker />', () => {
let wrapper;
const setOpen = jest.fn();
const useStateSpy = jest.spyOn(React, 'useState');
useStateSpy.mockImplementation(init => [init, setOpen]);
beforeEach(() => {
wrapper = Enzyme.shallow(<FeaturePicker />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('SetOpen', () => {
it('calls setOpen with true param', () => {
const buttonProps = wrapper.find('button').props();
buttonProps.onClick();
expect(setOpen).toHaveBeenCalledWith(true);
});
});
});

View File

@ -0,0 +1,28 @@
import styled from 'styled-components';
import { colors } from 'strapi-helper-plugin';
const StyledRelationBox = styled.div`
width: 20rem;
height: 13.8rem;
background-color: ${colors.relations.boxBkgd};
box-shadow: 0 1px 2px ${colors.relations.boxShadow};
border-radius: 2px;
.box-header {
height: 3.6rem;
line-height: 3.6rem;
text-align: center;
font-size: 1.4rem;
font-weight: 700;
text-transform: capitalize;
background-color: ${colors.relations.headerBkgd};
i {
margin-right: 8px;
}
}
.box-body {
padding-top: 1rem;
}
`;
export default StyledRelationBox;

View File

@ -0,0 +1,87 @@
import React from 'react';
import PropTypes from 'prop-types';
import { InputTextWithErrors as InputText } from 'strapi-helper-plugin';
import StyledRelationBox from './StyledRelationBox';
import FeaturePicker from '../FeaturePicker';
const RelationBox = ({
autoFocus,
didCheckErrors,
errors,
featureName,
features,
main,
nature,
onChange,
selectedFeature,
source,
plugin,
value,
}) => {
return (
<StyledRelationBox>
<div className="box-header">
{main ? (
<p>
<i className="fa fa-caret-square-o-right" />
{featureName}
{!!source && <span>&nbsp;({source})</span>}
</p>
) : (
<FeaturePicker
features={features}
plugin={plugin}
selectedFeature={selectedFeature}
></FeaturePicker>
)}
</div>
<div className="box-body">
<InputText
autoFocus={autoFocus}
didCheckErrors={didCheckErrors}
errors={errors}
label="Field Name"
disabled={value === '-' || nature === 'oneWay'}
name={main ? 'name' : 'key'}
onChange={onChange}
type="text"
value={nature === 'oneWay' ? '-' : value}
/>
</div>
</StyledRelationBox>
);
};
RelationBox.defaultProps = {
autoFocus: false,
didCheckErrors: false,
errors: [],
main: false,
featureName: '',
features: [],
nature: null,
onClick: () => {},
plugin: null,
selectedModel: null,
source: null,
};
RelationBox.propTypes = {
autoFocus: PropTypes.bool,
didCheckErrors: PropTypes.bool,
errors: PropTypes.array,
main: PropTypes.bool,
featureName: PropTypes.string,
features: PropTypes.array,
nature: PropTypes.string,
onChange: PropTypes.func.isRequired,
onClick: PropTypes.func,
plugin: PropTypes.string,
selectedModel: PropTypes.string,
source: PropTypes.string,
value: PropTypes.string.isRequired,
};
export default RelationBox;

View File

@ -0,0 +1,7 @@
import React from 'react';
describe('<RelationBox />', () => {
it('should not crash', () => {
expect(false).toBe(true);
});
});

View File

@ -0,0 +1,17 @@
import styled from 'styled-components';
import { colors } from 'strapi-helper-plugin';
const RelationsWrapper = styled.div`
width: 100%;
display: flex;
padding: 2.7rem 1.5rem 3.3rem 1.5rem;
justify-content: space-between;
> div {
width: 200px;
background-color: ${colors.relations.boxBkgd};
box-shadow: 0 1px 2px ${colors.relations.boxShadow};
}
`;
export default RelationsWrapper;

View File

@ -7,6 +7,7 @@ import { cloneDeep, pick, set, camelCase } from 'lodash';
import { fromJS, OrderedMap } from 'immutable';
import {
ADD_ATTRIBUTE_RELATION,
ADD_ATTRIBUTE_RELATION_GROUP,
ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE,
ADD_ATTRIBUTE_TO_EXISTING_GROUP,
ADD_ATTRIBUTE_TO_TEMP_CONTENT_TYPE,
@ -32,6 +33,7 @@ import {
ON_CHANGE_ATTRIBUTE,
ON_CHANGE_ATTRIBUTE_GROUP,
ON_CHANGE_RELATION,
ON_CHANGE_RELATION_GROUP,
ON_CHANGE_RELATION_NATURE,
ON_CHANGE_RELATION_TARGET,
RESET_NEW_CONTENT_TYPE_MAIN_INFOS,
@ -47,6 +49,7 @@ import {
SET_TEMPORARY_ATTRIBUTE,
SET_TEMPORARY_ATTRIBUTE_GROUP,
SET_TEMPORARY_ATTRIBUTE_RELATION,
SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
SUBMIT_CONTENT_TYPE,
SUBMIT_CONTENT_TYPE_SUCCEEDED,
SUBMIT_GROUP,
@ -68,6 +71,14 @@ export function addAttributeRelation(isModelTemporary, modelName) {
};
}
export function addAttributeRelationGroup(isGroupTemporary, groupName) {
return {
type: ADD_ATTRIBUTE_RELATION_GROUP,
isGroupTemporary,
groupName,
};
}
export function addAttributeToExistingContentType(
contentTypeName,
attributeType
@ -350,6 +361,17 @@ export function onChangeRelation({ target }) {
};
}
export function onChangeRelationGroup({ target }) {
const value =
target.name === 'unique' ? target.value : target.value.split(' ').join('');
return {
type: ON_CHANGE_RELATION_GROUP,
keys: target.name.split('.'),
value,
};
}
export function onChangeRelationNature(nature, currentModel) {
return {
type: ON_CHANGE_RELATION_NATURE,
@ -494,6 +516,23 @@ export function setTemporaryAttributeRelation(
};
}
export function setTemporaryAttributeRelationGroup(
target,
isGroupTemporary,
source,
attributeName,
isEditing
) {
return {
type: SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
attributeName,
isEditing,
isGroupTemporary,
source,
target,
};
}
export function submitContentType(oldContentTypeName, data, context, source) {
const attributes = formatModelAttributes(data.attributes);
const body = Object.assign(cloneDeep(data), { attributes });

View File

@ -6,6 +6,8 @@
export const ADD_ATTRIBUTE_RELATION =
'ContentTypeBuilder/App/ADD_ATTRIBUTE_RELATION';
export const ADD_ATTRIBUTE_RELATION_GROUP =
'ContentTypeBuilder/App/ADD_ATTRIBUTE_RELATION_GROUP';
export const ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE =
'ContentTypeBuilder/App/ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE';
export const ADD_ATTRIBUTE_TO_EXISTING_GROUP =
@ -82,6 +84,8 @@ export const SET_TEMPORARY_ATTRIBUTE_GROUP =
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_GROUP';
export const SET_TEMPORARY_ATTRIBUTE_RELATION =
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_RELATION';
export const SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP =
'ContentTypeBuilder/App/SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP';
export const SUBMIT_CONTENT_TYPE = 'ContentTypeBuilder/App/SUBMIT_CONTENT_TYPE';
export const SUBMIT_CONTENT_TYPE_SUCCEEDED =
'ContentTypeBuilder/App/SUBMIT_CONTENT_TYPE_SUCCEEDED';

View File

@ -8,6 +8,7 @@ import { fromJS, List, Map, OrderedMap } from 'immutable';
import pluralize from 'pluralize';
import {
ADD_ATTRIBUTE_RELATION,
ADD_ATTRIBUTE_RELATION_GROUP,
ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE,
ADD_ATTRIBUTE_TO_EXISTING_GROUP,
ADD_ATTRIBUTE_TO_TEMP_CONTENT_TYPE,
@ -32,6 +33,7 @@ import {
ON_CHANGE_ATTRIBUTE,
ON_CHANGE_ATTRIBUTE_GROUP,
ON_CHANGE_RELATION,
ON_CHANGE_RELATION_GROUP,
ON_CHANGE_RELATION_TARGET,
RESET_EXISTING_CONTENT_TYPE_MAIN_INFOS,
RESET_EXISTING_GROUP_MAIN_INFOS,
@ -46,6 +48,7 @@ import {
SET_TEMPORARY_ATTRIBUTE,
SET_TEMPORARY_ATTRIBUTE_GROUP,
SET_TEMPORARY_ATTRIBUTE_RELATION,
SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP,
SUBMIT_CONTENT_TYPE_SUCCEEDED,
SUBMIT_GROUP_SUCCEEDED,
SUBMIT_TEMP_CONTENT_TYPE_SUCCEEDED,
@ -90,6 +93,17 @@ export const initialState = fromJS({
target: '',
unique: false,
},
temporaryAttributeRelationGroup: {
name: '',
columnName: '',
dominant: false,
targetColumnName: '',
key: '-',
nature: 'oneWay',
plugin: '',
target: '',
unique: false,
},
initialTemporaryAttributeRelation: {
name: '',
columnName: '',
@ -173,6 +187,52 @@ function appReducer(state = initialState, action) {
return newState;
}
case ADD_ATTRIBUTE_RELATION_GROUP: {
const { isGroupTemporary, groupName } = action;
const basePath = isGroupTemporary
? ['newGroup']
: ['modifiedDataGroup', groupName];
const { key, name, nature, target } = state
.get('temporaryAttributeRelationGroup')
.toJS();
let newState = state.updateIn(
[...basePath, 'schema', 'attributes', name],
() => {
const newAttribute = state
.get('temporaryAttributeRelationGroup')
.remove('name');
return newAttribute;
}
);
if (target === groupName && nature !== 'oneWay') {
newState = newState.updateIn([...basePath, 'attributes', key], () => {
const newAttribute = state
.get('temporaryAttributeRelationGroup')
.set(
'key',
state.getIn(['temporaryAttributeRelationGroup', 'name'])
)
.update('dominant', () => false)
.update('nature', value => {
if (nature === 'oneToMany') {
return 'manyToOne';
} else if (nature === 'manyToOne') {
return 'oneToMany';
} else {
return value;
}
})
.remove('name');
return newAttribute;
});
}
return newState;
}
case ADD_ATTRIBUTE_TO_EXISITING_CONTENT_TYPE: {
return state
.updateIn(
@ -431,6 +491,11 @@ function appReducer(state = initialState, action) {
['temporaryAttributeRelation', ...action.keys],
() => action.value
);
case ON_CHANGE_RELATION_GROUP:
return state.updateIn(
['temporaryAttributeRelationGroup', ...action.keys],
() => action.value
);
case ON_CHANGE_RELATION_NATURE: {
const { currentModel, nature } = action;
@ -744,6 +809,49 @@ function appReducer(state = initialState, action) {
() => action.source || ''
);
}
case SET_TEMPORARY_ATTRIBUTE_RELATION_GROUP: {
if (action.isEditing) {
const basePath = action.isGroupTemporary
? ['newGroup']
: ['modifiedDataGroup', action.target];
return state
.update('temporaryAttributeRelationGroup', () =>
state
.getIn([
...basePath,
'schema',
'attributes',
action.attributeName,
])
.set('name', action.attributeName)
)
.update('initialTemporaryAttributeRelationGroup', () =>
state
.getIn([
...basePath,
'schema',
'attributes',
action.attributeName,
])
.set('name', action.attributeName)
);
}
return state
.updateIn(
['temporaryAttributeRelationGroup', 'target'],
() => action.target
)
.updateIn(
['temporaryAttributeRelationGroup', 'name'],
() => action.target
)
.updateIn(
['temporaryAttributeRelationGroup', 'plugin'],
() => action.source || ''
);
}
case SUBMIT_CONTENT_TYPE_SUCCEEDED: {
return state
.update('isLoading', () => true)

View File

@ -12,6 +12,7 @@ import ListRow from '../../components/ListRow';
import AttributesModalPicker from '../AttributesPickerModal';
import AttributeForm from '../AttributeForm';
import ViewContainer from '../ViewContainer';
import RelationFormGroup from '../RelationFormGroup';
import {
BackHeader,
@ -26,13 +27,16 @@ import {
} from 'strapi-helper-plugin';
import {
addAttributeRelationGroup,
addAttributeToTempGroup,
addAttributeToExistingGroup,
clearTemporaryAttributeGroup,
deleteGroupAttribute,
onChangeAttributeGroup,
onChangeRelationGroup,
saveEditedAttributeGroup,
setTemporaryAttributeGroup,
setTemporaryAttributeRelationGroup,
submitGroup,
submitTempGroup,
resetEditTempGroup,
@ -145,7 +149,7 @@ export class GroupPage extends React.Component {
/* istanbul ignore if */
const title = this.isUpdatingTempFeature()
? get(newGroup, 'name', null)
: get(modifiedDataGroup, [name, 'schema', 'name'], null);
: get(modifiedDataGroup, [name, 'name'], null);
return title;
};
@ -293,6 +297,7 @@ export class GroupPage extends React.Component {
handleSubmit = (shouldContinue = false) => {
const {
addAttributeRelationGroup,
addAttributeToExistingGroup,
addAttributeToTempGroup,
history: { push },
@ -300,10 +305,14 @@ export class GroupPage extends React.Component {
const attributeType = this.getAttributeType();
if (this.isUpdatingTempFeature()) {
addAttributeToTempGroup(attributeType);
if (this.getAttributeType() === 'relation') {
addAttributeRelation(this.isUpdatingTempFeature(), this.getFeatureName());
} else {
addAttributeToExistingGroup(this.getFeatureName(), attributeType);
if (this.isUpdatingTempFeature()) {
addAttributeToTempGroup(attributeType);
} else {
addAttributeToExistingGroup(this.getFeatureName(), attributeType);
}
}
const nextSearch = shouldContinue ? 'modalType=chooseAttributes' : '';
@ -382,11 +391,14 @@ export class GroupPage extends React.Component {
render() {
const {
clearTemporaryAttributeGroup,
groups,
history: { push },
onChangeAttributeGroup,
onChangeRelationGroup,
temporaryAttributeGroup,
temporaryAttributeRelationGroup,
setTemporaryAttributeRelationGroup,
} = this.props;
const { showWarning } = this.state;
const attributes = this.getFeatureAttributes();
@ -472,7 +484,10 @@ export class GroupPage extends React.Component {
attributeType={this.getAttributeType()}
attributeToEditName={this.getAttributeName()}
featureType={this.featureType}
isOpen={this.getModalType() === 'attributeForm'}
isOpen={
this.getModalType() === 'attributeForm' &&
this.getAttributeType() !== 'relation'
}
modifiedData={temporaryAttributeGroup}
onCancel={clearTemporaryAttributeGroup}
onChange={onChangeAttributeGroup}
@ -481,6 +496,25 @@ export class GroupPage extends React.Component {
push={push}
/>
<RelationFormGroup
actionType={this.getActionType()}
activeTab={this.getSettingType()}
alreadyTakenAttributes={this.getFeatureAttributesNames()}
featureType={this.featureType}
featureName={this.getFeatureName()}
features={groups}
isOpen={
this.getModalType() === 'attributeForm' &&
this.getAttributeType() === 'relation'
}
modifiedData={temporaryAttributeRelationGroup}
onCancel={() => {}}
onChange={onChangeRelationGroup}
onSubmit={this.handleSubmit}
setTempAttribute={setTemporaryAttributeRelationGroup}
push={push}
/>
<PopUpWarning
isOpen={showWarning}
toggleModal={this.toggleModalWarning}
@ -504,6 +538,7 @@ GroupPage.defaultProps = {
};
GroupPage.propTypes = {
addAttributeRelationGroup: PropTypes.func.isRequired,
addAttributeToExistingGroup: PropTypes.func.isRequired,
addAttributeToTempGroup: PropTypes.func.isRequired,
canOpenModal: PropTypes.bool,
@ -526,24 +561,30 @@ GroupPage.propTypes = {
modifiedDataGroup: PropTypes.object.isRequired,
newGroup: PropTypes.object.isRequired,
onChangeAttributeGroup: PropTypes.func.isRequired,
onChangeRelationGroup: PropTypes.func.isRequired,
resetEditTempGroup: PropTypes.func.isRequired,
saveEditedAttributeGroup: PropTypes.func.isRequired,
setTemporaryAttributeGroup: PropTypes.func.isRequired,
setTemporaryAttributeRelationGroup: PropTypes.func.isRequired,
submitGroup: PropTypes.func.isRequired,
submitTempGroup: PropTypes.func.isRequired,
temporaryAttributeGroup: PropTypes.object.isRequired,
temporaryAttributeRelationGroup: PropTypes.object.isRequired,
};
export function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
addAttributeRelationGroup,
addAttributeToExistingGroup,
addAttributeToTempGroup,
clearTemporaryAttributeGroup,
deleteGroupAttribute,
onChangeAttributeGroup,
onChangeRelationGroup,
saveEditedAttributeGroup,
setTemporaryAttributeGroup,
setTemporaryAttributeRelationGroup,
submitGroup,
submitTempGroup,
resetEditTempGroup,

View File

@ -38,6 +38,7 @@ const renderComponent = (props = {}) => {
const basePath = '/plugins/content-type-builder/groups';
const props = {
addAttributeRelationGroup: jest.fn(),
addAttributeToExistingGroup: jest.fn(),
addAttributeToTempGroup: jest.fn(),
clearTemporaryAttributeGroup: jest.fn(),
@ -125,6 +126,7 @@ const props = {
resetEditTempGroup: jest.fn(),
saveEditedAttributeGroup: jest.fn(),
setTemporaryAttributeGroup: jest.fn(),
setTemporaryAttributeRelationGroup: jest.fn(),
submitTempGroup: jest.fn(),
submitGroup: jest.fn(),
temporaryAttributeGroup: {},

View File

@ -309,23 +309,6 @@ class RelationForm extends React.Component {
<hr />
</section>
</HeaderModal>
{/* <HeaderModal>
<div style={{ fontSize: '1.8rem', fontWeight: 'bold' }}>
<FormattedMessage
id={`${pluginId}.popUpForm.${actionType || 'create'}`}
/>
&nbsp;
<span style={{ fontStyle: 'italic', textTransform: 'capitalize' }}>
{titleContent}
</span>
&nbsp;
<FormattedMessage id={`${pluginId}.popUpForm.field`} />
</div>
<HeaderModalNavContainer>
{NAVLINKS.map(this.renderNavLink)}
</HeaderModalNavContainer>
</HeaderModal> */}
<form onSubmit={this.handleSubmitAndContinue}>
<BodyModal>{showForm && content}</BodyModal>
<FooterModal>

View File

@ -0,0 +1,334 @@
/**
*
* RelationFormGroup
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { get, isEmpty, upperFirst } from 'lodash';
import pluginId from '../../pluginId';
import BodyModal from '../../components/BodyModal';
import ButtonModalPrimary from '../../components/ButtonModalPrimary';
import ButtonModalSecondary from '../../components/ButtonModalSecondary';
import ButtonModalSuccess from '../../components/ButtonModalSuccess';
import FooterModal from '../../components/FooterModal';
import HeaderModal from '../../components/HeaderModal';
import HeaderModalNavContainer from '../../components/HeaderModalNavContainer';
import HeaderModalTitle from '../../components/HeaderModalTitle';
import HeaderNavLink from '../../components/HeaderNavLink';
import RelationBox from '../../components/RelationBox';
import RelationsWrapper from '../../components/RelationsWrapper';
import WrapperModal from '../../components/WrapperModal';
// import NaturePicker from './NaturePicker';
// import RelationBox from './RelationBox';
import Icon from '../../assets/icons/icon_type_ct.png';
import IconGroup from '../../assets/icons/icon_type_groups.png';
const NAVLINKS = [{ id: 'base', custom: 'relation' }, { id: 'advanced' }];
class RelationFormGroup extends React.Component {
// eslint-disable-line react/prefer-stateless-function
state = { didCheckErrors: false, formErrors: {}, showForm: false };
getFormErrors = () => {
const {
actionType,
alreadyTakenAttributes,
attributeToEditName,
modifiedData,
} = this.props;
const formValidations = {
name: { required: true, unique: true },
key: { required: true, unique: true },
};
const alreadyTakenAttributesUpdated = alreadyTakenAttributes.filter(
attribute => {
if (actionType === 'edit') {
return (
attribute !== attributeToEditName && attribute !== modifiedData.key
);
}
return attribute !== attributeToEditName;
}
);
let formErrors = {};
if (modifiedData.name === modifiedData.key) {
formErrors = { key: [{ id: `${pluginId}.error.attribute.key.taken` }] };
}
formErrors = Object.keys(formValidations).reduce((acc, current) => {
const { required, unique } = formValidations[current];
const value = modifiedData[current];
if (required === true && !value) {
acc[current] = [{ id: `${pluginId}.error.validation.required` }];
}
if (unique === true && alreadyTakenAttributesUpdated.includes(value)) {
acc[current] = [{ id: `${pluginId}.error.attribute.key.taken` }];
}
return acc;
}, formErrors);
this.setState(prevState => ({
didCheckErrors: !prevState.didCheckErrors,
formErrors,
}));
return formErrors;
};
getIcon = () => {
const { featureType } = this.props;
return featureType === 'model' ? Icon : IconGroup;
};
handleCancel = () => {
const { push } = this.props;
push({ search: '' });
};
handleOnClosed = () => {
const { onCancel } = this.props;
onCancel();
this.setState({ formErrors: {}, showForm: false }); // eslint-disable-line react/no-unused-state
};
handleOnOpened = () => {
const {
actionType,
attributeToEditName,
isUpdatingTemporary,
features,
featureToEditName,
setTempAttribute,
} = this.props;
const [{ name, source }] = features;
const target = actionType === 'edit' ? featureToEditName : name;
console.log(target);
setTempAttribute(
target,
isUpdatingTemporary,
source,
attributeToEditName,
actionType === 'edit'
);
this.setState({ showForm: true });
};
handleToggle = () => {
const { push } = this.props;
push({ search: '' });
};
handleGoTo = to => {
const { emitEvent } = this.context;
const { actionType, attributeToEditName, push } = this.props;
const attributeName =
actionType === 'edit' ? `&attributeName=${attributeToEditName}` : '';
if (to === 'advanced') {
emitEvent('didSelectContentTypeFieldSettings');
}
push({
search: `modalType=attributeForm&attributeType=relation&settingType=${to}&actionType=${actionType}${attributeName}`,
});
};
handleSubmitAndContinue = e => {
e.preventDefault();
if (isEmpty(this.getFormErrors())) {
this.submit(true);
}
};
renderNavLink = (link, index) => {
const { activeTab } = this.props;
return (
<HeaderNavLink
isActive={activeTab === link.id}
key={link.id}
{...link}
onClick={this.handleGoTo}
nextTab={index === NAVLINKS.length - 1 ? 0 : index + 1}
/>
);
};
submit = (shouldContinue = false) => {
const { actionType, onSubmit, onSubmitEdit } = this.props;
if (actionType === 'edit') {
onSubmitEdit(shouldContinue);
} else {
onSubmit(shouldContinue);
}
};
renderRelationForm = () => {
const {
featureName,
modifiedData: { key, name, nature, plugin, target },
features,
onChange,
onChangeRelationNature,
source,
} = this.props;
const { formErrors, didCheckErrors } = this.state;
return (
<RelationsWrapper>
<RelationBox
autoFocus
didCheckErrors={didCheckErrors}
errors={get(formErrors, 'name', [])}
featureName={featureName}
main
onChange={onChange}
source={source}
value={name}
/>
<RelationBox
autoFocus
didCheckErrors={didCheckErrors}
errors={get(formErrors, 'key', [])}
features={features}
nature={nature}
onChange={onChange}
plugin={plugin}
selectedFeature={target}
source={source}
value={key}
/>
{/* <RelationBox
autoFocus
errors={get(formErrors, 'name', [])}
didCheckErrors={didCheckErrors}
main
modelName={groupToEditName}
onChange={onChange}
source={source}
value={name}
/>
<NaturePicker
modelName={groupToEditName}
nature={nature}
name={name}
target={target}
onClick={onChangeRelationNature}
/>
<RelationBox
errors={get(formErrors, 'key', [])}
didCheckErrors={didCheckErrors}
groups={groups}
nature={nature}
onChange={onChange}
onClick={this.handleClick}
selectedModel={target}
plugin={plugin}
value={key}
/> */}
</RelationsWrapper>
);
};
render() {
const { actionType, activeTab, attributeToEditName, isOpen } = this.props;
const { showForm } = this.state;
const titleContent =
actionType === 'create' ? 'relation' : attributeToEditName;
const content =
activeTab === 'base' || !activeTab
? this.renderRelationForm()
: this.renderAdvancedSettings();
return (
<WrapperModal
isOpen={isOpen}
onClosed={this.handleOnClosed}
onOpened={this.handleOnOpened}
onToggle={this.handleToggle}
>
<HeaderModal>
<section>
<HeaderModalTitle>
<img src={this.getIcon()} alt="ct" />
<span>{titleContent}</span>
</HeaderModalTitle>
</section>
<section>
<HeaderModalTitle>
<FormattedMessage
id={`${pluginId}.popUpForm.${actionType || 'create'}`}
/>
</HeaderModalTitle>
<div className="settings-tabs">
<HeaderModalNavContainer>
{NAVLINKS.map(this.renderNavLink)}
</HeaderModalNavContainer>
</div>
<hr />
</section>
</HeaderModal>
<form onSubmit={this.handleSubmitAndContinue}>
<BodyModal>{showForm && content}</BodyModal>
<FooterModal>
<section>
<ButtonModalPrimary
message={`${pluginId}.form.button.add`}
type="submit"
add
/>
</section>
<section>
<ButtonModalSecondary
message={`${pluginId}.form.button.cancel`}
onClick={this.handleCancel}
/>
<ButtonModalSuccess
message={`${pluginId}.form.button.done`}
type="button"
onClick={this.handleSubmit}
/>
</section>
</FooterModal>
</form>
</WrapperModal>
);
}
}
RelationFormGroup.contextTypes = {
emitEvent: PropTypes.func,
};
RelationFormGroup.defaultProps = {
featureType: 'model',
};
RelationFormGroup.propTypes = {
featureType: PropTypes.string,
};
export default RelationFormGroup;

View File

@ -0,0 +1,7 @@
import React from 'react';
describe('<RelationFormGroup />', () => {
it('should not crash', () => {
expect(false).toBe(true);
});
});