Add column selection to data table
[MAILPOET-1809]
This commit is contained in:
@@ -158,269 +158,6 @@ jQuery(document).ready(() => {
|
||||
? data
|
||||
: new Handlebars.SafeString(Handlebars.Utils.escapeExpression(data))));
|
||||
|
||||
// start array index from 1
|
||||
Handlebars.registerHelper('calculate_index', (rawIndex) => {
|
||||
const index = parseInt(rawIndex, 10);
|
||||
// display filler data (e.g., ellipsis) if we've reached the maximum number of rows and
|
||||
// subscribers count is greater than the maximum number of rows we're displaying
|
||||
if (index === maxRowsToShow && subscribers.subscribersCount > (maxRowsToShow + 1)) {
|
||||
fillerPosition = index;
|
||||
return filler;
|
||||
}
|
||||
if (index === (subscribers.subscribers.length - 1)) {
|
||||
// if we're on the last line, show the total count of subscribers data
|
||||
return subscribers.subscribersCount.toLocaleString();
|
||||
}
|
||||
return index + 1;
|
||||
});
|
||||
|
||||
// reduce subscribers object if the total length is greater than the
|
||||
// maximum number of defined rows
|
||||
if (subscribers.subscribersCount > (maxRowsToShow + 1)) {
|
||||
subscribers.subscribers.splice(
|
||||
maxRowsToShow, subscribers.subscribersCount - (maxRowsToShow + 1),
|
||||
fillerArray
|
||||
);
|
||||
}
|
||||
|
||||
// filter subscribers' data to detect dates, emails, etc.
|
||||
function filterSubscribers() {
|
||||
const subscribersClone = jQuery.extend(true, {}, subscribers);
|
||||
let preventNextStep = false;
|
||||
jQuery(
|
||||
'[data-id="notice_invalidEmail"], [data-id="notice_invalidDate"]'
|
||||
)
|
||||
.remove();
|
||||
const displayedColumns = jQuery.map(
|
||||
jQuery('.mailpoet_subscribers_column_data_match'), (element, elementIndex) => {
|
||||
const columnId = jQuery(element).data('column-id');
|
||||
const validationRule = jQuery(element).data('validation-rule');
|
||||
jQuery(element).val(columnId).trigger('change');
|
||||
return {
|
||||
id: columnId,
|
||||
index: elementIndex,
|
||||
validationRule,
|
||||
element,
|
||||
};
|
||||
}
|
||||
);
|
||||
// iterate through the object of mailpoet columns
|
||||
jQuery.map(window.mailpoetColumns, (column) => {
|
||||
let firstRowData;
|
||||
let validationRule;
|
||||
let testedFormat;
|
||||
let allowedDateFormats;
|
||||
// check if the column id matches the selected id of one of the
|
||||
// subscriber's data columns
|
||||
const matchedColumn = _.find(
|
||||
displayedColumns,
|
||||
data => data.id === column.id
|
||||
);
|
||||
// EMAIL filter: if the first value in the column doesn't have a valid
|
||||
// email, hide the next button
|
||||
if (column.id === 'email') {
|
||||
if (!window.mailpoet_email_regex.test(
|
||||
subscribersClone.subscribers[0][matchedColumn.index]
|
||||
)
|
||||
) {
|
||||
preventNextStep = true;
|
||||
if (!jQuery('[data-id="notice_invalidEmail"]').length) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidElement'), {
|
||||
static: true,
|
||||
scroll: true,
|
||||
hideClose: true,
|
||||
id: 'invalidEmail',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
MailPoet.Notice.hide('invalidEmail');
|
||||
}
|
||||
}
|
||||
// DATE filter: if column type is date, check if we can recognize it
|
||||
if (column.type === 'date' && matchedColumn) {
|
||||
allowedDateFormats = [
|
||||
Moment.ISO_8601,
|
||||
'YYYY/MM/DD',
|
||||
'MM/DD/YYYY',
|
||||
'DD/MM/YYYY',
|
||||
'YYYY/MM/DD',
|
||||
'YYYY/DD/MM',
|
||||
'MM/YYYY',
|
||||
'YYYY/MM',
|
||||
'YYYY',
|
||||
];
|
||||
firstRowData = subscribersClone.subscribers[0][matchedColumn.index];
|
||||
validationRule = false;
|
||||
// check if date exists
|
||||
if (firstRowData.trim() === '') {
|
||||
subscribersClone.subscribers[0][matchedColumn.index] = `<span class="mailpoet_data_match mailpoet_import_error" title="${MailPoet.I18n.t('noDateFieldMatch')}">${MailPoet.I18n.t('emptyFirstRowDate')}</span> `;
|
||||
preventNextStep = true;
|
||||
} else {
|
||||
Object.keys(allowedDateFormats).forEach((format) => {
|
||||
testedFormat = allowedDateFormats[format];
|
||||
if (Moment(firstRowData, testedFormat, true).isValid()) {
|
||||
validationRule = (typeof (testedFormat) === 'function')
|
||||
? 'datetime'
|
||||
: testedFormat;
|
||||
// set validation on the column element
|
||||
jQuery(matchedColumn.element).data('validation-rule', validationRule);
|
||||
return;
|
||||
}
|
||||
if (validationRule === 'datetime') {
|
||||
validationRule = Moment.ISO_8601;
|
||||
}
|
||||
});
|
||||
}
|
||||
jQuery.map(subscribersClone.subscribers, (dataSubscribers, index) => {
|
||||
const data = dataSubscribers;
|
||||
const rowData = data[matchedColumn.index];
|
||||
const date = Moment(rowData, testedFormat, true);
|
||||
if (index === fillerPosition || rowData.trim() === '') return;
|
||||
// validate date
|
||||
if (date.isValid()) {
|
||||
data[matchedColumn.index] = new Handlebars.SafeString(
|
||||
`${Handlebars.Utils.escapeExpression(data[matchedColumn.index])}<span class="mailpoet_data_match" title="${MailPoet.I18n.t('verifyDateMatch')}">${MailPoet.Date.format(date)}</span> `
|
||||
);
|
||||
} else {
|
||||
data[matchedColumn.index] = new Handlebars.SafeString(
|
||||
`${Handlebars.Utils.escapeExpression(data[matchedColumn.index])}<span class="mailpoet_data_match mailpoet_import_error" title="${MailPoet.I18n.t('noDateFieldMatch')}">${new Handlebars.SafeString(MailPoet.I18n.t('dateMatchError'))}</span> `
|
||||
);
|
||||
preventNextStep = true;
|
||||
}
|
||||
});
|
||||
if (preventNextStep && !jQuery('.mailpoet_invalidDate').length) {
|
||||
MailPoet.Notice.error(MailPoet.I18n.t('columnContainsInvalidDate'), {
|
||||
static: true,
|
||||
scroll: true,
|
||||
hideClose: true,
|
||||
id: 'invalidDate',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// refresh table with susbcribers' data
|
||||
jQuery('#subscribers_data > table > tbody')
|
||||
.html(subscribersDataTemplatePartial(subscribersClone));
|
||||
|
||||
if (preventNextStep) {
|
||||
toggleNextStepButton('off');
|
||||
} else if (!jQuery('.mailpoet_notice.error:visible').length
|
||||
&& segmentSelectElement.val()) {
|
||||
toggleNextStepButton('on');
|
||||
}
|
||||
}
|
||||
|
||||
// render template
|
||||
jQuery('#subscribers_data > table').html(subscribersDataTemplate(subscribers));
|
||||
|
||||
// filter displayed data
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.select2({
|
||||
data: window.mailpoetColumnsSelect2,
|
||||
width: '15em',
|
||||
templateResult(item) {
|
||||
return item.name;
|
||||
},
|
||||
templateSelection(item) {
|
||||
return item.name;
|
||||
},
|
||||
})
|
||||
.on('select2:selecting', (selectEvent) => {
|
||||
const selectElement = selectEvent.currentTarget;
|
||||
const selectedOptionId = selectEvent.params.args.data.id;
|
||||
// CREATE CUSTOM FIELD
|
||||
if (selectedOptionId === 'create') {
|
||||
selectEvent.preventDefault();
|
||||
jQuery(selectElement).select2('close');
|
||||
MailPoet.Modal.popup({
|
||||
title: MailPoet.I18n.t('addNewField'),
|
||||
template: jQuery('#form_template_field_form').html(),
|
||||
});
|
||||
jQuery('#form_field_new').parsley().on('form:submit', () => {
|
||||
// get data
|
||||
const data = jQuery('#form_field_new').mailpoetSerializeObject();
|
||||
|
||||
// save custom field
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'customFields',
|
||||
action: 'save',
|
||||
data,
|
||||
}).done((response) => {
|
||||
const newColumnData = {
|
||||
id: response.data.id,
|
||||
name: response.data.name,
|
||||
type: response.data.type,
|
||||
params: response.data.params,
|
||||
custom: true,
|
||||
};
|
||||
// if this is the first custom column, create an "optgroup"
|
||||
if (window.mailpoetColumnsSelect2.length === 2) {
|
||||
window.mailpoetColumnsSelect2.push({
|
||||
name: MailPoet.I18n.t('userColumns'),
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
window.mailpoetColumnsSelect2[2].children.push(newColumnData);
|
||||
window.mailpoetColumns.push(newColumnData);
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.each(() => {
|
||||
jQuery(selectElement)
|
||||
.html('')
|
||||
.select2('destroy')
|
||||
.select2({
|
||||
data: window.mailpoetColumnsSelect2,
|
||||
width: '15em',
|
||||
templateResult(item) {
|
||||
return item.name;
|
||||
},
|
||||
templateSelection(item) {
|
||||
return item.name;
|
||||
},
|
||||
});
|
||||
});
|
||||
jQuery(selectElement).data('column-id', newColumnData.id);
|
||||
jQuery(selectElement).data('validation-rule', false);
|
||||
filterSubscribers();
|
||||
// close popup
|
||||
MailPoet.Modal.close();
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(error => error.message),
|
||||
{ positionAfter: '#field_name' }
|
||||
);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
// CHANGE COLUMN
|
||||
// check for duplicate values in all select options
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.each(() => {
|
||||
const element = selectElement;
|
||||
const elementId = jQuery(element).val();
|
||||
// if another column has the same value and it's not an 'ignore',
|
||||
// prompt user
|
||||
if (elementId === selectedOptionId
|
||||
&& elementId !== 'ignore') {
|
||||
if (confirm(`${MailPoet.I18n.t('selectedValueAlreadyMatched')} ${MailPoet.I18n.t('confirmCorrespondingColumn')}`)) { // eslint-disable-line
|
||||
jQuery(element).data('column-id', 'ignore');
|
||||
} else {
|
||||
selectEvent.preventDefault();
|
||||
jQuery(selectElement).select2('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('select2:select', (selectEvent) => {
|
||||
const selectElement = selectEvent.currentTarget;
|
||||
const selectedOptionId = selectEvent.params.data.id;
|
||||
jQuery(selectElement).data('column-id', selectedOptionId);
|
||||
filterSubscribers();
|
||||
});
|
||||
|
||||
|
||||
nextStepButton.off().on('click', (event) => {
|
||||
|
@@ -4,6 +4,7 @@ import { withRouter } from 'react-router-dom';
|
||||
import PreviousNextStepButtons from './previous_next_step_buttons.jsx';
|
||||
import Warnings from './step_data_manipulation/warnings.jsx';
|
||||
import MatchTable from './step_data_manipulation/match_table.jsx';
|
||||
import SelectSegment from './step_data_manipulation/select_segment.jsx';
|
||||
|
||||
function getPreviousStepLink(importData, subscribersLimitForValidation) {
|
||||
if (importData === undefined) {
|
||||
@@ -46,6 +47,7 @@ function StepDataManipulation({
|
||||
subscribers={stepMethodSelectionData.subscribers}
|
||||
header={stepMethodSelectionData.header}
|
||||
/>
|
||||
<SelectSegment />
|
||||
</div>
|
||||
<PreviousNextStepButtons
|
||||
canGoNext={false}
|
||||
|
@@ -0,0 +1,120 @@
|
||||
import jQuery from 'jquery';
|
||||
import MailPoet from 'mailpoet';
|
||||
|
||||
export default (selectColumnType) => {
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.select2({
|
||||
data: window.mailpoetColumnsSelect2,
|
||||
width: '15em',
|
||||
templateResult(item) {
|
||||
return item.name;
|
||||
},
|
||||
templateSelection(item) {
|
||||
return item.name;
|
||||
},
|
||||
})
|
||||
.on('select2:selecting', (selectEvent) => {
|
||||
const selectElement = selectEvent.currentTarget;
|
||||
const selectedOptionId = selectEvent.params.args.data.id;
|
||||
// CREATE CUSTOM FIELD
|
||||
if (selectedOptionId === 'create') {
|
||||
selectEvent.preventDefault();
|
||||
jQuery(selectElement).select2('close');
|
||||
MailPoet.Modal.popup({
|
||||
title: MailPoet.I18n.t('addNewField'),
|
||||
template: jQuery('#form_template_field_form').html(),
|
||||
});
|
||||
jQuery('#form_field_new').parsley().on('form:submit', () => {
|
||||
// get data
|
||||
const data = jQuery('#form_field_new').mailpoetSerializeObject();
|
||||
|
||||
// save custom field
|
||||
MailPoet.Ajax.post({
|
||||
api_version: window.mailpoet_api_version,
|
||||
endpoint: 'customFields',
|
||||
action: 'save',
|
||||
data,
|
||||
}).done((response) => {
|
||||
const newColumnData = {
|
||||
id: response.data.id,
|
||||
name: response.data.name,
|
||||
type: response.data.type,
|
||||
params: response.data.params,
|
||||
custom: true,
|
||||
};
|
||||
// if this is the first custom column, create an "optgroup"
|
||||
if (window.mailpoetColumnsSelect2.length === 2) {
|
||||
window.mailpoetColumnsSelect2.push({
|
||||
name: MailPoet.I18n.t('userColumns'),
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
window.mailpoetColumnsSelect2[2].children.push(newColumnData);
|
||||
window.mailpoetColumns.push(newColumnData);
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.each(() => {
|
||||
jQuery(selectElement)
|
||||
.html('')
|
||||
.select2('destroy')
|
||||
.select2({
|
||||
data: window.mailpoetColumnsSelect2,
|
||||
width: '15em',
|
||||
templateResult(item) {
|
||||
return item.name;
|
||||
},
|
||||
templateSelection(item) {
|
||||
return item.name;
|
||||
},
|
||||
});
|
||||
});
|
||||
jQuery(selectElement).data('column-id', newColumnData.id);
|
||||
jQuery(selectElement).data('validation-rule', false);
|
||||
const columnIndex = jQuery(selectElement).data('column-index');
|
||||
selectColumnType(newColumnData.id, columnIndex);
|
||||
// close popup
|
||||
MailPoet.Modal.close();
|
||||
}).fail((response) => {
|
||||
if (response.errors.length > 0) {
|
||||
MailPoet.Notice.error(
|
||||
response.errors.map(error => error.message),
|
||||
{ positionAfter: '#field_name' }
|
||||
);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
// CHANGE COLUMN
|
||||
// check for duplicate values in all select options
|
||||
jQuery('select.mailpoet_subscribers_column_data_match')
|
||||
.each(() => {
|
||||
const element = selectElement;
|
||||
const elementId = jQuery(element).val();
|
||||
// if another column has the same value and it's not an 'ignore',
|
||||
// prompt user
|
||||
if (elementId === selectedOptionId
|
||||
&& elementId !== 'ignore') {
|
||||
if (confirm(`${MailPoet.I18n.t('selectedValueAlreadyMatched')} ${MailPoet.I18n.t('confirmCorrespondingColumn')}`)) { // eslint-disable-line
|
||||
jQuery(element).data('column-id', 'ignore');
|
||||
} else {
|
||||
selectEvent.preventDefault();
|
||||
jQuery(selectElement).select2('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.on('select2:select', (selectEvent) => {
|
||||
const selectElement = selectEvent.currentTarget;
|
||||
const selectedOptionId = selectEvent.params.data.id;
|
||||
jQuery(selectElement).data('column-id', selectedOptionId);
|
||||
const columnIndex = jQuery(selectElement).data('column-index');
|
||||
selectColumnType(selectedOptionId, columnIndex);
|
||||
});
|
||||
jQuery.map(
|
||||
jQuery('.mailpoet_subscribers_column_data_match'), (element) => {
|
||||
const columnId = jQuery(element).data('column-id');
|
||||
jQuery(element).val(columnId).trigger('change');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -1,106 +1,120 @@
|
||||
import React from 'react';
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import MailPoet from 'mailpoet';
|
||||
import _ from 'underscore';
|
||||
|
||||
import generateColumnSelection from './generate_column_selection.jsx';
|
||||
import matchColumns from './match_columns.jsx';
|
||||
|
||||
const MAX_SUBSCRIBERS_SHOWN = 10;
|
||||
|
||||
function ColumnDataMatch({ columnTypes }) {
|
||||
return (
|
||||
<tr>
|
||||
<th>{MailPoet.I18n.t('matchData')}</th>
|
||||
{/* eslint-disable-next-line react/no-array-index-key */}
|
||||
{columnTypes.map((columnType, i) => <th key={columnType.column_id + i}>{columnType.column_id}</th>)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
ColumnDataMatch.propTypes = {
|
||||
columnTypes: PropTypes.arrayOf(PropTypes.shape({ column_id: PropTypes.string })).isRequired,
|
||||
};
|
||||
|
||||
function Header({ header }) {
|
||||
return (
|
||||
<tr className="mailpoet_header">
|
||||
<td />
|
||||
{header.map(headerName => <td key={headerName}>{headerName}</td>)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
header: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
};
|
||||
|
||||
function Subscriber({ subscriber, index }) {
|
||||
return (
|
||||
<>
|
||||
<td>{index}</td>
|
||||
{/* eslint-disable-next-line react/no-array-index-key */}
|
||||
{subscriber.map((field, i) => <td key={field + index + i}>{field}</td>)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Subscriber.propTypes = {
|
||||
subscriber: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
index: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
function Subscribers({ subscribers, subscribersCount }) {
|
||||
const filler = '. . .';
|
||||
const fillerArray = Array(subscribers[0].length).fill(filler);
|
||||
return (
|
||||
<>
|
||||
{
|
||||
subscribers
|
||||
.slice(0, MAX_SUBSCRIBERS_SHOWN)
|
||||
.map((subscriber, i) => (
|
||||
<tr key={subscriber[0]}>
|
||||
<Subscriber subscriber={subscriber} index={i + 1} />
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
{
|
||||
subscribersCount > MAX_SUBSCRIBERS_SHOWN + 1
|
||||
? <tr key="filler"><Subscriber subscriber={fillerArray} index={filler} /></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
subscribersCount > MAX_SUBSCRIBERS_SHOWN
|
||||
? (
|
||||
<tr key={subscribers[subscribersCount - 1][0]}>
|
||||
<Subscriber subscriber={subscribers[subscribersCount - 1]} index={subscribersCount} />
|
||||
</tr>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Subscribers.propTypes = {
|
||||
subscribersCount: PropTypes.number.isRequired,
|
||||
subscribers: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
|
||||
};
|
||||
|
||||
function MatchTable({
|
||||
subscribersCount,
|
||||
subscribers,
|
||||
header,
|
||||
}) {
|
||||
const matchedColumnTypes = matchColumns(subscribers, header);
|
||||
let selectedColumns = [];
|
||||
|
||||
useLayoutEffect(() => {
|
||||
generateColumnSelection((selectedOptionId, columnIndex) => {
|
||||
selectedColumns[columnIndex] = selectedOptionId;
|
||||
});
|
||||
});
|
||||
|
||||
function ColumnDataMatch() {
|
||||
const matchedColumnTypes = matchColumns(subscribers, header);
|
||||
selectedColumns = _.pluck(matchedColumnTypes, 'column_id');
|
||||
return (
|
||||
<tr>
|
||||
<th>{MailPoet.I18n.t('matchData')}</th>
|
||||
{
|
||||
matchedColumnTypes.map((columnType, i) => {
|
||||
return (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<th key={columnType.column_id + i}>
|
||||
<select
|
||||
className="mailpoet_subscribers_column_data_match"
|
||||
data-column-id={columnType.column_id}
|
||||
data-validation-rule="false"
|
||||
data-column-index={i}
|
||||
id={`column_${i}`}
|
||||
/>
|
||||
</th>
|
||||
);
|
||||
})
|
||||
}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
<tr className="mailpoet_header">
|
||||
<td />
|
||||
{header.map(headerName => <td key={headerName}>{headerName}</td>)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function Subscriber({ subscriber, index }) {
|
||||
return (
|
||||
<>
|
||||
<td>{index}</td>
|
||||
{/* eslint-disable-next-line react/no-array-index-key */}
|
||||
{subscriber.map((field, i) => <td key={field + index + i}>{field}</td>)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Subscriber.propTypes = {
|
||||
subscriber: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
index: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
function Subscribers() {
|
||||
const filler = '. . .';
|
||||
const fillerArray = Array(subscribers[0].length).fill(filler);
|
||||
return (
|
||||
<>
|
||||
{
|
||||
subscribers
|
||||
.slice(0, MAX_SUBSCRIBERS_SHOWN)
|
||||
.map((subscriber, i) => (
|
||||
<tr key={subscriber[0]}>
|
||||
<Subscriber subscriber={subscriber} index={i + 1} />
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
{
|
||||
subscribersCount > MAX_SUBSCRIBERS_SHOWN + 1
|
||||
? <tr key="filler"><Subscriber subscriber={fillerArray} index={filler} /></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
subscribersCount > MAX_SUBSCRIBERS_SHOWN
|
||||
? (
|
||||
<tr key={subscribers[subscribersCount - 1][0]}>
|
||||
<Subscriber
|
||||
subscriber={subscribers[subscribersCount - 1]}
|
||||
index={subscribersCount}
|
||||
/>
|
||||
</tr>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="subscribers_data">
|
||||
<table className="mailpoet_subscribers widefat fixed">
|
||||
<thead>
|
||||
<ColumnDataMatch columnTypes={matchedColumnTypes} />
|
||||
<ColumnDataMatch />
|
||||
</thead>
|
||||
<tbody>
|
||||
<Header header={header} />
|
||||
<Subscribers subscribers={subscribers} subscribersCount={subscribersCount} />
|
||||
<Header />
|
||||
<Subscribers />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user