Sorting for all listings & bugfixes for all listings except Newsletters

- newsletters listing now uses hash history
- newsletters are sorted by Subject (a->z)
- segments are sorted by Name (a->z)
- re-added WordPress Users list as a segment you can send a newsletter to
- added explicit error messages when an auto newsletter isn't fully configured
- added missing strings for "selectAll" in Segments listing
- fixed filters() in Subscribers listing (wrong count as it was not taking groups/filters/search into account)
This commit is contained in:
Jonathan Labreuille
2016-06-20 16:23:27 +02:00
parent 7af2775972
commit 68c09b8678
15 changed files with 136 additions and 95 deletions

View File

@@ -13,8 +13,7 @@ const columns = [
}, },
{ {
name: 'segments', name: 'segments',
label: MailPoet.I18n.t('segments'), label: MailPoet.I18n.t('segments')
sortable: false
}, },
{ {
name: 'created_at', name: 'created_at',

View File

@@ -379,21 +379,26 @@ const Listing = React.createClass({
) )
}) })
.map(key => { .map(key => {
let value = this.state[key] let value = this.state[key];
if (value === Object(value)) { if (value === Object(value)) {
value = jQuery.param(value) value = jQuery.param(value)
} else if (value === Boolean(value)) { } else if (value === Boolean(value)) {
value = value.toString() value = value.toString()
} }
if (value !== '') { if (value !== '' && value !== null) {
return `${key}[${value}]` return `${key}[${value}]`
} }
}) })
.filter(key => { return (key !== undefined) }) .filter(key => { return (key !== undefined) })
.join('/'); .join('/');
params = '/' + params; // prepend with tab is specified
if (this.props.tab !== undefined) {
params = `/${ this.props.tab }/${ params }`;
} else {
params = `/${ params }`;
}
if (this.props.location) { if (this.props.location) {
if (this.props.location.pathname !== params) { if (this.props.location.pathname !== params) {

View File

@@ -189,53 +189,62 @@ const NewsletterListNotification = React.createClass({
let sendingFrequency; let sendingFrequency;
let sendingToSegments; let sendingToSegments;
// set sending frequency // get list of segments' name
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
).replace(
'%$2s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$3s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
// set segments
const segments = newsletter.segments.map(function(segment) { const segments = newsletter.segments.map(function(segment) {
return segment.name return segment.name
}).join(', '); });
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace( // check if the user has specified segments to send to
'%$1s', segments if(segments.length === 0) {
); return (
<span className="mailpoet_error">
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingToSegments = MailPoet.I18n.t('ifNewContentToSegments').replace(
'%$1s', segments.join(', ')
);
// set sending frequency
switch (newsletter.options.intervalType) {
case 'daily':
sendingFrequency = MailPoet.I18n.t('sendDaily').replace(
'%$1s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'weekly':
sendingFrequency = MailPoet.I18n.t('sendWeekly').replace(
'%$1s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'monthly':
sendingFrequency = MailPoet.I18n.t('sendMonthly').replace(
'%$1s', monthDayValues[newsletter.options.monthDay]
).replace(
'%$2s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'nthWeekDay':
sendingFrequency = MailPoet.I18n.t('sendNthWeekDay').replace(
'%$1s', nthWeekDayValues[newsletter.options.nthWeekDay]
).replace(
'%$2s', weekDayValues[newsletter.options.weekDay]
).replace(
'%$3s', timeOfDayValues[newsletter.options.timeOfDay]
);
break;
case 'immediately':
sendingFrequency = MailPoet.I18n.t('sendImmediately');
break;
}
}
return ( return (
<span> <span>
@@ -286,6 +295,7 @@ const NewsletterListNotification = React.createClass({
<Listing <Listing
limit={ mailpoet_listing_per_page } limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params } params={ this.props.params }
endpoint="newsletters" endpoint="newsletters"
tab="notification" tab="notification"
@@ -295,6 +305,8 @@ const NewsletterListNotification = React.createClass({
item_actions={ newsletter_actions } item_actions={ newsletter_actions }
messages={ messages } messages={ messages }
auto_refresh={ true } auto_refresh={ true }
sort_by="subject"
sort_order="asc"
/> />
</div> </div>
); );

View File

@@ -1,14 +1,12 @@
import React from 'react' import React from 'react'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router' import { Router, Link } from 'react-router'
import { createHashHistory } from 'history'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
import classNames from 'classnames' import classNames from 'classnames'
import jQuery from 'jquery' import jQuery from 'jquery'
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet'
import Listing from 'listing/listing.jsx'
import ListingTabs from 'newsletters/listings/tabs.jsx'
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled'])); const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = { const messages = {
@@ -319,6 +317,7 @@ const NewsletterListStandard = React.createClass({
<Listing <Listing
limit={ mailpoet_listing_per_page } limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params } params={ this.props.params }
endpoint="newsletters" endpoint="newsletters"
tab="standard" tab="standard"
@@ -328,6 +327,8 @@ const NewsletterListStandard = React.createClass({
item_actions={ newsletter_actions } item_actions={ newsletter_actions }
messages={ messages } messages={ messages }
auto_refresh={ true } auto_refresh={ true }
sort_by="subject"
sort_order="asc"
/> />
</div> </div>
); );

View File

@@ -11,6 +11,7 @@ import MailPoet from 'mailpoet'
import _ from 'underscore' import _ from 'underscore'
const mailpoet_roles = window.mailpoet_roles || {}; const mailpoet_roles = window.mailpoet_roles || {};
const mailpoet_segments = window.mailpoet_segments || {};
const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled'])); const mailpoet_tracking_enabled = (!!(window['mailpoet_tracking_enabled']));
const messages = { const messages = {
@@ -216,39 +217,51 @@ const NewsletterListWelcome = React.createClass({
return (~~(segment.id) === ~~(newsletter.options.segment)); return (~~(segment.id) === ~~(newsletter.options.segment));
}); });
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace( if (segment === undefined) {
return (
<span className="mailpoet_error">
{ MailPoet.I18n.t('sendingToSegmentsNotSpecified') }
</span>
);
} else {
sendingEvent = MailPoet.I18n.t('welcomeEventSegment').replace(
'%$1s', segment.name '%$1s', segment.name
); );
}
break; break;
} }
// set sending delay // set sending delay
if (newsletter.options.afterTimeType !== 'immediate') { if (sendingEvent) {
switch (newsletter.options.afterTimeType) { if (newsletter.options.afterTimeType !== 'immediate') {
case 'hours': switch (newsletter.options.afterTimeType) {
sendingDelay = MailPoet.I18n.t('sendingDelayHours').replace( case 'hours':
'%$1d', newsletter.options.afterTimeNumber sendingDelay = MailPoet.I18n.t('sendingDelayHours').replace(
); '%$1d', newsletter.options.afterTimeNumber
break; );
break;
case 'days': case 'days':
sendingDelay = MailPoet.I18n.t('sendingDelayDays').replace( sendingDelay = MailPoet.I18n.t('sendingDelayDays').replace(
'%$1d', newsletter.options.afterTimeNumber '%$1d', newsletter.options.afterTimeNumber
); );
break; break;
case 'weeks': case 'weeks':
sendingDelay = MailPoet.I18n.t('sendingDelayWeeks').replace( sendingDelay = MailPoet.I18n.t('sendingDelayWeeks').replace(
'%$1d', newsletter.options.afterTimeNumber '%$1d', newsletter.options.afterTimeNumber
); );
break; break;
}
sendingEvent += ' [' + sendingDelay + ']';
} }
sendingEvent += ' [' + sendingDelay + ']'; // add a "period" at the end if we do have a sendingEvent
sendingEvent += '.';
} }
return ( return (
<span> <span>
{ sendingEvent }. { sendingEvent }
</span> </span>
); );
}, },
@@ -326,6 +339,7 @@ const NewsletterListWelcome = React.createClass({
<Listing <Listing
limit={ mailpoet_listing_per_page } limit={ mailpoet_listing_per_page }
location={ this.props.location }
params={ this.props.params } params={ this.props.params }
endpoint="newsletters" endpoint="newsletters"
tab="welcome" tab="welcome"
@@ -335,6 +349,8 @@ const NewsletterListWelcome = React.createClass({
item_actions={ newsletter_actions } item_actions={ newsletter_actions }
messages={ messages } messages={ messages }
auto_refresh={ true } auto_refresh={ true }
sort_by="subject"
sort_order="asc"
/> />
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router' import { Router, Route, IndexRedirect, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history' import { createHashHistory } from 'history'
import NewsletterTypes from 'newsletters/types.jsx' import NewsletterTypes from 'newsletters/types.jsx'
@@ -29,11 +29,14 @@ if(container) {
ReactDOM.render(( ReactDOM.render((
<Router history={ history }> <Router history={ history }>
<Route path="/" component={ App }> <Route path="/" component={ App }>
<IndexRoute component={ NewsletterListStandard } /> <IndexRedirect to="standard" />
{/* Listings */} {/* Listings */}
<Route name="listing/standard" path="standard" component={ NewsletterListStandard } /> <Route name="listing/standard" path="standard" component={ NewsletterListStandard } />
<Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } /> <Route name="listing/welcome" path="welcome" component={ NewsletterListWelcome } />
<Route name="listing/notification" path="notification" component={ NewsletterListNotification } /> <Route name="listing/notification" path="notification" component={ NewsletterListNotification } />
<Route path="standard/*" component={ NewsletterListStandard } />
<Route path="welcome/*" component={ NewsletterListWelcome } />
<Route path="notification/*" component={ NewsletterListNotification } />
{/* Newsletter: type selection */} {/* Newsletter: type selection */}
<Route path="new" component={ NewsletterTypes } /> <Route path="new" component={ NewsletterTypes } />
{/* New newsletter: types */} {/* New newsletter: types */}
@@ -44,8 +47,6 @@ if(container) {
<Route name="template" path="template/:id" component={ NewsletterTemplates } /> <Route name="template" path="template/:id" component={ NewsletterTemplates } />
{/* Sending options */} {/* Sending options */}
<Route path="send/:id" component={ NewsletterSend } /> <Route path="send/:id" component={ NewsletterSend } />
<Route path="*" component={ NewsletterListStandard } />
</Route> </Route>
</Router> </Router>
), container); ), container);

View File

@@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import { Router, Link } from 'react-router' import { Router, Link } from 'react-router'
import jQuery from 'jquery' import jQuery from 'jquery'
import MailPoet from 'mailpoet' import MailPoet from 'mailpoet'
import classNames from 'classnames' import classNames from 'classnames'
@@ -15,23 +14,19 @@ var columns = [
}, },
{ {
name: 'description', name: 'description',
label: MailPoet.I18n.t('description'), label: MailPoet.I18n.t('description')
sortable: false
}, },
{ {
name: 'subscribed', name: 'subscribed',
label: MailPoet.I18n.t('subscribed'), label: MailPoet.I18n.t('subscribed')
sortable: false
}, },
{ {
name: 'unconfirmed', name: 'unconfirmed',
label: MailPoet.I18n.t('unconfirmed'), label: MailPoet.I18n.t('unconfirmed')
sortable: false
}, },
{ {
name: 'unsubscribed', name: 'unsubscribed',
label: MailPoet.I18n.t('unsubscribed'), label: MailPoet.I18n.t('unsubscribed')
sortable: false
}, },
{ {
name: 'created_at', name: 'created_at',

View File

@@ -2,6 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router' import { Router, Route, IndexRoute, Link, useRouterHistory } from 'react-router'
import { createHashHistory } from 'history' import { createHashHistory } from 'history'
import SegmentList from 'segments/list.jsx' import SegmentList from 'segments/list.jsx'
import SegmentForm from 'segments/form.jsx' import SegmentForm from 'segments/form.jsx'

View File

@@ -21,8 +21,7 @@ const columns = [
}, },
{ {
name: 'segments', name: 'segments',
label: MailPoet.I18n.t('lists'), label: MailPoet.I18n.t('lists')
sortable: false
}, },
{ {

View File

@@ -399,7 +399,7 @@ class Menu {
$data = array(); $data = array();
$data['items_per_page'] = $this->getLimitPerPage('newsletters'); $data['items_per_page'] = $this->getLimitPerPage('newsletters');
$data['segments'] = Segment::getSegmentsWithSubscriberCount(); $data['segments'] = Segment::getPublished()->findArray();
$data['settings'] = Setting::getAll(); $data['settings'] = Setting::getAll();
$data['roles'] = $wp_roles->get_names(); $data['roles'] = $wp_roles->get_names();
$data['roles']['mailpoet_all'] = __('In any WordPress role'); $data['roles']['mailpoet_all'] = __('In any WordPress role');

View File

@@ -76,6 +76,10 @@ class Handler {
} }
return $custom_query; return $custom_query;
} else { } else {
$this->setFilter();
$this->setGroup();
$this->setSearch();
if(!empty($this->data['selection'])) { if(!empty($this->data['selection'])) {
$this->model->whereIn($this->table_name.'.id', $this->data['selection']); $this->model->whereIn($this->table_name.'.id', $this->data['selection']);
} }

View File

@@ -132,10 +132,11 @@ class Segment extends Model {
static function groupBy($orm, $group = null) { static function groupBy($orm, $group = null) {
if($group === 'trash') { if($group === 'trash') {
return $orm->whereNotNull('deleted_at'); $orm->whereNotNull('deleted_at');
} else { } else {
$orm = $orm->whereNull('deleted_at'); $orm->whereNull('deleted_at');
} }
return $orm;
} }
static function getSegmentsWithSubscriberCount() { static function getSegmentsWithSubscriberCount() {

View File

@@ -206,7 +206,9 @@ class Subscriber extends Model {
); );
} }
static function filters($orm, $group = 'all') { static function filters($data = array()) {
$group = (!empty($data['group'])) ? $data['group'] : 'all';
$segments = Segment::orderByAsc('name')->findMany(); $segments = Segment::orderByAsc('name')->findMany();
$segment_list = array(); $segment_list = array();
$segment_list[] = array( $segment_list[] = array(

View File

@@ -227,6 +227,7 @@
'sendMonthly': __('Send monthly on the %$1s at %$2s'), 'sendMonthly': __('Send monthly on the %$1s at %$2s'),
'sendNthWeekDay': __('Send every %$1s %$2s of the month at %$3s'), 'sendNthWeekDay': __('Send every %$1s %$2s of the month at %$3s'),
'sendImmediately': __('Send immediately'), 'sendImmediately': __('Send immediately'),
'ifNewContentToSegments': __("if there's new content to %$1s.") 'ifNewContentToSegments': __("if there's new content to %$1s."),
'sendingToSegmentsNotSpecified': __('You need to select a segment to send to.')
}) %> }) %>
<% endblock %> <% endblock %>

View File

@@ -14,6 +14,10 @@
'searchLabel': __('Search'), 'searchLabel': __('Search'),
'loadingItems': __('Loading segments...'), 'loadingItems': __('Loading segments...'),
'noItemsFound': __('No segments found.'), 'noItemsFound': __('No segments found.'),
'selectAllLabel': __('All segments on this page are selected.'),
'selectedAllLabel': __('All %d segments are selected.'),
'selectAllLink': __('Select all segments on all pages.'),
'clearSelection': __('Clear selection.'),
'permanentlyDeleted': __('%d segments were permanently deleted.'), 'permanentlyDeleted': __('%d segments were permanently deleted.'),
'selectBulkAction': __('Select bulk action'), 'selectBulkAction': __('Select bulk action'),
'bulkActions': __('Bulk Actions'), 'bulkActions': __('Bulk Actions'),