- Fix for #890
- "Channels" feature - Shoutout config param rendered on the homepage - "Clone" feature for campaigns
This commit is contained in:
parent
82251d1cb9
commit
00432e6cfe
23 changed files with 1691 additions and 494 deletions
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {withTranslation} from './lib/i18n';
|
||||
import {requiresAuthenticatedUser} from './lib/page';
|
||||
import {withComponentMixins} from "./lib/decorator-helpers";
|
||||
|
@ -14,6 +15,10 @@ export default class List extends Component {
|
|||
super(props);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
configItems: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
|
@ -21,6 +26,7 @@ export default class List extends Component {
|
|||
<div>
|
||||
<h2>{t('Mailtrain 2 beta')}</h2>
|
||||
<div>{t('Build') + ' 2020-05-28-0102'}</div>
|
||||
<p>{this.props.configItems.shoutout}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -79,9 +79,20 @@ export default class CUD extends Component {
|
|||
[CampaignSource.URL]: t('url')
|
||||
};
|
||||
|
||||
const sourceLabelsOrder = [
|
||||
CampaignSource.CUSTOM, CampaignSource.CUSTOM_FROM_CAMPAIGN , CampaignSource.TEMPLATE, CampaignSource.CUSTOM_FROM_TEMPLATE, CampaignSource.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
|
||||
];
|
||||
}
|
||||
|
||||
this.sourceOptions = [];
|
||||
for (const key of sourceLabelsOrder) {
|
||||
|
@ -116,6 +127,8 @@ export default class CUD extends Component {
|
|||
static propTypes = {
|
||||
action: PropTypes.string.isRequired,
|
||||
entity: PropTypes.object,
|
||||
createFromChannel: PropTypes.object,
|
||||
createFromCampaign: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
type: PropTypes.number
|
||||
}
|
||||
|
@ -298,61 +311,209 @@ export default class CUD extends Component {
|
|||
}
|
||||
|
||||
} else {
|
||||
|
||||
const data = {};
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
const lstUid = this.getNextListEntryId();
|
||||
const lstPrefix = 'lists_' + lstUid + '_';
|
||||
|
||||
this.populateFormValues({
|
||||
...data,
|
||||
if (this.props.createFromChannel) {
|
||||
const channel = this.props.createFromChannel;
|
||||
|
||||
type: this.props.type,
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (channel[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
} else {
|
||||
data[overridable + '_override'] = channel[overridable + '_override'];
|
||||
data[overridable + '_overriden'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
name: '',
|
||||
description: '',
|
||||
const lsts = [];
|
||||
for (const lst of channel.lists) {
|
||||
const lstUid = this.getNextListEntryId();
|
||||
|
||||
[lstPrefix + 'list']: null,
|
||||
[lstPrefix + 'segment']: null,
|
||||
[lstPrefix + 'useSegmentation']: false,
|
||||
lists: [lstUid],
|
||||
const prefix = 'lists_' + lstUid + '_';
|
||||
|
||||
send_configuration: null,
|
||||
namespace: getDefaultNamespace(this.props.permissions),
|
||||
data[prefix + 'list'] = lst.list;
|
||||
data[prefix + 'segment'] = lst.segment;
|
||||
data[prefix + 'useSegmentation'] = !!lst.segment;
|
||||
|
||||
subject: '',
|
||||
lsts.push(lstUid);
|
||||
}
|
||||
data.lists = lsts;
|
||||
|
||||
click_tracking_disabled: false,
|
||||
open_tracking_disabled: false,
|
||||
data.type = CampaignType.REGULAR;
|
||||
|
||||
unsubscribe_url: '',
|
||||
data.name = channel.cpg_name;
|
||||
data.description = channel.cpg_description;
|
||||
|
||||
data.send_configuration = channel.send_configuration;
|
||||
if (channel.send_configuration) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(channel.send_configuration);
|
||||
}
|
||||
|
||||
data.namespace = channel.namespace;
|
||||
|
||||
data.subject = channel.subject;
|
||||
|
||||
data.click_tracking_disabled = channel.click_tracking_disabled;
|
||||
data.open_tracking_disabled = channel.open_tracking_disabled;
|
||||
|
||||
data.unsubscribe_url = channel.unsubscribe_url;
|
||||
|
||||
data.source = channel.source;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (this.props.createFromCampaign) {
|
||||
const sourceCampaign = this.props.createFromCampaign;
|
||||
|
||||
for (const overridable of campaignOverridables) {
|
||||
if (sourceCampaign[overridable + '_override'] === null) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
} else {
|
||||
data[overridable + '_override'] = sourceCampaign[overridable + '_override'];
|
||||
data[overridable + '_overriden'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
data.type = sourceCampaign.type;
|
||||
|
||||
data.name = sourceCampaign.name;
|
||||
data.description = sourceCampaign.description;
|
||||
|
||||
data.send_configuration = sourceCampaign.send_configuration;
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.fetchSendConfiguration(sourceCampaign.send_configuration);
|
||||
|
||||
data.namespace = sourceCampaign.namespace;
|
||||
|
||||
data.subject = sourceCampaign.subject;
|
||||
|
||||
data.click_tracking_disabled = sourceCampaign.click_tracking_disabled;
|
||||
data.open_tracking_disabled = sourceCampaign.open_tracking_disabled;
|
||||
|
||||
data.unsubscribe_url = sourceCampaign.unsubscribe_url;
|
||||
|
||||
source: CampaignSource.CUSTOM,
|
||||
|
||||
// This is for CampaignSource.TEMPLATE and CampaignSource.CUSTOM_FROM_TEMPLATE
|
||||
data_sourceTemplate: null,
|
||||
data.data_sourceTemplate = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM_FROM_CAMPAIGN
|
||||
data_sourceCampaign: null,
|
||||
data.data_sourceCampaign = null;
|
||||
|
||||
// This is for CampaignSource.CUSTOM
|
||||
data_sourceCustom_type: mailtrainConfig.editors[0],
|
||||
data_sourceCustom_tag_language: mailtrainConfig.tagLanguages[0],
|
||||
data_sourceCustom_data: {},
|
||||
data_sourceCustom_html: '',
|
||||
data_sourceCustom_text: '',
|
||||
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 = '';
|
||||
|
||||
...this.templateTypes[mailtrainConfig.editors[0]].initData(),
|
||||
Object.assign(data, this.templateTypes[mailtrainConfig.editors[0]].initData());
|
||||
|
||||
// This is for CampaignSource.URL
|
||||
data_sourceUrl: '',
|
||||
data.data_sourceUrl = '';
|
||||
|
||||
// This is for CampaignType.RSS
|
||||
data_feedUrl: ''
|
||||
});
|
||||
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;
|
||||
|
||||
} else if (sourceCampaign.source === CampaignSource.TEMPLATE) {
|
||||
data.source = CampaignSource.TEMPLATE;
|
||||
data.data_sourceTemplate = sourceCampaign.data.sourceTemplate;
|
||||
|
||||
} else if (sourceCampaign.source === CampaignSource.URL) {
|
||||
data.source = CampaignSource.URL;
|
||||
data.data_sourceUrl = sourceCampaign.data.sourceUrl;
|
||||
}
|
||||
|
||||
} else {
|
||||
for (const overridable of campaignOverridables) {
|
||||
data[overridable + '_override'] = '';
|
||||
data[overridable + '_overriden'] = false;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
123
client/src/campaigns/Clone.js
Normal file
123
client/src/campaigns/Clone.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
'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, CampaignType} from "../../../shared/campaigns";
|
||||
import moment from 'moment';
|
||||
import {getMailerTypes} from "../send-configurations/helpers";
|
||||
import {getCampaignLabels} from "./helpers";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Trans} from "react-i18next";
|
||||
import {Table} from "../lib/table";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withForm,
|
||||
withErrorHandling,
|
||||
withPageHelpers,
|
||||
requiresAuthenticatedUser
|
||||
])
|
||||
export default class Clone extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.templateTypes = getTemplateTypes(props.t, 'data_sourceCustom_', ResourceType.CAMPAIGN);
|
||||
this.tagLanguages = getTagLanguages(props.t);
|
||||
|
||||
this.mailerTypes = getMailerTypes(props.t);
|
||||
|
||||
const { campaignTypeLabels } = getCampaignLabels(t);
|
||||
this.campaignTypeLabels = campaignTypeLabels;
|
||||
|
||||
this.initForm({
|
||||
leaveConfirmation: false,
|
||||
});
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.populateFormValues({
|
||||
sourceCampaign: null
|
||||
});
|
||||
}
|
||||
|
||||
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(['sourceCampaign', 'value'])) {
|
||||
state.setIn(['sourceCampaign', 'error'], t('campaignMustBeSelected'));
|
||||
}
|
||||
}
|
||||
|
||||
@withFormErrorHandlers
|
||||
async submitHandler(afterSubmitAction) {
|
||||
const t = this.props.t;
|
||||
|
||||
const sourceCampaign = this.getFormValue('sourceCampaign');
|
||||
this.navigateTo(`/campaigns/clone/${sourceCampaign}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
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: 9, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 10, title: t('namespace') }
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>{t('Create campaign')}</Title>
|
||||
|
||||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||||
<TableSelect id="sourceCampaign" label={t('campaign')} withHeader dropdown dataUrl='rest/campaigns-table' columns={campaignsColumns} order={[4, 'desc']} selectionLabelIndex={1} help={t('Select campaign to be cloned.')}/>
|
||||
|
||||
<ButtonRow>
|
||||
<Button type="submit" className="btn-primary" icon="chevron-right" label={t('Next')}/>
|
||||
</ButtonRow>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
import React, {Component} from 'react';
|
||||
import {withTranslation} from '../lib/i18n';
|
||||
import {ButtonDropdown, Icon} from '../lib/bootstrap-components';
|
||||
import {DropdownLink, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {DropdownLink, LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import moment from 'moment';
|
||||
|
@ -35,126 +35,156 @@ export default class List extends Component {
|
|||
}
|
||||
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
permissions: PropTypes.object,
|
||||
channel: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const channel = this.props.channel;
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createCampaign;
|
||||
|
||||
const columns = [
|
||||
{ data: 1, title: t('name') },
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code>, className: styles.tblCol_id },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('type'), render: data => this.campaignTypeLabels[data] },
|
||||
{
|
||||
data: 5,
|
||||
title: t('status'),
|
||||
render: (data, display, rowData) => {
|
||||
if (data === CampaignStatus.SCHEDULED) {
|
||||
const scheduled = rowData[6];
|
||||
if (scheduled && new Date(scheduled) > new Date()) {
|
||||
return t('sendingScheduled');
|
||||
} else {
|
||||
return t('sending');
|
||||
}
|
||||
} else {
|
||||
return this.campaignStatusLabels[data];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ data: 8, title: t('created'), render: data => moment(data).fromNow() },
|
||||
{ data: 9, title: t('namespace') },
|
||||
{
|
||||
className: styles.tblCol_buttons,
|
||||
actions: data => {
|
||||
const actions = [];
|
||||
const perms = data[10];
|
||||
const campaignType = data[4];
|
||||
const status = data[5];
|
||||
const campaignSource = data[7];
|
||||
|
||||
if (perms.includes('view')) {
|
||||
actions.push({
|
||||
label: <Icon icon="envelope" title={t('status')}/>,
|
||||
link: `/campaigns/${data[0]}/status`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewStats')) {
|
||||
actions.push({
|
||||
label: <Icon icon="signal" title={t('statistics')}/>,
|
||||
link: `/campaigns/${data[0]}/statistics`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('view') || perms.includes('edit')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('edit')}/>,
|
||||
link: `/campaigns/${data[0]}/edit`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('edit') && (campaignSource === CampaignSource.CUSTOM || campaignSource === CampaignSource.CUSTOM_FROM_TEMPLATE || campaignSource === CampaignSource.CUSTOM_FROM_CAMPAIGN)) {
|
||||
actions.push({
|
||||
label: <Icon icon="align-center" title={t('content')}/>,
|
||||
link: `/campaigns/${data[0]}/content`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewFiles') && (campaignSource === CampaignSource.CUSTOM || campaignSource === CampaignSource.CUSTOM_FROM_TEMPLATE || campaignSource === CampaignSource.CUSTOM_FROM_CAMPAIGN)) {
|
||||
actions.push({
|
||||
label: <Icon icon="hdd" title={t('files')}/>,
|
||||
link: `/campaigns/${data[0]}/files`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewAttachments')) {
|
||||
actions.push({
|
||||
label: <Icon icon="paperclip" title={t('attachments')}/>,
|
||||
link: `/campaigns/${data[0]}/attachments`
|
||||
});
|
||||
}
|
||||
|
||||
if (campaignType === CampaignType.TRIGGERED && perms.includes('viewTriggers')) {
|
||||
actions.push({
|
||||
label: <Icon icon="bell" title={t('triggers')}/>,
|
||||
link: `/campaigns/${data[0]}/triggers`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('share')) {
|
||||
actions.push({
|
||||
label: <Icon icon="share" title={t('share')}/>,
|
||||
link: `/campaigns/${data[0]}/share`
|
||||
});
|
||||
}
|
||||
|
||||
tableAddDeleteButton(actions, this, perms, `rest/campaigns/${data[0]}`, data[1], t('deletingCampaign'), t('campaignDeleted'));
|
||||
|
||||
return actions;
|
||||
const columns = [];
|
||||
columns.push({
|
||||
data: 1,
|
||||
title: t('name'),
|
||||
actions: data => {
|
||||
const perms = data[10];
|
||||
if (perms.includes('view')) {
|
||||
return [{label: data[1], link: `/campaigns/${data[0]}/status`}];
|
||||
} else {
|
||||
return [{label: data[1]}];
|
||||
}
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
columns.push({ data: 2, title: t('id'), render: data => <code>{data}</code>, className: styles.tblCol_id });
|
||||
columns.push({ data: 3, title: t('description') });
|
||||
columns.push({ data: 4, title: t('type'), render: data => this.campaignTypeLabels[data] });
|
||||
|
||||
if (!channel) {
|
||||
columns.push({ data: 5, title: t('Channel') });
|
||||
}
|
||||
|
||||
columns.push({
|
||||
data: 6,
|
||||
title: t('status'),
|
||||
render: (data, display, rowData) => {
|
||||
if (data === CampaignStatus.SCHEDULED) {
|
||||
const scheduled = rowData[6];
|
||||
if (scheduled && new Date(scheduled) > new Date()) {
|
||||
return t('sendingScheduled');
|
||||
} else {
|
||||
return t('sending');
|
||||
}
|
||||
} else {
|
||||
return this.campaignStatusLabels[data];
|
||||
}
|
||||
}
|
||||
});
|
||||
columns.push({ data: 9, title: t('created'), render: data => moment(data).fromNow() });
|
||||
columns.push({ data: 10, title: t('namespace') });
|
||||
columns.push({
|
||||
className: styles.tblCol_buttons,
|
||||
actions: data => {
|
||||
const actions = [];
|
||||
const perms = data[11];
|
||||
const campaignType = data[4];
|
||||
const campaignSource = data[8];
|
||||
|
||||
if (perms.includes('view')) {
|
||||
actions.push({
|
||||
label: <Icon icon="envelope" title={t('status')}/>,
|
||||
link: `/campaigns/${data[0]}/status`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewStats')) {
|
||||
actions.push({
|
||||
label: <Icon icon="signal" title={t('statistics')}/>,
|
||||
link: `/campaigns/${data[0]}/statistics`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('view') || perms.includes('edit')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('edit')}/>,
|
||||
link: `/campaigns/${data[0]}/edit`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('edit') && (campaignSource === CampaignSource.CUSTOM || campaignSource === CampaignSource.CUSTOM_FROM_TEMPLATE || campaignSource === CampaignSource.CUSTOM_FROM_CAMPAIGN)) {
|
||||
actions.push({
|
||||
label: <Icon icon="align-center" title={t('content')}/>,
|
||||
link: `/campaigns/${data[0]}/content`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewFiles') && (campaignSource === CampaignSource.CUSTOM || campaignSource === CampaignSource.CUSTOM_FROM_TEMPLATE || campaignSource === CampaignSource.CUSTOM_FROM_CAMPAIGN)) {
|
||||
actions.push({
|
||||
label: <Icon icon="hdd" title={t('files')}/>,
|
||||
link: `/campaigns/${data[0]}/files`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('viewAttachments')) {
|
||||
actions.push({
|
||||
label: <Icon icon="paperclip" title={t('attachments')}/>,
|
||||
link: `/campaigns/${data[0]}/attachments`
|
||||
});
|
||||
}
|
||||
|
||||
if (campaignType === CampaignType.TRIGGERED && perms.includes('viewTriggers')) {
|
||||
actions.push({
|
||||
label: <Icon icon="bell" title={t('triggers')}/>,
|
||||
link: `/campaigns/${data[0]}/triggers`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('share')) {
|
||||
actions.push({
|
||||
label: <Icon icon="share" title={t('share')}/>,
|
||||
link: `/campaigns/${data[0]}/share`
|
||||
});
|
||||
}
|
||||
|
||||
tableAddDeleteButton(actions, this, perms, `rest/campaigns/${data[0]}`, data[1], t('deletingCampaign'), t('campaignDeleted'));
|
||||
|
||||
return actions;
|
||||
}
|
||||
});
|
||||
|
||||
let createButton = null;
|
||||
|
||||
if (createPermitted) {
|
||||
if (channel) {
|
||||
createButton = <LinkButton to={`/channels/${channel.id}/create`} className="btn-primary" icon="plus" label={t('createCampaign')}/>;
|
||||
} else {
|
||||
createButton = (
|
||||
<>
|
||||
<LinkButton to={`/campaigns/clone`} className="btn-primary" icon="clone" label={t('Clone Campaign')}/>
|
||||
<ButtonDropdown buttonClassName="btn-primary" menuClassName="dropdown-menu-right" icon="plus" label={t('createCampaign')}>
|
||||
<DropdownLink to="/campaigns/create-regular">{t('regular')}</DropdownLink>
|
||||
<DropdownLink to="/campaigns/create-rss">{t('rss')}</DropdownLink>
|
||||
<DropdownLink to="/campaigns/create-triggered">{t('triggered')}</DropdownLink>
|
||||
</ButtonDropdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
<Toolbar>
|
||||
{createPermitted &&
|
||||
<ButtonDropdown buttonClassName="btn-primary" menuClassName="dropdown-menu-right" label={t('createCampaign')}>
|
||||
<DropdownLink to="/campaigns/create-regular">{t('regular')}</DropdownLink>
|
||||
<DropdownLink to="/campaigns/create-rss">{t('rss')}</DropdownLink>
|
||||
<DropdownLink to="/campaigns/create-triggered">{t('triggered')}</DropdownLink>
|
||||
</ButtonDropdown>
|
||||
}
|
||||
{createButton}
|
||||
</Toolbar>
|
||||
|
||||
<Title>{t('campaigns')}</Title>
|
||||
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[5, 'desc']} />
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/campaigns-table" columns={columns} order={[6, 'desc']} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import StatisticsOpened from "./StatisticsOpened";
|
|||
import StatisticsLinkClicks from "./StatisticsLinkClicks";
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
import Clone from "./Clone";
|
||||
|
||||
function getMenus(t) {
|
||||
const aggLabels = {
|
||||
|
@ -25,6 +26,12 @@ function getMenus(t) {
|
|||
'devices': t('devices')
|
||||
};
|
||||
|
||||
const createLabels = {
|
||||
[CampaignType.REGULAR]: t('createRegularCampaign'),
|
||||
[CampaignType.RSS]: t('createRssCampaign'),
|
||||
[CampaignType.TRIGGERED]: t('createTriggeredCampaign')
|
||||
};
|
||||
|
||||
return {
|
||||
'campaigns': {
|
||||
title: t('campaigns'),
|
||||
|
@ -160,16 +167,30 @@ function getMenus(t) {
|
|||
}
|
||||
},
|
||||
'create-regular': {
|
||||
title: t('createRegularCampaign'),
|
||||
title: createLabels[CampaignType.REGULAR],
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.REGULAR} permissions={props.permissions} />
|
||||
},
|
||||
'create-rss': {
|
||||
title: t('createRssCampaign'),
|
||||
title: createLabels[CampaignType.RSS],
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.RSS} permissions={props.permissions} />
|
||||
},
|
||||
'create-triggered': {
|
||||
title: t('createTriggeredCampaign'),
|
||||
title: createLabels[CampaignType.TRIGGERED],
|
||||
panelRender: props => <CampaignsCUD action="create" type={CampaignType.TRIGGERED} permissions={props.permissions} />
|
||||
},
|
||||
'clone': {
|
||||
title: t('Create Campaign'),
|
||||
link: params => `/campaigns/clone`,
|
||||
panelRender: props => <Clone />,
|
||||
children: {
|
||||
':existingCampaignId([0-9]+)': {
|
||||
title: resolved => createLabels[resolved.existingCampaign.type],
|
||||
resolve: {
|
||||
existingCampaign: params => `rest/campaigns-settings/${params.existingCampaignId}`
|
||||
},
|
||||
panelRender: props => <CampaignsCUD action="create" createFromCampaign={props.resolved.existingCampaign} permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
105
client/src/channels/List.js
Normal file
105
client/src/channels/List.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {withTranslation} from '../lib/i18n';
|
||||
import {Icon} from '../lib/bootstrap-components';
|
||||
import {LinkButton, requiresAuthenticatedUser, Title, Toolbar, withPageHelpers} from '../lib/page';
|
||||
import {withErrorHandling} from '../lib/error-handling';
|
||||
import {Table} from '../lib/table';
|
||||
import {tableAddDeleteButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
|
||||
import {withComponentMixins} from "../lib/decorator-helpers";
|
||||
import styles from "./styles.scss";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling,
|
||||
withPageHelpers,
|
||||
requiresAuthenticatedUser
|
||||
])
|
||||
export default class List extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.state = {};
|
||||
tableRestActionDialogInit(this);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
permissions: PropTypes.object
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
const permissions = this.props.permissions;
|
||||
const createPermitted = permissions.createChannel;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
data: 1,
|
||||
title: t('name'),
|
||||
actions: data => {
|
||||
const perms = data[10];
|
||||
if (perms.includes('view')) {
|
||||
return [{label: data[1], link: `/channels/${data[0]}/campaigns`}];
|
||||
} else {
|
||||
return [{label: data[1]}];
|
||||
}
|
||||
}
|
||||
},
|
||||
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
|
||||
{ data: 3, title: t('description') },
|
||||
{ data: 4, title: t('namespace') },
|
||||
{
|
||||
className: styles.tblCol_buttons,
|
||||
actions: data => {
|
||||
const actions = [];
|
||||
const perms = data[5];
|
||||
|
||||
if (perms.includes('view')) {
|
||||
actions.push({
|
||||
label: <Icon icon="inbox" title={t('Campaigns')}/>,
|
||||
link: `/channels/${data[0]}/campaigns`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('view') || perms.includes('edit')) {
|
||||
actions.push({
|
||||
label: <Icon icon="edit" title={t('edit')}/>,
|
||||
link: `/channels/${data[0]}/edit`
|
||||
});
|
||||
}
|
||||
|
||||
if (perms.includes('share')) {
|
||||
actions.push({
|
||||
label: <Icon icon="share" title={t('share')}/>,
|
||||
link: `/channels/${data[0]}/share`
|
||||
});
|
||||
}
|
||||
|
||||
tableAddDeleteButton(actions, this, perms, `rest/channels/${data[0]}`, data[1], t('Deleting channel ...'), t('Channel deleted'));
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
{tableRestActionDialogRender(this)}
|
||||
<Toolbar>
|
||||
{createPermitted &&
|
||||
<LinkButton to="/channels/create" className="btn-primary" icon="plus" label={t('Create Channel')}/>
|
||||
}
|
||||
</Toolbar>
|
||||
|
||||
<Title>{t('Channels')}</Title>
|
||||
|
||||
<Table ref={node => this.table = node} withHeader dataUrl="rest/channels-table" columns={columns} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
79
client/src/channels/root.js
Normal file
79
client/src/channels/root.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import CampaignsList from '../campaigns/List';
|
||||
import CampaignsCUD from '../campaigns/CUD';
|
||||
import ChannelsList from './List';
|
||||
//import ChannelsCUD from './CUD';
|
||||
import Share from '../shares/Share';
|
||||
import {ellipsizeBreadcrumbLabel} from "../lib/helpers"
|
||||
import {namespaceCheckPermissions} from "../lib/namespace";
|
||||
|
||||
function getMenus(t) {
|
||||
return {
|
||||
'channels': {
|
||||
title: t('Channels'),
|
||||
link: '/channels',
|
||||
checkPermissions: {
|
||||
createChannel: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createChannel']
|
||||
},
|
||||
createCampaign: {
|
||||
entityTypeId: 'namespace',
|
||||
requiredOperations: ['createCampaign']
|
||||
},
|
||||
...namespaceCheckPermissions('createChannel'),
|
||||
},
|
||||
|
||||
panelRender: props => <ChannelsList permissions={props.permissions}/>,
|
||||
children: {
|
||||
':channelId([0-9]+)': {
|
||||
title: resolved => t('channelName', {name: ellipsizeBreadcrumbLabel(resolved.channel.name)}),
|
||||
resolve: {
|
||||
channel: params => `rest/channels/${params.channelId}`
|
||||
},
|
||||
link: params => `/channels/${params.channelId}/campaigns`,
|
||||
navs: {
|
||||
campaigns: {
|
||||
title: t('Campaigns'),
|
||||
link: params => `/channels/${params.channelId}/campaigns`,
|
||||
visible: resolved => resolved.channel.permissions.includes('view'),
|
||||
panelRender: props => <CampaignsList channel={props.resolved.channel} />
|
||||
},
|
||||
/*
|
||||
':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`,
|
||||
visible: resolved => resolved.channel.permissions.includes('share'),
|
||||
panelRender: props => <Share title={t('share')} entity={props.resolved.channel} entityTypeId="channel" />
|
||||
}
|
||||
},
|
||||
children: {
|
||||
create: {
|
||||
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} />
|
||||
}
|
||||
}
|
||||
},
|
||||
'create': {
|
||||
title: t('Create Channel'),
|
||||
panelRender: props => <ChannelsCUD action="create" permissions={props.permissions} />
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
getMenus
|
||||
}
|
0
client/src/channels/styles.scss
Normal file
0
client/src/channels/styles.scss
Normal file
24
client/src/lib/bootstrap-components.js
vendored
24
client/src/lib/bootstrap-components.js
vendored
|
@ -111,6 +111,7 @@ export class Button extends Component {
|
|||
export class ButtonDropdown extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
icon: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
buttonClassName: PropTypes.string,
|
||||
menuClassName: PropTypes.string
|
||||
|
@ -119,19 +120,24 @@ export class ButtonDropdown extends Component {
|
|||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const className = 'dropdown' + (props.className ? ' ' + props.className : '');
|
||||
const className = 'btn-group' + (props.className ? ' ' + props.className : '');
|
||||
const buttonClassName = 'btn dropdown-toggle' + (props.buttonClassName ? ' ' + props.buttonClassName : '');
|
||||
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
|
||||
|
||||
return (
|
||||
<div className="dropdown" className={className}>
|
||||
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{props.label}
|
||||
</button>
|
||||
<ul className={menuClassName}>
|
||||
{props.children}
|
||||
</ul>
|
||||
let icon;
|
||||
if (props.icon) {
|
||||
icon = <Icon icon={props.icon}/>
|
||||
}
|
||||
|
||||
let iconSpacer;
|
||||
if (props.icon && props.label) {
|
||||
iconSpacer = ' ';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{icon}{iconSpacer}{props.label}</button>
|
||||
<ul className={menuClassName}>{props.children}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ 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 = {
|
||||
|
@ -929,6 +931,207 @@ 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
|
||||
])
|
||||
|
@ -984,6 +1187,7 @@ class TableSelect extends Component {
|
|||
dataUrl: PropTypes.string,
|
||||
data: PropTypes.array,
|
||||
columns: PropTypes.array,
|
||||
order: PropTypes.array,
|
||||
selectionKeyIndex: PropTypes.number,
|
||||
selectionLabelIndex: PropTypes.number,
|
||||
selectionAsArray: PropTypes.bool,
|
||||
|
@ -1076,6 +1280,7 @@ class TableSelect extends Component {
|
|||
data={props.data}
|
||||
dataUrl={props.dataUrl}
|
||||
columns={props.columns}
|
||||
order={props.order}
|
||||
selectMode={props.selectMode}
|
||||
selectionAsArray={this.props.selectionAsArray}
|
||||
withHeader={props.withHeader}
|
||||
|
@ -1094,6 +1299,7 @@ class TableSelect extends Component {
|
|||
data={props.data}
|
||||
dataUrl={props.dataUrl}
|
||||
columns={props.columns}
|
||||
order={props.order}
|
||||
pageLength={props.pageLength}
|
||||
selectMode={props.selectMode}
|
||||
selectionAsArray={this.props.selectionAsArray}
|
||||
|
|
|
@ -163,6 +163,13 @@ export function getRoutes(structure, parentRoute) {
|
|||
entryResolve = resolve;
|
||||
}
|
||||
|
||||
let entryResolveWithLocal;
|
||||
if (entry.localResolve) {
|
||||
entryResolveWithLocal = Object.assign({}, entryResolve, entry.localResolve);
|
||||
} else {
|
||||
entryResolveWithLocal = entryResolve;
|
||||
}
|
||||
|
||||
let entryCheckPermissions;
|
||||
if (entry.checkPermissions) {
|
||||
entryCheckPermissions = Object.assign({}, checkPermissions, entry.checkPermissions);
|
||||
|
@ -199,7 +206,7 @@ export function getRoutes(structure, parentRoute) {
|
|||
link: entry.link,
|
||||
panelInFullScreen: entry.panelInFullScreen,
|
||||
insideIframe: entry.insideIframe,
|
||||
resolve: entryResolve,
|
||||
resolve: entryResolveWithLocal,
|
||||
checkPermissions: entryCheckPermissions,
|
||||
parents,
|
||||
navs: [...navs, ...entryNavs],
|
||||
|
|
|
@ -12,6 +12,7 @@ import lists from './lists/root';
|
|||
import namespaces from './namespaces/root';
|
||||
import reports from './reports/root';
|
||||
import campaigns from './campaigns/root';
|
||||
import channels from './channels/root';
|
||||
import templates from './templates/root';
|
||||
import users from './users/root';
|
||||
import sendConfigurations from './send-configurations/root';
|
||||
|
@ -25,8 +26,9 @@ import {DropdownActionLink, Icon} from "./lib/bootstrap-components";
|
|||
import axios from './lib/axios';
|
||||
import {getUrl} from "./lib/urls";
|
||||
import {withComponentMixins} from "./lib/decorator-helpers";
|
||||
import Update from "./settings/Update";
|
||||
|
||||
const topLevelMenuKeys = ['lists', 'templates', 'campaigns'];
|
||||
const topLevelMenuKeys = ['lists', 'channels', 'templates', 'campaigns'];
|
||||
|
||||
if (mailtrainConfig.reportsEnabled) {
|
||||
topLevelMenuKeys.push('reports');
|
||||
|
@ -114,7 +116,10 @@ class Root extends Component {
|
|||
structure = {
|
||||
title: t('home'),
|
||||
link: '/',
|
||||
panelComponent: Home,
|
||||
localResolve: {
|
||||
configItems: params => `rest/settings`
|
||||
},
|
||||
panelRender: props => <Home configItems={props.resolved.configItems} />,
|
||||
primaryMenuComponent: MainMenu,
|
||||
children: {
|
||||
...login.getMenus(t),
|
||||
|
@ -127,7 +132,8 @@ class Root extends Component {
|
|||
...account.getMenus(t),
|
||||
...settings.getMenus(t),
|
||||
...sendConfigurations.getMenus(t),
|
||||
...campaigns.getMenus(t)
|
||||
...campaigns.getMenus(t),
|
||||
...channels.getMenus(t)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue