- Fix for #890
- "Channels" feature - Shoutout config param rendered on the homepage - "Clone" feature for campaigns
This commit is contained in:
parent
00432e6cfe
commit
d170548cfa
25 changed files with 1009 additions and 525 deletions
|
@ -25,7 +25,7 @@ export default class List extends Component {
|
|||
return (
|
||||
<div>
|
||||
<h2>{t('Mailtrain 2 beta')}</h2>
|
||||
<div>{t('Build') + ' 2020-05-28-0102'}</div>
|
||||
<div>{t('Build') + ' 2020-07-17-0000'}</div>
|
||||
<p>{this.props.configItems.shoutout}</p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -33,7 +33,7 @@ import {getUrl} from "../lib/urls";
|
|||
import {campaignOverridables, CampaignSource, CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
import {getCampaignLabels, ListsSelectorHelper} from "./helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Trans} from "react-i18next";
|
||||
|
@ -51,6 +51,8 @@ export default class CUD extends Component {
|
|||
|
||||
const t = props.t;
|
||||
|
||||
this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists');
|
||||
|
||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
|
||||
this.tagLanguages = getTagLanguages(props.t);
|
||||
|
||||
|
@ -79,20 +81,9 @@ export default class CUD extends Component {
|
|||
[CampaignSource.URL]: t('url')
|
||||
};
|
||||
|
||||
let sourceLabelsOrder;
|
||||
|
||||
if (props.createFromChannel) {
|
||||
// If a campaign is created within a channel, we allow only for those source types that makes sense
|
||||
sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN, CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
];
|
||||
|
||||
} else {
|
||||
// Regular creation or createFromCampaign
|
||||
sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
}
|
||||
const sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
|
||||
this.sourceOptions = [];
|
||||
for (const key of sourceLabelsOrder) {
|
||||
|
@ -113,8 +104,6 @@ export default class CUD extends Component {
|
|||
sendConfiguration: null
|
||||
};
|
||||
|
||||
this.nextListEntryId = 0;
|
||||
|
||||
this.initForm({
|
||||
leaveConfirmation: !props.entity || props.entity.permissions.includes('edit'),
|
||||
onChange: {
|
||||
|
@ -128,17 +117,11 @@ export default class CUD extends Component {
|
|||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
createFromChannel: PropTypes.object,
|
||||
createFromCampaign: PropTypes.object,
|
||||
crateFromCampaign: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
||||
getNextListEntryId() {
|
||||
const id = this.nextListEntryId;
|
||||
this.nextListEntryId += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
|
@ -156,10 +139,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
if (key && (match = key.match(/^(lists_[0-9]+_)list$/))) {
|
||||
const prefix = match[1];
|
||||
mutStateData.setIn([prefix + 'segment', 'value'], null);
|
||||
}
|
||||
this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||
|
@ -216,19 +196,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of data.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.getFormValuesMutator(data);
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(data.send_configuration);
|
||||
|
@ -275,27 +243,10 @@ export default class CUD extends Component {
|
|||
delete data[overridable + '_overriden'];
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lstUid of data.lists) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
const useSegmentation = data[prefix + 'useSegmentation'];
|
||||
|
||||
lsts.push({
|
||||
list: data[prefix + 'list'],
|
||||
segment: useSegmentation ? data[prefix + 'segment'] : null
|
||||
});
|
||||
}
|
||||
data.lists = lsts;
|
||||
|
||||
for (const key in data) {
|
||||
if (key.startsWith('data_') || key.startsWith('lists_')) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
this.listsSelectorHelper.submitFormValuesMutator(data);
|
||||
|
||||
return filterData(data, [
|
||||
'name', 'description', 'segment', 'namespace', 'send_configuration',
|
||||
'name', 'description', 'channel', 'namespace', 'send_configuration',
|
||||
'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
|
||||
'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
|
||||
'type', 'source', 'parent', 'lists'
|
||||
|
@ -314,10 +265,33 @@ export default class CUD extends Component {
|
|||
|
||||
const data = {};
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
|
||||
|
||||
if (this.props.createFromChannel) {
|
||||
const channel = this.props.createFromChannel;
|
||||
|
||||
data.channel = channel.id;
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (channel[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
|
@ -328,19 +302,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of channel.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.populateFrom(data, channel.lists);
|
||||
|
||||
data.type = CampaignType.REGULAR;
|
||||
|
||||
|
@ -366,23 +328,27 @@ export default class CUD extends Component {
|
|||
|
||||
if (channel.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data_sourceTemplate = channel.sourceTemplate;
|
||||
}
|
||||
|
||||
if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
channel.data_sourceCampaign = channel.data.sourceCampaign;
|
||||
}
|
||||
} else if (channel.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data_sourceCampaign = channel.data.sourceCampaign;
|
||||
|
||||
if (channel.source === CampaignSource.CUSTOM) {
|
||||
} else if (channel.source === CampaignSource.CUSTOM) {
|
||||
data.data_sourceCustom_type = channel.data.sourceCustom.type;
|
||||
data.data_sourceCustom_tag_language = channel.data.sourceCustom.tag_language;
|
||||
data.data_sourceCustom_data = channel.data.sourceCustom.data;
|
||||
|
||||
this.templateTypes[channel.data.sourceCustom.type].afterLoad(data);
|
||||
|
||||
} else if (channel.source === CampaignSource.URL) {
|
||||
data.data_sourceUrl = channel.data.sourceUrl
|
||||
}
|
||||
|
||||
|
||||
} else if (this.props.createFromCampaign) {
|
||||
const sourceCampaign = this.props.createFromCampaign;
|
||||
|
||||
data.channel = sourceCampaign.channel;
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (sourceCampaign[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
|
@ -393,19 +359,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of sourceCampaign.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
this.listsSelectorHelper.populateFrom(data, sourceCampaign.lists);
|
||||
|
||||
data.type = sourceCampaign.type;
|
||||
|
||||
|
@ -425,28 +379,6 @@ export default class CUD extends Component {
|
|||
|
||||
data.unsubscribe_url = sourceCampaign.unsubscribe_url;
|
||||
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
|
||||
if (sourceCampaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || sourceCampaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || sourceCampaign.source === CampaignSource.CUSTOM) {
|
||||
data.source = CampaignSource.CUSTOM_FROM_CAMPAIGN;
|
||||
data.data_sourceCampaign = sourceCampaign.id;
|
||||
|
@ -466,18 +398,14 @@ export default class CUD extends Component {
|
|||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
data.channel = null;
|
||||
|
||||
data.type = this.props.type;
|
||||
|
||||
data.name = '';
|
||||
data.description = '';
|
||||
|
||||
const lstUid = this.getNextListEntryId();
|
||||
const lstPrefix = 'lists_' + lstUid + '_';
|
||||
|
||||
data[lstPrefix + 'list'] = null;
|
||||
data[lstPrefix + 'segment'] = null;
|
||||
data[lstPrefix + 'useSegmentation'] = false;
|
||||
data.lists = [lstUid];
|
||||
this.listsSelectorHelper.populateFrom(data, [{list: null, segment: null}]);
|
||||
|
||||
data.send_configuration = null;
|
||||
data.namespace = getDefaultNamespace(this.props.permissions);
|
||||
|
@ -490,27 +418,6 @@ export default class CUD extends Component {
|
|||
data.unsubscribe_url = '';
|
||||
|
||||
data.source = CampaignSource.CUSTOM;
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
data.data_sourceCustom_html = '';
|
||||
data.data_sourceCustom_text = '';
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data.data_feedUrl = '';
|
||||
}
|
||||
|
||||
this.populateFormValues(data);
|
||||
|
@ -583,17 +490,7 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
for (const lstUid of state.getIn(['lists', 'value'])) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
if (!state.getIn([prefix + 'list', 'value'])) {
|
||||
state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
|
||||
}
|
||||
|
||||
if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
|
||||
state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
|
||||
}
|
||||
}
|
||||
this.listsSelectorHelper.localValidateFormValues(state)
|
||||
|
||||
validateNamespace(t, state);
|
||||
}
|
||||
|
@ -627,7 +524,12 @@ export default class CUD extends Component {
|
|||
if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
|
||||
} else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
const channelId = this.getFormValue('channel');
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
}
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/campaigns-settings/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
|
@ -642,7 +544,12 @@ export default class CUD extends Component {
|
|||
if (afterSubmitAction === CUD.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${submitResult}/status`, 'success', t('campaignCreated'));
|
||||
} else if (afterSubmitAction === CUD.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
|
||||
const channelId = this.getFormValue('channel');
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignCreated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/campaigns`, 'success', t('campaignCreated'));
|
||||
}
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${submitResult}/edit`, 'success', t('campaignCreated'));
|
||||
}
|
||||
|
@ -654,46 +561,6 @@ export default class CUD extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onAddListEntry(orderBeforeIdx) {
|
||||
this.updateForm(mutState => {
|
||||
const lsts = mutState.getIn(['lists', 'value']);
|
||||
let paramId = 0;
|
||||
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
mutState.setIn([prefix + 'list', 'value'], null);
|
||||
mutState.setIn([prefix + 'segment', 'value'], null);
|
||||
mutState.setIn([prefix + 'useSegmentation', 'value'], false);
|
||||
|
||||
mutState.setIn(['lists', 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveListEntry(lstUid) {
|
||||
this.updateForm(mutState => {
|
||||
const lsts = this.getFormValue('lists');
|
||||
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
mutState.delete(prefix + 'list');
|
||||
mutState.delete(prefix + 'segment');
|
||||
mutState.delete(prefix + 'useSegmentation');
|
||||
|
||||
mutState.setIn(['lists', 'value'], lsts.filter(val => val !== lstUid));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(orderIdx) {
|
||||
const lsts = this.getFormValue('lists');
|
||||
this.updateFormValue('lists', [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(orderIdx) {
|
||||
const lsts = this.getFormValue('lists');
|
||||
this.updateFormValue('lists', [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
@ -710,90 +577,13 @@ export default class CUD extends Component {
|
|||
extraSettings = <InputField id="data_feedUrl" label={t('rssFeedUrl')}/>
|
||||
}
|
||||
|
||||
const listsColumns = [
|
||||
const channelsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('subscribers') },
|
||||
{ data: 4, title: t('description') },
|
||||
{ data: 5, title: t('namespace') }
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('namespace') }
|
||||
];
|
||||
|
||||
const segmentsColumns = [
|
||||
{ data: 1, title: t('name') }
|
||||
];
|
||||
|
||||
const lstsEditEntries = [];
|
||||
const lsts = this.getFormValue('lists') || [];
|
||||
let lstOrderIdx = 0;
|
||||
for (const lstUid of lsts) {
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
const lstOrderIdxClosure = lstOrderIdx;
|
||||
|
||||
const selectedList = this.getFormValue(prefix + 'list');
|
||||
|
||||
lstsEditEntries.push(
|
||||
<div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
|
||||
<div className={campaignsStyles.entryButtons}>
|
||||
{lsts.length > 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="trash-alt"
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveListEntry(lstUid)}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('insertNewEntryBeforeThisOne')}
|
||||
onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
|
||||
/>
|
||||
{lstOrderIdx > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('moveUp')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
{lstOrderIdx < lsts.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('moveDown')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={campaignsStyles.entryContent}>
|
||||
<TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
||||
<div>
|
||||
<CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
|
||||
{selectedList && this.getFormValue(prefix + 'useSegmentation') &&
|
||||
<TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
lstOrderIdx += 1;
|
||||
}
|
||||
|
||||
const lstsEdit =
|
||||
<Fieldset label={t('lists')}>
|
||||
{lstsEditEntries}
|
||||
<div key="newEntry" className={campaignsStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('addList')}
|
||||
onClickAsync={() => this.onAddListEntry(lsts.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>;
|
||||
|
||||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
|
@ -938,13 +728,15 @@ export default class CUD extends Component {
|
|||
|
||||
<TextArea id="description" label={t('description')}/>
|
||||
|
||||
<TableSelect id="channel" label={t('Channel')} withHeader withClear dropdown dataUrl='rest/channels-with-create-campaign-permission-table' columns={channelsColumns} selectionLabelIndex={1} />
|
||||
|
||||
{extraSettings}
|
||||
|
||||
<NamespaceSelect/>
|
||||
|
||||
<hr/>
|
||||
|
||||
{lstsEdit}
|
||||
{this.listsSelectorHelper.render()}
|
||||
|
||||
<hr/>
|
||||
|
||||
|
|
|
@ -171,7 +171,12 @@ export default class CustomContent extends Component {
|
|||
if (afterSubmitAction === CustomContent.AfterSubmitAction.STATUS) {
|
||||
this.navigateToWithFlashMessage(`/campaigns/${this.props.entity.id}/status`, 'success', t('campaignUpdated'));
|
||||
} else if (afterSubmitAction === CustomContent.AfterSubmitAction.LEAVE) {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
const channelId = this.props.entity.channel;
|
||||
if (channelId) {
|
||||
this.navigateToWithFlashMessage(`/channels/${channelId}/campaigns`, 'success', t('campaignUpdated'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage('/campaigns', 'success', t('campaignUpdated'));
|
||||
}
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/campaigns-content/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
|
|
|
@ -44,7 +44,7 @@ export default class List extends Component {
|
|||
|
||||
const channel = this.props.channel;
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createCampaign;
|
||||
const createPermitted = permissions.createCampaign && (!channel || channel.permissions.includes('createCampaign'));
|
||||
|
||||
const columns = [];
|
||||
columns.push({
|
||||
|
@ -184,7 +184,11 @@ export default class List extends Component {
|
|||
|
||||
<Title>{t('campaigns')}</Title>
|
||||
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
|
||||
{channel ?
|
||||
<Table ref={node => this.table = node} withHeader dataUrl={`rest/campaigns-by-channel-table/${channel.id}`} columns={columns} order={[5, 'desc']} />
|
||||
:
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
'use strict';
|
||||
|
||||
import {CampaignStatus, CampaignType} from "../../../shared/campaigns";
|
||||
import campaignsStyles from "./styles.scss";
|
||||
import {Button} from "../lib/bootstrap-components";
|
||||
import {CheckBox, Fieldset, TableSelect} from "../lib/form";
|
||||
import React from "react";
|
||||
|
||||
export function getCampaignLabels(t) {
|
||||
|
||||
|
@ -30,3 +34,251 @@ export function getCampaignLabels(t) {
|
|||
}
|
||||
|
||||
|
||||
export class ListsSelectorHelper {
|
||||
constructor(owner, t, id, allowEmpty = false) {
|
||||
this.owner = owner;
|
||||
this.t = t;
|
||||
this.id = id;
|
||||
this.nextEntryId = 0;
|
||||
this.allowEmpty = allowEmpty;
|
||||
|
||||
this.keyRegex = new RegExp(`^(${id}_[0-9]+_)list$`);
|
||||
}
|
||||
|
||||
getNextEntryId() {
|
||||
const id = this.nextEntryId;
|
||||
this.nextEntryId += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
getPrefix(lstUid) {
|
||||
return this.id + '_' + lstUid + '_';
|
||||
}
|
||||
|
||||
onAddListEntry(orderBeforeIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
owner.updateForm(mutState => {
|
||||
const lsts = mutState.getIn([id, 'value']);
|
||||
let paramId = 0;
|
||||
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
mutState.setIn([prefix + 'list', 'value'], null);
|
||||
mutState.setIn([prefix + 'segment', 'value'], null);
|
||||
mutState.setIn([prefix + 'useSegmentation', 'value'], false);
|
||||
|
||||
mutState.setIn([id, 'value'], [...lsts.slice(0, orderBeforeIdx), lstUid, ...lsts.slice(orderBeforeIdx)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveListEntry(lstUid) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
owner.updateForm(mutState => {
|
||||
const lsts = owner.getFormValue(id);
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
mutState.delete(prefix + 'list');
|
||||
mutState.delete(prefix + 'segment');
|
||||
mutState.delete(prefix + 'useSegmentation');
|
||||
|
||||
mutState.setIn([id, 'value'], lsts.filter(val => val !== lstUid));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(orderIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const lsts = owner.getFormValue(id);
|
||||
owner.updateFormValue(id, [...lsts.slice(0, orderIdx - 1), lsts[orderIdx], lsts[orderIdx - 1], ...lsts.slice(orderIdx + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(orderIdx) {
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const lsts = owner.getFormValue(id);
|
||||
owner.updateFormValue(id, [...lsts.slice(0, orderIdx), lsts[orderIdx + 1], lsts[orderIdx], ...lsts.slice(orderIdx + 2)]);
|
||||
}
|
||||
|
||||
|
||||
// Public methods
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
if (key && (match = key.match(this.keyRegex))) {
|
||||
const prefix = match[1];
|
||||
mutStateData.setIn([prefix + 'segment', 'value'], null);
|
||||
}
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of data[id]) {
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data[id] = lsts;
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lstUid of data[id]) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
const useSegmentation = data[prefix + 'useSegmentation'];
|
||||
|
||||
lsts.push({
|
||||
list: data[prefix + 'list'],
|
||||
segment: useSegmentation ? data[prefix + 'segment'] : null
|
||||
});
|
||||
}
|
||||
data[id] = lsts;
|
||||
|
||||
for (const key in data) {
|
||||
if (key.startsWith('data_') || key.startsWith(id + '_')) {
|
||||
delete data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateFrom(data, lists) {
|
||||
const id = this.id;
|
||||
|
||||
const lsts = [];
|
||||
for (const lst of lists) {
|
||||
const lstUid = this.getNextEntryId();
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data[id] = lsts;
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const id = this.id;
|
||||
const t = this.t;
|
||||
|
||||
for (const lstUid of state.getIn([id, 'value'])) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
|
||||
if (!state.getIn([prefix + 'list', 'value'])) {
|
||||
state.setIn([prefix + 'list', 'error'], t('listMustBeSelected'));
|
||||
}
|
||||
|
||||
if (state.getIn([prefix + 'useSegmentation', 'value']) && !state.getIn([prefix + 'segment', 'value'])) {
|
||||
state.setIn([prefix + 'segment', 'error'], t('segmentMustBeSelected'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.t;
|
||||
const owner = this.owner;
|
||||
const id = this.id;
|
||||
|
||||
const listsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('subscribers') },
|
||||
{ data: 4, title: t('description') },
|
||||
{ data: 5, title: t('namespace') }
|
||||
];
|
||||
|
||||
const segmentsColumns = [
|
||||
{ data: 1, title: t('name') }
|
||||
];
|
||||
|
||||
const lstsEditEntries = [];
|
||||
const lsts = owner.getFormValue(id) || [];
|
||||
let lstOrderIdx = 0;
|
||||
for (const lstUid of lsts) {
|
||||
const prefix = this.getPrefix(lstUid);
|
||||
const lstOrderIdxClosure = lstOrderIdx;
|
||||
|
||||
const selectedList = owner.getFormValue(prefix + 'list');
|
||||
|
||||
lstsEditEntries.push(
|
||||
<div key={lstUid} className={campaignsStyles.entry + ' ' + campaignsStyles.entryWithButtons}>
|
||||
<div className={campaignsStyles.entryButtons}>
|
||||
{(this.allowEmpty || lsts.length > 1) &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="trash-alt"
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveListEntry(lstUid)}
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('insertNewEntryBeforeThisOne')}
|
||||
onClickAsync={() => this.onAddListEntry(lstOrderIdxClosure)}
|
||||
/>
|
||||
{lstOrderIdx > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('moveUp')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
{lstOrderIdx < lsts.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('moveDown')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(lstOrderIdxClosure)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={campaignsStyles.entryContent}>
|
||||
<TableSelect id={prefix + 'list'} label={t('list')} withHeader dropdown dataUrl='rest/lists-table' columns={listsColumns} selectionLabelIndex={1} />
|
||||
<div>
|
||||
<CheckBox id={prefix + 'useSegmentation'} label={t('segment')} text={t('useAParticularSegment')}/>
|
||||
{selectedList && owner.getFormValue(prefix + 'useSegmentation') &&
|
||||
<TableSelect id={prefix + 'segment'} withHeader dropdown dataUrl={`rest/segments-table/${selectedList}`} columns={segmentsColumns} selectionLabelIndex={1} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
lstOrderIdx += 1;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fieldset label={t('lists')}>
|
||||
{lstsEditEntries}
|
||||
<div key="newEntry" className={campaignsStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('addList')}
|
||||
onClickAsync={() => this.onAddListEntry(lsts.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
585
client/src/channels/CUD.js
Normal file
585
client/src/channels/CUD.js
Normal file
|
@ -0,0 +1,585 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withTranslation} from '../lib/i18n';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page'
|
||||
import {
|
||||
AlignedRow,
|
||||
Button,
|
||||
ButtonRow,
|
||||
CheckBox,
|
||||
Dropdown,
|
||||
Fieldset,
|
||||
filterData,
|
||||
Form,
|
||||
FormSendMethod,
|
||||
InputField,
|
||||
StaticField,
|
||||
TableSelect,
|
||||
TextArea,
|
||||
withForm,
|
||||
withFormErrorHandlers
|
||||
} from '../lib/form';
|
||||
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
|
||||
import {getDefaultNamespace, NamespaceSelect, validateNamespace} from '../lib/namespace';
|
||||
import {DeleteModalDialog} from "../lib/modals";
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {getTagLanguages, getTemplateTypes, getTypeForm, ResourceType} from '../templates/helpers';
|
||||
import axios from '../lib/axios';
|
||||
import styles from "../lib/styles.scss";
|
||||
import campaignsStyles from "./styles.scss";
|
||||
import {getUrl} from "../lib/urls";
|
||||
import {campaignOverridables, CampaignSource, CampaignStatus} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {getCampaignLabels, ListsSelectorHelper} from "../campaigns/helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Trans} from "react-i18next";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withForm,
|
||||
withErrorHandling,
|
||||
withPageHelpers,
|
||||
requiresAuthenticatedUser
|
||||
])
|
||||
export default class CUD extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.listsSelectorHelper = new ListsSelectorHelper(this, t, 'lists', true);
|
||||
|
||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN, true);
|
||||
this.tagLanguages = getTagLanguages(props.t);
|
||||
|
||||
this.mailerTypes = getMailerTypes(props.t);
|
||||
|
||||
const { campaignTypeLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
|
||||
this.sourceLabels = {
|
||||
[CampaignSource.CUSTOM]: t('customContent'),
|
||||
[CampaignSource.CUSTOM_FROM_CAMPAIGN]: t('customContentClonedFromAnotherCampaign'),
|
||||
[CampaignSource.TEMPLATE]: t('template'),
|
||||
[CampaignSource.CUSTOM_FROM_TEMPLATE]: t('customContentClonedFromTemplate'),
|
||||
[CampaignSource.URL]: t('url')
|
||||
};
|
||||
|
||||
const sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.URL
|
||||
];
|
||||
|
||||
this.sourceOptions = [];
|
||||
for (const key of sourceLabelsOrder) {
|
||||
this.sourceOptions.push({key, label: this.sourceLabels[key]});
|
||||
}
|
||||
|
||||
this.customTemplateTypeOptions = [];
|
||||
for (const key of mailtrainConfig.editors) {
|
||||
this.customTemplateTypeOptions.push({key, label: this.templateTypes[key].typeName});
|
||||
}
|
||||
|
||||
this.customTemplateTagLanguageOptions = [];
|
||||
for (const key of mailtrainConfig.tagLanguages) {
|
||||
this.customTemplateTagLanguageOptions.push({key, label: this.tagLanguages[key].name});
|
||||
}
|
||||
|
||||
this.state = {
|
||||
sendConfiguration: null
|
||||
};
|
||||
|
||||
this.initForm({
|
||||
onChange: {
|
||||
send_configuration: ::this.onSendConfigurationChanged
|
||||
},
|
||||
onChangeBeforeValidation: ::this.onFormChangeBeforeValidation
|
||||
});
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
||||
onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue) {
|
||||
let match;
|
||||
|
||||
if (key === 'data_sourceCustom_type') {
|
||||
if (newValue) {
|
||||
this.templateTypes[newValue].afterTypeChange(mutStateData);
|
||||
}
|
||||
}
|
||||
|
||||
if (key === 'data_sourceCustom_tag_language') {
|
||||
if (newValue) {
|
||||
const currentType = this.getFormValue('data_sourceCustom_type');
|
||||
const isEdit = !!this.props.entity;
|
||||
this.templateTypes[currentType].afterTagLanguageChange(mutStateData, isEdit);
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.onFormChangeBeforeValidation(mutStateData, key, oldValue, newValue);
|
||||
}
|
||||
|
||||
onSendConfigurationChanged(newState, key, oldValue, sendConfigurationId) {
|
||||
newState.sendConfiguration = null;
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(sendConfigurationId);
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async fetchSendConfiguration(sendConfigurationId) {
|
||||
if (sendConfigurationId) {
|
||||
this.fetchSendConfigurationId = sendConfigurationId;
|
||||
|
||||
try {
|
||||
const result = await axios.get(getUrl(`rest/send-configurations-public/${sendConfigurationId}`));
|
||||
|
||||
if (sendConfigurationId === this.fetchSendConfigurationId) {
|
||||
this.setState({
|
||||
sendConfiguration: result.data
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof interoperableErrors.PermissionDeniedError) {
|
||||
this.setState({
|
||||
sendConfiguration: null
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateTemplateDefaults(data) {
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data.data_sourceCustom_type = mailtrainConfig.editors[0];
|
||||
data.data_sourceCustom_tag_language = mailtrainConfig.tagLanguages[0];
|
||||
data.data_sourceCustom_data = {};
|
||||
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data.data_sourceUrl = '';
|
||||
}
|
||||
|
||||
getFormValuesMutator(data) {
|
||||
this.populateTemplateDefaults(data);
|
||||
|
||||
if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data_sourceTemplate = data.data.sourceTemplate;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data_sourceCampaign = data.data.sourceCampaign;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM) {
|
||||
data.data_sourceCustom_type = data.data.sourceCustom.type;
|
||||
data.data_sourceCustom_tag_language = data.data.sourceCustom.tag_language;
|
||||
data.data_sourceCustom_data = data.data.sourceCustom.data;
|
||||
|
||||
this.templateTypes[data.data.sourceCustom.type].afterLoad(data);
|
||||
|
||||
} else if (data.source === CampaignSource.URL) {
|
||||
data.data_sourceUrl = data.data.sourceUrl
|
||||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (data[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
} else {
|
||||
data[overridable + '_overriden'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.getFormValuesMutator(data);
|
||||
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(data.send_configuration);
|
||||
}
|
||||
|
||||
submitFormValuesMutator(data) {
|
||||
const isEdit = !!this.props.entity;
|
||||
|
||||
data.source = Number.parseInt(data.source);
|
||||
|
||||
data.data = {};
|
||||
if (data.source === CampaignSource.TEMPLATE || data.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
data.data.sourceTemplate = data.data_sourceTemplate;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
data.data.sourceCampaign = data.data_sourceCampaign;
|
||||
|
||||
} else if (data.source === CampaignSource.CUSTOM) {
|
||||
this.templateTypes[data.data_sourceCustom_type].beforeSave(data);
|
||||
|
||||
data.data.sourceCustom = {
|
||||
type: data.data_sourceCustom_type,
|
||||
tag_language: data.data_sourceCustom_tag_language,
|
||||
data: data.data_sourceCustom_data,
|
||||
}
|
||||
|
||||
} else if (data.source === CampaignSource.URL) {
|
||||
data.data.sourceUrl = data.data_sourceUrl;
|
||||
}
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (!data[overridable + '_overriden']) {
|
||||
data[overridable + '_override'] = null;
|
||||
}
|
||||
delete data[overridable + '_overriden'];
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.submitFormValuesMutator(data);
|
||||
|
||||
return filterData(data, [
|
||||
'name', 'description', 'namespace', 'cpg_name', 'cpg_description', 'send_configuration',
|
||||
'subject', 'from_name_override', 'from_email_override', 'reply_to_override',
|
||||
'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url',
|
||||
'source', 'lists'
|
||||
]);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.entity) {
|
||||
this.getFormValuesFromEntity(this.props.entity);
|
||||
|
||||
} else {
|
||||
|
||||
const data = {};
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
data.type = this.props.type;
|
||||
|
||||
data.name = '';
|
||||
data.description = '';
|
||||
|
||||
data.cpg_name = '';
|
||||
data.cpg_description = '';
|
||||
|
||||
this.listsSelectorHelper.populateFrom(data, []);
|
||||
|
||||
data.send_configuration = null;
|
||||
data.namespace = getDefaultNamespace(this.props.permissions);
|
||||
|
||||
data.subject = '';
|
||||
|
||||
data.click_tracking_disabled = false;
|
||||
data.open_tracking_disabled = false;
|
||||
|
||||
data.unsubscribe_url = '';
|
||||
|
||||
data.source = CampaignSource.CUSTOM;
|
||||
|
||||
this.populateTemplateDefaults(data);
|
||||
|
||||
this.populateFormValues(data);
|
||||
}
|
||||
}
|
||||
|
||||
localValidateFormValues(state) {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
|
||||
for (const key of state.keys()) {
|
||||
state.setIn([key, 'error'], null);
|
||||
}
|
||||
|
||||
if (!state.getIn(['name', 'value'])) {
|
||||
state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
|
||||
}
|
||||
|
||||
const sourceTypeKey = Number.parseInt(state.getIn(['source', 'value']));
|
||||
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
if (!state.getIn(['data_sourceTemplate', 'value'])) {
|
||||
state.setIn(['data_sourceTemplate', 'error'], t('templateMustBeSelected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
if (!state.getIn(['data_sourceCampaign', 'value'])) {
|
||||
state.setIn(['data_sourceCampaign', 'error'], t('campaignMustBeSelected'));
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']);
|
||||
if (!customTemplateTypeKey) {
|
||||
state.setIn(['data_sourceCustom_type', 'error'], t('typeMustBeSelected'));
|
||||
}
|
||||
|
||||
if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) {
|
||||
state.setIn(['data_sourceCustom_tag_language', 'error'], t('tagLanguageMustBeSelected'));
|
||||
}
|
||||
|
||||
if (customTemplateTypeKey) {
|
||||
this.templateTypes[customTemplateTypeKey].validate(state);
|
||||
}
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.URL) {
|
||||
if (!state.getIn(['data_sourceUrl', 'value'])) {
|
||||
state.setIn(['data_sourceUrl', 'error'], t('urlMustNotBeEmpty'));
|
||||
}
|
||||
}
|
||||
|
||||
this.listsSelectorHelper.localValidateFormValues(state)
|
||||
|
||||
validateNamespace(t, state);
|
||||
}
|
||||
|
||||
async save() {
|
||||
await this.submitHandler();
|
||||
}
|
||||
|
||||
@withFormErrorHandlers
|
||||
async submitHandler(submitAndLeave) {
|
||||
const t = this.props.t;
|
||||
|
||||
let sendMethod, url;
|
||||
if (this.props.entity) {
|
||||
sendMethod = FormSendMethod.PUT;
|
||||
url = `rest/channels/${this.props.entity.id}`;
|
||||
} else {
|
||||
sendMethod = FormSendMethod.POST;
|
||||
url = 'rest/channels'
|
||||
}
|
||||
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('info', t('saving'));
|
||||
|
||||
const submitResult = await this.validateAndSendFormValuesToURL(sendMethod, url);
|
||||
|
||||
if (submitResult) {
|
||||
if (this.props.entity) {
|
||||
if (submitAndLeave) {
|
||||
this.navigateToWithFlashMessage('/channels', 'success', t('Channel updated'));
|
||||
} else {
|
||||
await this.getFormValuesFromURL(`rest/channels/${this.props.entity.id}`);
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('success', t('Channel updated'));
|
||||
}
|
||||
} else {
|
||||
if (submitAndLeave) {
|
||||
this.navigateToWithFlashMessage('/channels', 'success', t('Channel created'));
|
||||
} else {
|
||||
this.navigateToWithFlashMessage(`/channels/${submitResult}/edit`, 'success', t('Channel created'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.enableForm();
|
||||
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
const isEdit = !!this.props.entity;
|
||||
const canModify = !isEdit || this.props.entity.permissions.includes('edit');
|
||||
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
|
||||
|
||||
const sourceTypeKey = Number.parseInt(this.getFormValue('source'));
|
||||
const campaignTypeKey = this.getFormValue('type');
|
||||
|
||||
const sendConfigurationsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('type'), render: data => this.mailerTypes[data].typeName },
|
||||
{ data: 6, title: t('namespace') }
|
||||
];
|
||||
|
||||
let sendSettings;
|
||||
if (this.getFormValue('send_configuration')) {
|
||||
if (this.state.sendConfiguration) {
|
||||
sendSettings = [];
|
||||
|
||||
const addOverridable = (id, label) => {
|
||||
if(this.state.sendConfiguration[id + '_overridable']){
|
||||
if (this.getFormValue(id + '_overriden')) {
|
||||
sendSettings.push(<InputField label={label} key={id + '_override'} id={id + '_override'}/>);
|
||||
} else {
|
||||
sendSettings.push(
|
||||
<StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
|
||||
{this.state.sendConfiguration[id]}
|
||||
</StaticField>
|
||||
);
|
||||
}
|
||||
sendSettings.push(<CheckBox key={id + '_overriden'} id={id + '_overriden'} text={t('override')} className={campaignsStyles.overrideCheckbox}/>);
|
||||
}
|
||||
else{
|
||||
sendSettings.push(
|
||||
<StaticField key={id + '_original'} label={label} id={id + '_original'} className={styles.formDisabled}>
|
||||
{this.state.sendConfiguration[id]}
|
||||
</StaticField>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
addOverridable('from_name', t('fromName'));
|
||||
addOverridable('from_email', t('fromEmailAddress'));
|
||||
addOverridable('reply_to', t('replytoEmailAddress'));
|
||||
} else {
|
||||
sendSettings = <AlignedRow>{t('loadingSendConfiguration')}</AlignedRow>
|
||||
}
|
||||
} else {
|
||||
sendSettings = null;
|
||||
}
|
||||
|
||||
let templateEdit = null;
|
||||
if (sourceTypeKey === CampaignSource.TEMPLATE || (!isEdit && sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE)) {
|
||||
const templatesColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('description') },
|
||||
{ data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
|
||||
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 6, title: t('namespace') },
|
||||
];
|
||||
|
||||
let help = null;
|
||||
if (sourceTypeKey === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
help = t('selectingATemplateCreatesACampaign');
|
||||
}
|
||||
|
||||
// The "key" property here and in the TableSelect below is to tell React that these tables are different and should be rendered by different instances. Otherwise, React will use
|
||||
// only one instance, which fails because Table does not handle updates in "columns" property
|
||||
templateEdit = <TableSelect key="templateSelect" id="data_sourceTemplate" label={t('template')} withHeader dropdown dataUrl='rest/templates-table' columns={templatesColumns} selectionLabelIndex={1} help={help}/>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM_FROM_CAMPAIGN) {
|
||||
const campaignsColumns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('type'), render: data => this.campaignTypeLabels[data] },
|
||||
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 6, title: t('namespace') }
|
||||
];
|
||||
|
||||
templateEdit = <TableSelect key="campaignSelect" id="data_sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl='rest/campaigns-with-content-table' columns={campaignsColumns} selectionLabelIndex={1} help={t('contentOfTheSelectedCampaignWillBeCopied')}/>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.CUSTOM) {
|
||||
const customTemplateTypeKey = this.getFormValue('data_sourceCustom_type');
|
||||
|
||||
let customTemplateTypeForm = null;
|
||||
|
||||
if (customTemplateTypeKey) {
|
||||
customTemplateTypeForm = getTypeForm(this, customTemplateTypeKey, false);
|
||||
}
|
||||
|
||||
templateEdit = <div>
|
||||
<Dropdown id="data_sourceCustom_type" label={t('type')} options={this.customTemplateTypeOptions}/>
|
||||
<Dropdown id="data_sourceCustom_tag_language" label={t('tagLanguage')} options={this.customTemplateTagLanguageOptions}/>
|
||||
|
||||
{customTemplateTypeForm}
|
||||
</div>;
|
||||
|
||||
} else if (sourceTypeKey === CampaignSource.URL) {
|
||||
templateEdit = <InputField id="data_sourceUrl" label={t('renderUrl')} help={t('ifAMessageIsSentThenThisUrlWillBePosTed')}/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{canDelete &&
|
||||
<DeleteModalDialog
|
||||
stateOwner={this}
|
||||
visible={this.props.action === 'delete'}
|
||||
deleteUrl={`rest/channels/${this.props.entity.id}`}
|
||||
backUrl={`/channels/${this.props.entity.id}/edit`}
|
||||
successUrl="/channels"
|
||||
deletingMsg={t('Deleting channel ...')}
|
||||
deletedMsg={t('Channel deleted')}/>
|
||||
}
|
||||
|
||||
<Title>{isEdit ? t('Edit Channel') : t('Create Channel')}</Title>
|
||||
|
||||
{!canModify &&
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<Trans><b>Warning!</b> You do not have necessary permissions to edit this channel. Any changes that you perform here will be lost.</Trans>
|
||||
</div>
|
||||
}
|
||||
|
||||
{isEdit && this.props.entity.status === CampaignStatus.SENDING &&
|
||||
<div className={`alert alert-info`} role="alert">
|
||||
{t('formCannotBeEditedBecauseTheCampaignIs')}
|
||||
</div>
|
||||
}
|
||||
|
||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||
<InputField id="name" label={t('name')}/>
|
||||
|
||||
{isEdit &&
|
||||
<StaticField id="cid" className={styles.formDisabled} label={t('id')}>
|
||||
{this.getFormValue('cid')}
|
||||
</StaticField>
|
||||
}
|
||||
|
||||
<TextArea id="description" label={t('description')}/>
|
||||
|
||||
<NamespaceSelect/>
|
||||
|
||||
<hr/>
|
||||
<Fieldset label={t('Campaign defaults')}>
|
||||
<InputField id="cpg_name" label={t('Campaign name')}/>
|
||||
<TextArea id="cpg_description" label={t('Campaign description')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
|
||||
{this.listsSelectorHelper.render()}
|
||||
|
||||
<hr/>
|
||||
|
||||
<Fieldset label={t('sendSettings')}>
|
||||
|
||||
<TableSelect id="send_configuration" label={t('sendConfiguration')} withHeader withClear dropdown dataUrl='rest/send-configurations-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
|
||||
|
||||
{sendSettings}
|
||||
|
||||
<InputField label={t('subjectLine')} key="subject" id="subject"/>
|
||||
|
||||
<InputField id="unsubscribe_url" label={t('customUnsubscribeUrl')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
|
||||
<Fieldset label={t('tracking')}>
|
||||
<CheckBox id="open_tracking_disabled" text={t('disableOpenedTracking')}/>
|
||||
<CheckBox id="click_tracking_disabled" text={t('disableClickedTracking')}/>
|
||||
</Fieldset>
|
||||
|
||||
<hr/>
|
||||
<Fieldset label={t('template')}>
|
||||
<Dropdown id="source" label={t('contentSource')} options={this.sourceOptions}/>
|
||||
</Fieldset>
|
||||
|
||||
{templateEdit}
|
||||
|
||||
<ButtonRow>
|
||||
{canModify &&
|
||||
<>
|
||||
<Button type="submit" className="btn-primary" icon="check" label={t('save')}/>
|
||||
{isEdit && <Button type="submit" className="btn-primary" icon="check" label={t('saveAndLeave')} onClickAsync={async () => await this.submitHandler(true)}/>}
|
||||
</>
|
||||
}
|
||||
{canDelete && <LinkButton className="btn-danger" icon="trash-alt" label={t('delete')} to={`/channels/${this.props.entity.id}/delete`}/> }
|
||||
</ButtonRow>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ export default class List extends Component {
|
|||
data: 1,
|
||||
title: t('name'),
|
||||
actions: data => {
|
||||
const perms = data[10];
|
||||
const perms = data[5];
|
||||
if (perms.includes('view')) {
|
||||
return [{label: data[1], link: `/channels/${data[0]}/campaigns`}];
|
||||
} else {
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import CampaignsList from '../campaigns/List';
|
||||
import CampaignsCUD from '../campaigns/CUD';
|
||||
import ChannelsList from './List';
|
||||
//import ChannelsCUD from './CUD';
|
||||
import ChannelsCUD from './CUD';
|
||||
import Share from '../shares/Share';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
@ -29,7 +29,7 @@ function getMenus(t) {
|
|||
panelRender: props => <ChannelsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':channelId([0-9]+)': {
|
||||
title: resolved => t('channelName', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
|
||||
title: resolved => t('Channel "{{name}}"', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
|
||||
resolve: {
|
||||
channel: params => `rest/channels/${params.channelId}`
|
||||
},
|
||||
|
@ -39,16 +39,14 @@ function getMenus(t) {
|
|||
title: t('Campaigns'),
|
||||
link: params => `/channels/${params.channelId}/campaigns`,
|
||||
visible: resolved => resolved.channel.permissions.includes('view'),
|
||||
panelRender: props => <CampaignsList channel={props.resolved.channel} />
|
||||
panelRender: props => <CampaignsList channel={props.resolved.channel} permissions={props.permissions} />
|
||||
},
|
||||
/*
|
||||
':action(edit|delete)': {
|
||||
title: t('edit'),
|
||||
link: params => `/channels/${params.channelId}/edit`,
|
||||
visible: resolved => resolved.channel.permissions.includes('view') || resolved.channel.permissions.includes('edit'),
|
||||
panelRender: props => <ChannelsCUD action={props.match.params.action} entity={props.resolved.channel} permissions={props.permissions} />
|
||||
},
|
||||
*/
|
||||
share: {
|
||||
title: t('share'),
|
||||
link: params => `/channels/${params.channelId}/share`,
|
||||
|
@ -61,7 +59,7 @@ function getMenus(t) {
|
|||
title: t('createCampaign'),
|
||||
link: params => `/channels/${params.channelId}/create`,
|
||||
visible: resolved => resolved.channel.permissions.includes('createCampaign'),
|
||||
panelRender: props => <CampaignsCUD action="create" channel={props.resolved.channel} permissions={props.permissions} />
|
||||
panelRender: props => <CampaignsCUD action="create" createFromChannel={props.resolved.channel} permissions={props.permissions} />,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -34,8 +34,6 @@ import styles from "./styles.scss";
|
|||
import moment from "moment";
|
||||
import {getUrl} from "./urls";
|
||||
import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
|
||||
import cudStyles from "../../../mvis/ivis-core/client/src/settings/jobs/CUD.scss";
|
||||
import {campaignOverridables, CampaignSource, CampaignType} from "../../../shared/campaigns";
|
||||
|
||||
|
||||
const FormState = {
|
||||
|
@ -931,207 +929,6 @@ class ButtonRow extends Component {
|
|||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withFormStateOwner
|
||||
], null, ['submitFormValuesMutator', 'getFormValueIdForPicker'])
|
||||
class ListCreator extends Component {
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
className: PropTypes.string,
|
||||
withOrder: PropTypes.bool,
|
||||
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._nextEntryId = 0;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const values = this.props.initValues;
|
||||
if (values && values.length > 0) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
let entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
if (!entryIds) {
|
||||
entryIds = [];
|
||||
}
|
||||
|
||||
for (const entryValue of values) {
|
||||
const entryId = this.getNextEntryId();
|
||||
mutState.setIn([this.getFormValueId(entryId), 'value'], entryValue);
|
||||
entryIds.push(entryId);
|
||||
}
|
||||
|
||||
mutState.setIn([this.props.id, 'value'], entryIds);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static getFormValuesMutator(pickerId, data) {
|
||||
const elems = [];
|
||||
for (const elem of data[pickerId]) {
|
||||
const uid = this.getNextEntryId();
|
||||
|
||||
const prefix = this.getFormValueId(uid);
|
||||
|
||||
data[prefix + 'list'] = elem.list;
|
||||
data[prefix + 'segment'] = elem.segment;
|
||||
data[prefix + 'useSegmentation'] = !!elem.segment;
|
||||
|
||||
elems.push(uid);
|
||||
}
|
||||
data[pickerId] = elems;
|
||||
}
|
||||
|
||||
|
||||
static submitFormValuesMutator(pickerId, data) {
|
||||
const entryValues = [];
|
||||
const entryIds = data[pickerId];
|
||||
|
||||
if (!entryIds) {
|
||||
return entryValues;
|
||||
}
|
||||
|
||||
for (const entryId of entryIds) {
|
||||
const entryFormId = ListCreator.getFormValueIdForPicker(pickerId, entryId);
|
||||
const value = data[entryFormId];
|
||||
entryValues.push(value);
|
||||
delete data[entryFormId]
|
||||
}
|
||||
|
||||
data[pickerId] = entryValues;
|
||||
}
|
||||
|
||||
static getFormValueIdForPicker(pickerId, entryId) {
|
||||
return `${pickerId}_${entryId}`;
|
||||
}
|
||||
|
||||
getFormValueId(entryId) {
|
||||
return ListCreator.getFormValueIdForPicker(this.props.id, entryId);
|
||||
}
|
||||
|
||||
getNextEntryId() {
|
||||
return this._nextEntryId++;
|
||||
}
|
||||
|
||||
onAddListEntry(positionBefore) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
let entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
if (!entryIds) {
|
||||
entryIds = [];
|
||||
}
|
||||
|
||||
if (positionBefore == null) {
|
||||
positionBefore = entryIds.length;
|
||||
}
|
||||
|
||||
const entryId = this.getNextEntryId();
|
||||
|
||||
mutState.setIn([this.getFormValueId(entryId), 'value'], null);
|
||||
mutState.setIn([this.props.id, 'value'], [...entryIds.slice(0, positionBefore), entryId, ...entryIds.slice(positionBefore)]);
|
||||
});
|
||||
}
|
||||
|
||||
onRemoveSetEntry(entryId) {
|
||||
this.getFormStateOwner().updateForm(mutState => {
|
||||
const entryIds = mutState.getIn([this.props.id, 'value']);
|
||||
|
||||
mutState.delete(this.getFormValueId(entryId));
|
||||
|
||||
mutState.setIn([this.props.id, 'value'], entryIds.filter(id => id !== entryId));
|
||||
});
|
||||
}
|
||||
|
||||
onListEntryMoveUp(position) {
|
||||
const owner = this.getFormStateOwner();
|
||||
const entryIds = owner.getFormValue(this.props.id);
|
||||
owner.updateFormValue(this.props.id, [...entryIds.slice(0, position - 1), entryIds[position], entryIds[position - 1], ...entryIds.slice(position + 1)]);
|
||||
}
|
||||
|
||||
onListEntryMoveDown(position) {
|
||||
const owner = this.getFormStateOwner();
|
||||
const entryIds = owner.getFormValue(this.props.id);
|
||||
owner.updateFormValue(this.props.id, [...entryIds.slice(0, position), entryIds[position + 1], entryIds[position], ...entryIds.slice(position + 2)]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const owner = this.getFormStateOwner();
|
||||
const id = props.id;
|
||||
const t = props.t;
|
||||
const withOrder = props.withOrder;
|
||||
const entries = [];
|
||||
const entryIds = owner.getFormValue(id) || [];
|
||||
|
||||
const entryButtonsStyles = withOrder ? cudStyles.entryButtonsWithOrder : cudStyles.entryButtons;
|
||||
for (let pos = 0; pos < entryIds.length; pos++) {
|
||||
const entryId = entryIds[pos];
|
||||
const elementId = this.getFormValueId(entryId);
|
||||
entries.push(
|
||||
<div key={entryId}
|
||||
className={cudStyles.entry + (withOrder ? ' ' + cudStyles.withOrder : '') + ' ' + cudStyles.entryWithButtons}>
|
||||
<div className={entryButtonsStyles}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon={`trash-alt ${withOrder ? "" : "fa-2x"}`}
|
||||
title={t('remove')}
|
||||
onClickAsync={() => this.onRemoveSetEntry(entryId)}
|
||||
/>
|
||||
{withOrder &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
title={t('Insert new entry before this one')}
|
||||
onClickAsync={() => this.onAddListEntry(pos)}
|
||||
/>
|
||||
}
|
||||
{withOrder && pos > 0 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-up"
|
||||
title={t('Move up')}
|
||||
onClickAsync={() => this.onListEntryMoveUp(pos)}
|
||||
/>
|
||||
}
|
||||
{withOrder && pos < entryIds.length - 1 &&
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="chevron-down"
|
||||
title={t('Move down')}
|
||||
onClickAsync={() => this.onListEntryMoveDown(pos)}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div className={cudStyles.entryContent}>
|
||||
{React.cloneElement(this.props.entryElement, {id: elementId})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fieldset id={id} className={props.classname} help={props.help} flat={props.flat} label={props.label}>
|
||||
{entries}
|
||||
<div key="newEntry" className={cudStyles.newEntry}>
|
||||
<Button
|
||||
className="btn-secondary"
|
||||
icon="plus"
|
||||
label={t('Add entry')}
|
||||
onClickAsync={() => this.onAddListEntry(entryIds.length)}
|
||||
/>
|
||||
</div>
|
||||
</Fieldset>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withFormStateOwner
|
||||
])
|
||||
|
@ -1200,6 +997,7 @@ class TableSelect extends Component {
|
|||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
format: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
withClear: PropTypes.bool,
|
||||
|
||||
pageLength: PropTypes.number
|
||||
}
|
||||
|
@ -1245,6 +1043,15 @@ class TableSelect extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
async clear() {
|
||||
const owner = this.getFormStateOwner();
|
||||
if (this.props.selectMode === TableSelectMode.SINGLE && !this.props.selectionAsArray) {
|
||||
owner.updateFormValue(this.props.id, null);
|
||||
} else {
|
||||
owner.updateFormValue(this.props.id, []);
|
||||
}
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.table.refresh();
|
||||
}
|
||||
|
@ -1256,6 +1063,8 @@ class TableSelect extends Component {
|
|||
const htmlId = 'form_' + id;
|
||||
const t = props.t;
|
||||
|
||||
const selection = owner.getFormValue(id);
|
||||
|
||||
if (props.dropdown) {
|
||||
const className = owner.addFormValidationClass('form-control', id);
|
||||
|
||||
|
@ -1271,6 +1080,7 @@ class TableSelect extends Component {
|
|||
{!props.disabled &&
|
||||
<div className="input-group-append">
|
||||
<Button label={t('select')} className="btn-secondary" onClickAsync={::this.toggleOpen}/>
|
||||
{props.withClear && selection && <Button icon="times" title={t('Clear')} className="btn-secondary" onClickAsync={::this.clear}/>}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
@ -1285,7 +1095,7 @@ class TableSelect extends Component {
|
|||
selectionAsArray={this.props.selectionAsArray}
|
||||
withHeader={props.withHeader}
|
||||
selectionKeyIndex={props.selectionKeyIndex}
|
||||
selection={owner.getFormValue(id)}
|
||||
selection={selection}
|
||||
onSelectionDataAsync={::this.onSelectionDataAsync}
|
||||
onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
|
@ -1305,7 +1115,7 @@ class TableSelect extends Component {
|
|||
selectionAsArray={this.props.selectionAsArray}
|
||||
withHeader={props.withHeader}
|
||||
selectionKeyIndex={props.selectionKeyIndex}
|
||||
selection={owner.getFormValue(id)}
|
||||
selection={selection}
|
||||
onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -211,7 +211,7 @@ function renderFrameWithContent(t, panelInFullScreen, showSidebar, primaryMenu,
|
|||
</div>
|
||||
|
||||
<footer key="appFooter" className="app-footer">
|
||||
<div className="text-muted">© 2018 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
|
||||
<div className="text-muted">© 2020 <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/Mailtrain-org/mailtrain">{t('sourceOnGitHub')}</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,7 +39,7 @@ export function getTagLanguages(t) {
|
|||
};
|
||||
}
|
||||
|
||||
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE) {
|
||||
export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEMPLATE, allowEmpty = false) {
|
||||
// The prefix is used to to enable use within other forms (i.e. campaign form)
|
||||
const templateTypes = {};
|
||||
|
||||
|
@ -97,7 +97,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
dataUrl={`rest/mosaico-templates-by-tag-language-table/${tagLanguageKey}`}
|
||||
columns={mosaicoTemplatesColumns}
|
||||
selectionLabelIndex={1}
|
||||
disabled={isEdit}/>
|
||||
disabled={isEdit}
|
||||
withClear={allowEmpty}
|
||||
/>
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -169,7 +171,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
},
|
||||
validate: state => {
|
||||
const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
|
||||
if (!mosaicoTemplate) {
|
||||
if (!allowEmpty && !mosaicoTemplate) {
|
||||
state.setIn([prefix + 'mosaicoTemplate', 'error'], t('mosaicoTemplateMustBeSelected'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue