437 lines
No EOL
16 KiB
JavaScript
437 lines
No EOL
16 KiB
JavaScript
'use strict';
|
||
|
||
import React, {Component} from 'react';
|
||
import PropTypes from 'prop-types';
|
||
import { withTranslation } from '../../lib/i18n';
|
||
import {
|
||
NavButton,
|
||
requiresAuthenticatedUser,
|
||
Title,
|
||
withPageHelpers
|
||
} from '../../lib/page';
|
||
import {
|
||
Button,
|
||
ButtonRow,
|
||
CheckBox,
|
||
Dropdown,
|
||
Fieldset,
|
||
Form,
|
||
FormSendMethod,
|
||
InputField,
|
||
StaticField,
|
||
TextArea,
|
||
withForm
|
||
} from '../../lib/form';
|
||
import {
|
||
withAsyncErrorHandler,
|
||
withErrorHandling
|
||
} from '../../lib/error-handling';
|
||
import {DeleteModalDialog} from "../../lib/modals";
|
||
import {getImportLabels} from './helpers';
|
||
import {
|
||
ImportSource,
|
||
inProgress,
|
||
MappingType,
|
||
prepInProgress,
|
||
runInProgress
|
||
} from '../../../../shared/imports';
|
||
import axios from "../../lib/axios";
|
||
import {getUrl} from "../../lib/urls";
|
||
import listStyles from "../styles.scss";
|
||
import styles from "../../lib/styles.scss";
|
||
|
||
|
||
function truncate(str, len, ending = '...') {
|
||
str = str.trim();
|
||
|
||
if (str.length > len) {
|
||
return str.substring(0, len - ending.length) + ending;
|
||
} else {
|
||
return str;
|
||
}
|
||
}
|
||
|
||
|
||
@withTranslation()
|
||
@withForm
|
||
@withPageHelpers
|
||
@withErrorHandling
|
||
@requiresAuthenticatedUser
|
||
export default class CUD extends Component {
|
||
constructor(props) {
|
||
super(props);
|
||
|
||
this.state = {};
|
||
|
||
const {importSourceLabels, mappingTypeLabels} = getImportLabels(props.t);
|
||
|
||
this.importSourceLabels = importSourceLabels;
|
||
|
||
this.importSourceOptions = [
|
||
{key: ImportSource.CSV_FILE, label: importSourceLabels[ImportSource.CSV_FILE]},
|
||
// {key: ImportSource.LIST, label: importSourceLabels[ImportSource.LIST]}
|
||
];
|
||
|
||
this.mappingOptions = [
|
||
{key: MappingType.BASIC_SUBSCRIBE, label: mappingTypeLabels[MappingType.BASIC_SUBSCRIBE]},
|
||
{key: MappingType.BASIC_UNSUBSCRIBE, label: mappingTypeLabels[MappingType.BASIC_UNSUBSCRIBE]},
|
||
];
|
||
|
||
this.refreshTimeoutHandler = ::this.refreshEntity;
|
||
this.refreshTimeoutId = 0;
|
||
|
||
this.initForm();
|
||
}
|
||
|
||
static propTypes = {
|
||
action: PropTypes.string.isRequired,
|
||
list: PropTypes.object,
|
||
fieldsGrouped: PropTypes.array,
|
||
entity: PropTypes.object
|
||
}
|
||
|
||
initFromEntity(entity) {
|
||
this.getFormValuesFromEntity(entity, data => {
|
||
data.settings = data.settings || {};
|
||
const mapping = data.mapping || {};
|
||
|
||
if (data.source === ImportSource.CSV_FILE) {
|
||
data.csvFileName = data.settings.csv.originalname;
|
||
data.csvDelimiter = data.settings.csv.delimiter;
|
||
}
|
||
|
||
const mappingSettings = mapping.settings || {};
|
||
data.mapping_settings_checkEmails = 'checkEmails' in mappingSettings ? !!mappingSettings.checkEmails : true;
|
||
|
||
const mappingFlds = mapping.fields || {};
|
||
for (const field of this.props.fieldsGrouped) {
|
||
if (field.column) {
|
||
const colMapping = mappingFlds[field.column] || {};
|
||
data['mapping_fields_' + field.column + '_column'] = colMapping.column || '';
|
||
} else {
|
||
for (const option of field.settings.options) {
|
||
const col = field.groupedOptions[option.key].column;
|
||
const colMapping = mappingFlds[col] || {};
|
||
data['mapping_fields_' + col + '_column'] = colMapping.column || '';
|
||
}
|
||
}
|
||
}
|
||
|
||
const emailMapping = mappingFlds.email || {};
|
||
data.mapping_fields_email_column = emailMapping.column || '';
|
||
});
|
||
|
||
if (inProgress(entity.status)) {
|
||
this.refreshTimeoutId = setTimeout(this.refreshTimeoutHandler, 1000);
|
||
}
|
||
}
|
||
|
||
componentDidMount() {
|
||
if (this.props.entity) {
|
||
this.initFromEntity(this.props.entity);
|
||
} else {
|
||
this.populateFormValues({
|
||
name: '',
|
||
description: '',
|
||
source: ImportSource.CSV_FILE,
|
||
csvFileName: '',
|
||
csvDelimiter: ',',
|
||
});
|
||
}
|
||
}
|
||
|
||
componentWillUnmount() {
|
||
clearTimeout(this.refreshTimeoutId);
|
||
}
|
||
|
||
@withAsyncErrorHandler
|
||
async refreshEntity() {
|
||
const resp = await axios.get(getUrl(`rest/imports/${this.props.list.id}/${this.props.entity.id}`));
|
||
this.initFromEntity(resp.data);
|
||
}
|
||
|
||
localValidateFormValues(state) {
|
||
const t = this.props.t;
|
||
const isEdit = !!this.props.entity;
|
||
const source = Number.parseInt(state.getIn(['source', 'value']));
|
||
|
||
for (const key of state.keys()) {
|
||
state.setIn([key, 'error'], null);
|
||
}
|
||
|
||
if (!state.getIn(['name', 'value'])) {
|
||
state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
|
||
}
|
||
|
||
if (!isEdit) {
|
||
if (source === ImportSource.CSV_FILE) {
|
||
if (!this.csvFile || this.csvFile.files.length === 0) {
|
||
state.setIn(['csvFileName', 'error'], t('fileMustBeSelected'));
|
||
}
|
||
|
||
if (!state.getIn(['csvDelimiter', 'value']).trim()) {
|
||
state.setIn(['csvDelimiter', 'error'], t('csvDelimiterMustNotBeEmpty'));
|
||
}
|
||
}
|
||
} else {
|
||
const mappingType = Number.parseInt(state.getIn(['mapping_type', 'value']));
|
||
|
||
if (mappingType === MappingType.BASIC_SUBSCRIBE || mappingType === MappingType.BASIC_UNSUBSCRIBE) {
|
||
if (!state.getIn(['mapping_fields_email_column', 'value'])) {
|
||
state.setIn(['mapping_fields_email_column', 'error'], t('emailMappingHasToBeProvided'));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async submitHandler() {
|
||
const t = this.props.t;
|
||
const isEdit = !!this.props.entity;
|
||
|
||
|
||
let sendMethod, url;
|
||
if (this.props.entity) {
|
||
sendMethod = FormSendMethod.PUT;
|
||
url = `rest/imports/${this.props.list.id}/${this.props.entity.id}`
|
||
} else {
|
||
sendMethod = FormSendMethod.POST;
|
||
url = `rest/imports/${this.props.list.id}`
|
||
}
|
||
|
||
try {
|
||
this.disableForm();
|
||
this.setFormStatusMessage('info', t('saving'));
|
||
|
||
const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
|
||
data.source = Number.parseInt(data.source);
|
||
data.settings = {};
|
||
|
||
const formData = new FormData();
|
||
if (!isEdit) {
|
||
if (data.source === ImportSource.CSV_FILE) {
|
||
data.settings.csv = {};
|
||
formData.append('csvFile', this.csvFile.files[0]);
|
||
data.settings.csv.delimiter = data.csvDelimiter.trim();
|
||
}
|
||
|
||
} else {
|
||
data.mapping_type = Number.parseInt(data.mapping_type);
|
||
const mapping = {
|
||
fields: {},
|
||
settings: {}
|
||
};
|
||
|
||
if (data.mapping_type === MappingType.BASIC_SUBSCRIBE) {
|
||
mapping.settings.checkEmails = data.mapping_settings_checkEmails;
|
||
|
||
for (const field of this.props.fieldsGrouped) {
|
||
if (field.column) {
|
||
const colMapping = data['mapping_fields_' + field.column + '_column'];
|
||
if (colMapping) {
|
||
mapping.fields[field.column] = {
|
||
column: colMapping
|
||
};
|
||
}
|
||
} else {
|
||
for (const option of field.settings.options) {
|
||
const col = field.groupedOptions[option.key].column;
|
||
const colMapping = data['mapping_fields_' + col + '_column'];
|
||
if (colMapping) {
|
||
mapping.fields[col] = {
|
||
column: colMapping
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (data.mapping_type === MappingType.BASIC_SUBSCRIBE || data.mapping_type === MappingType.BASIC_UNSUBSCRIBE) {
|
||
mapping.fields.email = {
|
||
column: data.mapping_fields_email_column
|
||
};
|
||
}
|
||
|
||
|
||
data.mapping = mapping;
|
||
}
|
||
|
||
for (const key in data) {
|
||
if (key.startsWith('mapping_fields') || key.startsWith('mapping_settings')) {
|
||
delete data[key];
|
||
}
|
||
}
|
||
|
||
delete data.csvFile;
|
||
delete data.csvDelimiter;
|
||
delete data.sampleRow;
|
||
|
||
formData.append('entity', JSON.stringify(data));
|
||
|
||
return formData;
|
||
});
|
||
|
||
if (submitResponse) {
|
||
if (!isEdit) {
|
||
this.navigateTo(`/lists/${this.props.list.id}/imports/${submitResponse}/edit`);
|
||
} else {
|
||
this.navigateToWithFlashMessage(`/lists/${this.props.list.id}/imports/${this.props.entity.id}/status`, 'success', t('importSaved'));
|
||
}
|
||
|
||
} else {
|
||
this.enableForm();
|
||
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
|
||
}
|
||
} catch (error) {
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
onFileSelected(evt, x) {
|
||
if (!this.getFormValue('name') && this.csvFile.files.length > 0) {
|
||
this.updateFormValue('name', this.csvFile.files[0].name);
|
||
}
|
||
|
||
this.scheduleFormRevalidate();
|
||
}
|
||
|
||
render() {
|
||
const t = this.props.t;
|
||
const isEdit = !!this.props.entity;
|
||
|
||
const source = Number.parseInt(this.getFormValue('source'));
|
||
const status = this.getFormValue('status');
|
||
const settings = this.getFormValue('settings');
|
||
|
||
let settingsEdit = null;
|
||
if (source === ImportSource.CSV_FILE) {
|
||
if (isEdit) {
|
||
settingsEdit =
|
||
<div>
|
||
<StaticField id="csvFileName" className={styles.formDisabled} label={t('file')}>{this.getFormValue('csvFileName')}</StaticField>
|
||
<StaticField id="csvDelimiter" className={styles.formDisabled} label={t('delimiter')}>{this.getFormValue('csvDelimiter')}</StaticField>
|
||
</div>;
|
||
} else {
|
||
settingsEdit =
|
||
<div>
|
||
<StaticField withValidation id="csvFileName" label={t('file')}><input ref={node => this.csvFile = node} type="file" onChange={::this.onFileSelected}/></StaticField>
|
||
<InputField id="csvDelimiter" label={t('delimiter')}/>
|
||
</div>;
|
||
}
|
||
}
|
||
|
||
let mappingEdit;
|
||
if (isEdit) {
|
||
if (prepInProgress(status)) {
|
||
mappingEdit = (
|
||
<div>{t('preparationInProgressPleaseWaitTillItIs')}</div>
|
||
);
|
||
|
||
} else {
|
||
let mappingSettings = null;
|
||
const mappingType = Number.parseInt(this.getFormValue('mapping_type'));
|
||
|
||
if (mappingType === MappingType.BASIC_SUBSCRIBE || mappingType === MappingType.BASIC_UNSUBSCRIBE) {
|
||
const sampleRow = this.getFormValue('sampleRow');
|
||
const sourceOpts = [];
|
||
sourceOpts.push({key: '', label: t('––Select ––')});
|
||
if (source === ImportSource.CSV_FILE) {
|
||
for (const csvCol of settings.csv.columns) {
|
||
let help = '';
|
||
if (sampleRow) {
|
||
help = ' (' + t('eg', {keySeparator: '>', nsSeparator: '|'}) + ' ' + truncate(sampleRow[csvCol.column], 50) + ')';
|
||
}
|
||
|
||
sourceOpts.push({key: csvCol.column, label: csvCol.name + help});
|
||
}
|
||
}
|
||
|
||
const settingsRows = [];
|
||
const mappingRows = [
|
||
<Dropdown key="email" id="mapping_fields_email_column" label={t('email')} options={sourceOpts}/>
|
||
];
|
||
|
||
if (mappingType === MappingType.BASIC_SUBSCRIBE) {
|
||
settingsRows.push(<CheckBox key="checkEmails" id="mapping_settings_checkEmails" text={t('checkImportedEmails')}/>)
|
||
|
||
for (const field of this.props.fieldsGrouped) {
|
||
if (field.column) {
|
||
mappingRows.push(
|
||
<Dropdown key={field.column} id={'mapping_fields_' + field.column + '_column'} label={field.name} options={sourceOpts}/>
|
||
);
|
||
} else {
|
||
for (const option of field.settings.options) {
|
||
const col = field.groupedOptions[option.key].column;
|
||
mappingRows.push(
|
||
<Dropdown key={col} id={'mapping_fields_' + col + '_column'} label={field.groupedOptions[option.key].name} options={sourceOpts}/>
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
mappingSettings = (
|
||
<div>
|
||
{settingsRows}
|
||
<Fieldset label={t('mapping')} className={listStyles.mapping}>
|
||
{mappingRows}
|
||
</Fieldset>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
mappingEdit = (
|
||
<div>
|
||
<Dropdown id="mapping_type" label={t('type')} options={this.mappingOptions}/>
|
||
{mappingSettings}
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
let saveButtonLabel;
|
||
if (!isEdit) {
|
||
saveButtonLabel = t('saveAndEditSettings');
|
||
} else {
|
||
saveButtonLabel = t('save');
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
{isEdit &&
|
||
<DeleteModalDialog
|
||
stateOwner={this}
|
||
visible={this.props.action === 'delete'}
|
||
deleteUrl={`rest/imports/${this.props.list.id}/${this.props.entity.id}`}
|
||
backUrl={`/lists/${this.props.list.id}/imports/${this.props.entity.id}/edit`}
|
||
successUrl={`/lists/${this.props.list.id}/imports`}
|
||
deletingMsg={t('deletingImport')}
|
||
deletedMsg={t('importDeleted')}/>
|
||
}
|
||
|
||
<Title>{isEdit ? t('editImport') : t('createImport')}</Title>
|
||
|
||
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
|
||
<InputField id="name" label={t('name')}/>
|
||
<TextArea id="description" label={t('description')}/>
|
||
|
||
{isEdit ?
|
||
<StaticField id="source" className={styles.formDisabled} label={t('source')}>{this.importSourceLabels[this.getFormValue('source')]}</StaticField>
|
||
:
|
||
<Dropdown id="source" label={t('source')} options={this.importSourceOptions}/>
|
||
}
|
||
|
||
{settingsEdit}
|
||
|
||
{mappingEdit}
|
||
|
||
|
||
<ButtonRow>
|
||
<Button type="submit" className="btn-primary" icon="ok" label={saveButtonLabel}/>
|
||
{isEdit && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/lists/${this.props.list.id}/imports/${this.props.entity.id}/delete`}/>}
|
||
</ButtonRow>
|
||
</Form>
|
||
</div>
|
||
);
|
||
}
|
||
} |