Fix listing related eslint PropTypes violations
This commit is contained in:
@@ -123,7 +123,10 @@ FormField.propTypes = {
|
|||||||
field: PropTypes.shape({
|
field: PropTypes.shape({
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
values: PropTypes.object,
|
values: PropTypes.object,
|
||||||
tip: PropTypes.string,
|
tip: PropTypes.oneOfType([
|
||||||
|
PropTypes.array,
|
||||||
|
PropTypes.string,
|
||||||
|
]),
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
fields: PropTypes.array,
|
fields: PropTypes.array,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
|
@@ -26,6 +26,11 @@ class Form extends React.Component {
|
|||||||
id: '',
|
id: '',
|
||||||
onSubmit: undefined,
|
onSubmit: undefined,
|
||||||
automationId: '',
|
automationId: '',
|
||||||
|
messages: {
|
||||||
|
onUpdate: () => { /* no-op */ },
|
||||||
|
onCreate: () => { /* no-op */ },
|
||||||
|
},
|
||||||
|
endpoint: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@@ -67,6 +72,7 @@ class Form extends React.Component {
|
|||||||
loadItem = (id) => {
|
loadItem = (id) => {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
if (!this.props.endpoint) return;
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: this.props.endpoint,
|
endpoint: this.props.endpoint,
|
||||||
@@ -120,6 +126,8 @@ class Form extends React.Component {
|
|||||||
item.id = this.props.params.id;
|
item.id = this.props.params.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.endpoint) return;
|
||||||
|
|
||||||
MailPoet.Ajax.post({
|
MailPoet.Ajax.post({
|
||||||
api_version: window.mailpoet_api_version,
|
api_version: window.mailpoet_api_version,
|
||||||
endpoint: this.props.endpoint,
|
endpoint: this.props.endpoint,
|
||||||
@@ -257,14 +265,14 @@ Form.propTypes = {
|
|||||||
}).isRequired,
|
}).isRequired,
|
||||||
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||||
errors: PropTypes.arrayOf(PropTypes.object),
|
errors: PropTypes.arrayOf(PropTypes.object),
|
||||||
endpoint: PropTypes.string.isRequired,
|
endpoint: PropTypes.string,
|
||||||
fields: PropTypes.arrayOf(PropTypes.object),
|
fields: PropTypes.arrayOf(PropTypes.object),
|
||||||
messages: PropTypes.shape({
|
messages: PropTypes.shape({
|
||||||
onUpdate: PropTypes.func,
|
onUpdate: PropTypes.func,
|
||||||
onCreate: PropTypes.func,
|
onCreate: PropTypes.func,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
children: PropTypes.element,
|
children: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
automationId: PropTypes.string,
|
automationId: PropTypes.string,
|
||||||
beforeFormContent: PropTypes.func,
|
beforeFormContent: PropTypes.func,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class ListingBulkActions extends React.Component {
|
class ListingBulkActions extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
@@ -7,20 +8,16 @@ class ListingBulkActions extends React.Component {
|
|||||||
extra: false,
|
extra: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChangeAction = (e) => {
|
getSelectedAction = () => {
|
||||||
this.setState({
|
const selectedAction = this.action.value;
|
||||||
action: e.target.value,
|
if (selectedAction.length > 0) {
|
||||||
extra: false,
|
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
|
||||||
}, () => {
|
|
||||||
const action = this.getSelectedAction();
|
|
||||||
|
|
||||||
// action on select callback
|
if (action.length > 0) {
|
||||||
if (action !== null && action.onSelect !== undefined) {
|
return action[0];
|
||||||
this.setState({
|
|
||||||
extra: action.onSelect(e),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleApplyAction = (e) => {
|
handleApplyAction = (e) => {
|
||||||
@@ -60,16 +57,20 @@ class ListingBulkActions extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getSelectedAction = () => {
|
handleChangeAction = (e) => {
|
||||||
const selectedAction = this.action.value;
|
this.setState({
|
||||||
if (selectedAction.length > 0) {
|
action: e.target.value,
|
||||||
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
|
extra: false,
|
||||||
|
}, () => {
|
||||||
|
const action = this.getSelectedAction();
|
||||||
|
|
||||||
if (action.length > 0) {
|
// action on select callback
|
||||||
return action[0];
|
if (action !== null && action.onSelect !== undefined) {
|
||||||
|
this.setState({
|
||||||
|
extra: action.onSelect(e),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -113,4 +114,14 @@ class ListingBulkActions extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListingBulkActions.propTypes = {
|
||||||
|
bulk_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
onBulkAction: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default ListingBulkActions;
|
export default ListingBulkActions;
|
||||||
|
@@ -1,34 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class ListingFilters extends React.Component {
|
class ListingFilters extends React.Component {
|
||||||
handleFilterAction = () => {
|
|
||||||
const filters = {};
|
|
||||||
this.getAvailableFilters().forEach((filter, i) => {
|
|
||||||
filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
|
|
||||||
});
|
|
||||||
if (this.props.onBeforeSelectFilter) {
|
|
||||||
this.props.onBeforeSelectFilter(filters);
|
|
||||||
}
|
|
||||||
return this.props.onSelectFilter(filters);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEmptyTrash = () => {
|
|
||||||
return this.props.onEmptyTrash();
|
|
||||||
};
|
|
||||||
|
|
||||||
getAvailableFilters = () => {
|
|
||||||
const filters = this.props.filters;
|
|
||||||
return Object.keys(filters).filter(filter => !(
|
|
||||||
filters[filter].length === 0
|
|
||||||
|| (
|
|
||||||
filters[filter].length === 1
|
|
||||||
&& !filters[filter][0].value
|
|
||||||
)
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const selectedFilters = this.props.filter;
|
const selectedFilters = this.props.filter;
|
||||||
this.getAvailableFilters().forEach(
|
this.getAvailableFilters().forEach(
|
||||||
@@ -42,6 +17,30 @@ class ListingFilters extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAvailableFilters = () => {
|
||||||
|
const filters = this.props.filters;
|
||||||
|
return Object.keys(filters).filter(filter => !(
|
||||||
|
filters[filter].length === 0
|
||||||
|
|| (
|
||||||
|
filters[filter].length === 1
|
||||||
|
&& !filters[filter][0].value
|
||||||
|
)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEmptyTrash = () => this.props.onEmptyTrash();
|
||||||
|
|
||||||
|
handleFilterAction = () => {
|
||||||
|
const filters = {};
|
||||||
|
this.getAvailableFilters().forEach((filter, i) => {
|
||||||
|
filters[this[`filter-${i}`].name] = this[`filter-${i}`].value;
|
||||||
|
});
|
||||||
|
if (this.props.onBeforeSelectFilter) {
|
||||||
|
this.props.onBeforeSelectFilter(filters);
|
||||||
|
}
|
||||||
|
return this.props.onSelectFilter(filters);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const filters = this.props.filters;
|
const filters = this.props.filters;
|
||||||
const availableFilters = this.getAvailableFilters()
|
const availableFilters = this.getAvailableFilters()
|
||||||
@@ -96,4 +95,20 @@ class ListingFilters extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListingFilters.propTypes = {
|
||||||
|
filters: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.array,
|
||||||
|
]).isRequired,
|
||||||
|
onEmptyTrash: PropTypes.func.isRequired,
|
||||||
|
onBeforeSelectFilter: PropTypes.func,
|
||||||
|
onSelectFilter: PropTypes.func.isRequired,
|
||||||
|
filter: PropTypes.objectOf(PropTypes.string).isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingFilters.defaultProps = {
|
||||||
|
onBeforeSelectFilter: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default ListingFilters;
|
export default ListingFilters;
|
||||||
|
@@ -1,13 +1,10 @@
|
|||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import PropTypes from 'prop-types';
|
||||||
|
import ListingColumn from './listing_column.jsx';
|
||||||
|
|
||||||
class ListingHeader extends React.Component {
|
class ListingHeader extends React.Component {
|
||||||
handleSelectItems = () => {
|
handleSelectItems = () => this.props.onSelectItems(this.toggle.checked);
|
||||||
return this.props.onSelectItems(
|
|
||||||
this.toggle.checked
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const columns = this.props.columns.map((column, index) => {
|
const columns = this.props.columns.map((column, index) => {
|
||||||
@@ -57,47 +54,23 @@ class ListingHeader extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListingColumn extends React.Component {
|
ListingHeader.propTypes = {
|
||||||
handleSort = () => {
|
onSelectItems: PropTypes.func.isRequired,
|
||||||
const sortBy = this.props.column.name;
|
onSort: PropTypes.func.isRequired,
|
||||||
const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
columns: PropTypes.arrayOf(PropTypes.object),
|
||||||
this.props.onSort(sortBy, sortOrder);
|
sort_by: PropTypes.string,
|
||||||
};
|
sort_order: PropTypes.string,
|
||||||
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
ListingHeader.defaultProps = {
|
||||||
const classes = classNames(
|
columns: [],
|
||||||
'manage-column',
|
sort_by: undefined,
|
||||||
{ 'column-primary': this.props.column.is_primary },
|
sort_order: 'desc',
|
||||||
{ sortable: this.props.column.sortable },
|
};
|
||||||
this.props.column.sorted,
|
|
||||||
{ sorted: (this.props.sort_by === this.props.column.name) }
|
|
||||||
);
|
|
||||||
let label;
|
|
||||||
|
|
||||||
if (this.props.column.sortable === true) {
|
|
||||||
label = (
|
|
||||||
<a
|
|
||||||
onClick={this.handleSort}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<span>{ this.props.column.label }</span>
|
|
||||||
<span className="sorting-indicator" />
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
label = this.props.column.label;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<th
|
|
||||||
role="columnheader"
|
|
||||||
className={classes}
|
|
||||||
id={this.props.column.name}
|
|
||||||
scope="col"
|
|
||||||
width={this.props.column.width || null}
|
|
||||||
>{label}</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = ListingHeader;
|
module.exports = ListingHeader;
|
||||||
|
@@ -1,312 +1,76 @@
|
|||||||
import MailPoet from 'mailpoet';
|
|
||||||
import jQuery from 'jquery';
|
import jQuery from 'jquery';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { Link } from 'react-router';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
import ListingBulkActions from 'listing/bulk_actions.jsx';
|
import ListingBulkActions from 'listing/bulk_actions.jsx';
|
||||||
import ListingHeader from 'listing/header.jsx';
|
import ListingHeader from 'listing/header.jsx';
|
||||||
import ListingPages from 'listing/pages.jsx';
|
import ListingPages from 'listing/pages.jsx';
|
||||||
import ListingSearch from 'listing/search.jsx';
|
import ListingSearch from 'listing/search.jsx';
|
||||||
import ListingGroups from 'listing/groups.jsx';
|
import ListingGroups from 'listing/groups.jsx';
|
||||||
import ListingFilters from 'listing/filters.jsx';
|
import ListingFilters from 'listing/filters.jsx';
|
||||||
|
import ListingItems from 'listing/listing_items.jsx';
|
||||||
|
|
||||||
class ListingItem extends React.Component {
|
const Listing = createReactClass({ // eslint-disable-line react/prefer-es6-class
|
||||||
state = {
|
|
||||||
expanded: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSelectItem = (e) => {
|
|
||||||
this.props.onSelectItem(
|
|
||||||
parseInt(e.target.value, 10),
|
|
||||||
e.target.checked
|
|
||||||
);
|
|
||||||
|
|
||||||
return !e.target.checked;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleRestoreItem = (id) => {
|
|
||||||
this.props.onRestoreItem(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTrashItem = (id) => {
|
|
||||||
this.props.onTrashItem(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDeleteItem = (id) => {
|
|
||||||
this.props.onDeleteItem(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleToggleItem = () => {
|
|
||||||
this.setState({ expanded: !this.state.expanded });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let checkbox = false;
|
|
||||||
|
|
||||||
if (this.props.is_selectable === true) {
|
|
||||||
checkbox = (
|
|
||||||
<th className="check-column" scope="row">
|
|
||||||
<label className="screen-reader-text" htmlFor={`listing-row-checkbox-${this.props.item.id}`}>{
|
|
||||||
`Select ${this.props.item[this.props.columns[0].name]}`
|
|
||||||
}</label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
value={this.props.item.id}
|
|
||||||
checked={
|
|
||||||
this.props.item.selected || this.props.selection === 'all'
|
|
||||||
}
|
|
||||||
onChange={this.handleSelectItem}
|
|
||||||
disabled={this.props.selection === 'all'}
|
|
||||||
id={`listing-row-checkbox-${this.props.item.id}`}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const customActions = this.props.item_actions;
|
|
||||||
let itemActions = false;
|
|
||||||
|
|
||||||
if (customActions.length > 0) {
|
|
||||||
let isFirst = true;
|
|
||||||
itemActions = customActions
|
|
||||||
.filter(action => action.display === undefined || action.display(this.props.item))
|
|
||||||
.map((action, index) => {
|
|
||||||
let customAction = null;
|
|
||||||
|
|
||||||
if (action.name === 'trash') {
|
|
||||||
customAction = (
|
|
||||||
<span key={`action-${action.name}`} className="trash">
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleTrashItem(this.props.item.id)}
|
|
||||||
>
|
|
||||||
{MailPoet.I18n.t('moveToTrash')}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else if (action.refresh) {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
onClick={this.props.onRefreshItems}
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
role="button"
|
|
||||||
tabIndex={index}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
{ action.link(this.props.item) }
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else if (action.link) {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
{ action.link(this.props.item) }
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
customAction = (
|
|
||||||
<span
|
|
||||||
key={`action-${action.name}`}
|
|
||||||
className={action.name}
|
|
||||||
>
|
|
||||||
{(!isFirst) ? ' | ' : ''}
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={
|
|
||||||
(action.onClick !== undefined)
|
|
||||||
? () => action.onClick(this.props.item, this.props.onRefreshItems)
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
>{ action.label }</a>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customAction !== null && isFirst === true) {
|
|
||||||
isFirst = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return customAction;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
itemActions = (
|
|
||||||
<span className="edit">
|
|
||||||
<Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actions;
|
|
||||||
|
|
||||||
if (this.props.group === 'trash') {
|
|
||||||
actions = (
|
|
||||||
<div>
|
|
||||||
<div className="row-actions">
|
|
||||||
<span>
|
|
||||||
<a
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleRestoreItem(this.props.item.id)}
|
|
||||||
>{MailPoet.I18n.t('restore')}</a>
|
|
||||||
</span>
|
|
||||||
{ ' | ' }
|
|
||||||
<span className="delete">
|
|
||||||
<a
|
|
||||||
className="submitdelete"
|
|
||||||
href="javascript:;"
|
|
||||||
onClick={() => this.handleDeleteItem(this.props.item.id)}
|
|
||||||
>{MailPoet.I18n.t('deletePermanently')}</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => this.handleToggleItem(this.props.item.id)}
|
|
||||||
className="toggle-row"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
actions = (
|
|
||||||
<div>
|
|
||||||
<div className="row-actions">
|
|
||||||
{ itemActions }
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
onClick={() => this.handleToggleItem(this.props.item.id)}
|
|
||||||
className="toggle-row"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowClasses = classNames({ 'is-expanded': this.state.expanded });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
|
|
||||||
{ checkbox }
|
|
||||||
{ this.props.onRenderItem(this.props.item, actions) }
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ListingItems extends React.Component {
|
|
||||||
render() {
|
|
||||||
if (this.props.items.length === 0) {
|
|
||||||
let message;
|
|
||||||
if (this.props.loading === true) {
|
|
||||||
message = (this.props.messages.onLoadingItems
|
|
||||||
&& this.props.messages.onLoadingItems(this.props.group))
|
|
||||||
|| MailPoet.I18n.t('loadingItems');
|
|
||||||
} else {
|
|
||||||
message = (this.props.messages.onNoItemsFound
|
|
||||||
&& this.props.messages.onNoItemsFound(this.props.group))
|
|
||||||
|| MailPoet.I18n.t('noItemsFound');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tbody>
|
|
||||||
<tr className="no-items">
|
|
||||||
<td
|
|
||||||
colSpan={
|
|
||||||
this.props.columns.length
|
|
||||||
+ (this.props.is_selectable ? 1 : 0)
|
|
||||||
}
|
|
||||||
className="colspanchange"
|
|
||||||
>
|
|
||||||
{message}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const selectAllClasses = classNames(
|
|
||||||
'mailpoet_select_all',
|
|
||||||
{ mailpoet_hidden: (
|
|
||||||
this.props.selection === false
|
|
||||||
|| (this.props.count <= this.props.limit)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tbody>
|
|
||||||
<tr className={selectAllClasses}>
|
|
||||||
<td colSpan={
|
|
||||||
this.props.columns.length
|
|
||||||
+ (this.props.is_selectable ? 1 : 0)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
(this.props.selection !== 'all')
|
|
||||||
? MailPoet.I18n.t('selectAllLabel')
|
|
||||||
: MailPoet.I18n.t('selectedAllLabel').replace(
|
|
||||||
'%d',
|
|
||||||
this.props.count.toLocaleString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<a
|
|
||||||
onClick={this.props.onSelectAll}
|
|
||||||
href="javascript:;"
|
|
||||||
>{
|
|
||||||
(this.props.selection !== 'all')
|
|
||||||
? MailPoet.I18n.t('selectAllLink')
|
|
||||||
: MailPoet.I18n.t('clearSelection')
|
|
||||||
}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{this.props.items.map((item) => {
|
|
||||||
const renderItem = item;
|
|
||||||
renderItem.id = parseInt(item.id, 10);
|
|
||||||
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
|
|
||||||
let key = `item-${renderItem.id}-${item.id}`;
|
|
||||||
if (typeof this.props.getListingItemKey === 'function') {
|
|
||||||
key = this.props.getListingItemKey(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListingItem
|
|
||||||
columns={this.props.columns}
|
|
||||||
onSelectItem={this.props.onSelectItem}
|
|
||||||
onRenderItem={this.props.onRenderItem}
|
|
||||||
onDeleteItem={this.props.onDeleteItem}
|
|
||||||
onRestoreItem={this.props.onRestoreItem}
|
|
||||||
onTrashItem={this.props.onTrashItem}
|
|
||||||
onRefreshItems={this.props.onRefreshItems}
|
|
||||||
selection={this.props.selection}
|
|
||||||
is_selectable={this.props.is_selectable}
|
|
||||||
item_actions={this.props.item_actions}
|
|
||||||
group={this.props.group}
|
|
||||||
key={key}
|
|
||||||
item={renderItem}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Listing = createReactClass({
|
|
||||||
displayName: 'Listing',
|
displayName: 'Listing',
|
||||||
|
|
||||||
|
/* eslint-disable react/require-default-props */
|
||||||
|
propTypes: {
|
||||||
|
limit: PropTypes.number,
|
||||||
|
sort_by: PropTypes.string,
|
||||||
|
sort_order: PropTypes.string,
|
||||||
|
params: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
auto_refresh: PropTypes.bool,
|
||||||
|
location: PropTypes.shape({
|
||||||
|
pathname: PropTypes.string,
|
||||||
|
}),
|
||||||
|
base_url: PropTypes.string,
|
||||||
|
type: PropTypes.string,
|
||||||
|
endpoint: PropTypes.string.isRequired,
|
||||||
|
afterGetItems: PropTypes.func,
|
||||||
|
messages: PropTypes.shape({
|
||||||
|
onRestore: PropTypes.func,
|
||||||
|
onTrash: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func,
|
||||||
|
}),
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
bulk_actions: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object),
|
||||||
|
search: PropTypes.bool,
|
||||||
|
groups: PropTypes.bool,
|
||||||
|
renderExtraActions: PropTypes.func,
|
||||||
|
onBeforeSelectFilter: PropTypes.func,
|
||||||
|
getListingItemKey: PropTypes.func,
|
||||||
|
},
|
||||||
|
/* eslint-enable react/require-default-props */
|
||||||
|
|
||||||
contextTypes: {
|
contextTypes: {
|
||||||
router: React.PropTypes.object.isRequired,
|
router: React.PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getDefaultProps: () => ({
|
||||||
|
limit: 10,
|
||||||
|
sort_by: null,
|
||||||
|
sort_order: undefined,
|
||||||
|
auto_refresh: false,
|
||||||
|
location: undefined,
|
||||||
|
base_url: '',
|
||||||
|
type: undefined,
|
||||||
|
afterGetItems: undefined,
|
||||||
|
messages: undefined,
|
||||||
|
columns: [],
|
||||||
|
bulk_actions: [],
|
||||||
|
item_actions: [],
|
||||||
|
search: false,
|
||||||
|
groups: false,
|
||||||
|
renderExtraActions: undefined,
|
||||||
|
onBeforeSelectFilter: undefined,
|
||||||
|
getListingItemKey: undefined,
|
||||||
|
}),
|
||||||
|
|
||||||
getInitialState: function getInitialState() {
|
getInitialState: function getInitialState() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -327,64 +91,25 @@ const Listing = createReactClass({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getParam: function getParam(param) {
|
componentDidMount: function componentDidMount() {
|
||||||
const regex = /(.*)\[(.*)\]/;
|
this.isComponentMounted = true;
|
||||||
const matches = regex.exec(param);
|
const params = this.props.params || {};
|
||||||
return [matches[1], matches[2]];
|
this.initWithParams(params);
|
||||||
},
|
|
||||||
|
|
||||||
initWithParams: function initWithParams(params) {
|
if (this.props.auto_refresh) {
|
||||||
const state = this.getInitialState();
|
jQuery(document).on('heartbeat-tick.mailpoet', () => {
|
||||||
// check for url params
|
this.getItems();
|
||||||
if (params.splat) {
|
|
||||||
params.splat.split('/').forEach((param) => {
|
|
||||||
const [key, value] = this.getParam(param);
|
|
||||||
const filters = {};
|
|
||||||
switch (key) {
|
|
||||||
case 'filter':
|
|
||||||
value.split('&').forEach((pair) => {
|
|
||||||
const [k, v] = pair.split('=');
|
|
||||||
filters[k] = v;
|
|
||||||
});
|
|
||||||
|
|
||||||
state.filter = filters;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state[key] = value;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// limit per page
|
|
||||||
if (this.props.limit !== undefined) {
|
|
||||||
state.limit = Math.abs(Number(this.props.limit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by
|
|
||||||
if (state.sort_by === null && this.props.sort_by !== undefined) {
|
|
||||||
state.sort_by = this.props.sort_by;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort order
|
|
||||||
if (state.sort_order === null && this.props.sort_order !== undefined) {
|
|
||||||
state.sort_order = this.props.sort_order;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(state, () => {
|
|
||||||
this.getItems();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getParams: function getParams() {
|
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
|
||||||
// get all route parameters (without the "splat")
|
const params = nextProps.params || {};
|
||||||
const params = _.omit(this.props.params, 'splat');
|
this.initWithParams(params);
|
||||||
// TODO:
|
},
|
||||||
// find a way to set the "type" in the routes definition
|
|
||||||
// so that it appears in `this.props.params`
|
componentWillUnmount: function componentWillUnmount() {
|
||||||
if (this.props.type) {
|
this.isComponentMounted = false;
|
||||||
params.type = this.props.type;
|
|
||||||
}
|
|
||||||
return params;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setParams: function setParams() {
|
setParams: function setParams() {
|
||||||
@@ -451,25 +176,22 @@ const Listing = createReactClass({
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function componentDidMount() {
|
getParams: function getParams() {
|
||||||
this.isComponentMounted = true;
|
// get all route parameters (without the "splat")
|
||||||
const params = this.props.params || {};
|
const params = _.omit(this.props.params, 'splat');
|
||||||
this.initWithParams(params);
|
// TODO:
|
||||||
|
// find a way to set the "type" in the routes definition
|
||||||
if (this.props.auto_refresh) {
|
// so that it appears in `this.props.params`
|
||||||
jQuery(document).on('heartbeat-tick.mailpoet', () => {
|
if (this.props.type) {
|
||||||
this.getItems();
|
params.type = this.props.type;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return params;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function componentWillUnmount() {
|
getParam: function getParam(param) {
|
||||||
this.isComponentMounted = false;
|
const regex = /(.*)\[(.*)\]/;
|
||||||
},
|
const matches = regex.exec(param);
|
||||||
|
return [matches[1], matches[2]];
|
||||||
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
|
|
||||||
const params = nextProps.params || {};
|
|
||||||
this.initWithParams(params);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getItems: function getItems() {
|
getItems: function getItems() {
|
||||||
@@ -525,6 +247,48 @@ const Listing = createReactClass({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initWithParams: function initWithParams(params) {
|
||||||
|
const state = this.getInitialState();
|
||||||
|
// check for url params
|
||||||
|
if (params.splat) {
|
||||||
|
params.splat.split('/').forEach((param) => {
|
||||||
|
const [key, value] = this.getParam(param);
|
||||||
|
const filters = {};
|
||||||
|
switch (key) {
|
||||||
|
case 'filter':
|
||||||
|
value.split('&').forEach((pair) => {
|
||||||
|
const [k, v] = pair.split('=');
|
||||||
|
filters[k] = v;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.filter = filters;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
state[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit per page
|
||||||
|
if (this.props.limit !== undefined) {
|
||||||
|
state.limit = Math.abs(Number(this.props.limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort by
|
||||||
|
if (state.sort_by === null && this.props.sort_by !== undefined) {
|
||||||
|
state.sort_by = this.props.sort_by;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort order
|
||||||
|
if (state.sort_order === null && this.props.sort_order !== undefined) {
|
||||||
|
state.sort_order = this.props.sort_order;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(state, () => {
|
||||||
|
this.getItems();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
handleRestoreItem: function handleRestoreItem(id) {
|
handleRestoreItem: function handleRestoreItem(id) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
|
68
assets/js/src/listing/listing_column.jsx
Normal file
68
assets/js/src/listing/listing_column.jsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class ListingColumn extends React.Component {
|
||||||
|
handleSort = () => {
|
||||||
|
const sortBy = this.props.column.name;
|
||||||
|
const sortOrder = (this.props.column.sorted === 'asc') ? 'desc' : 'asc';
|
||||||
|
this.props.onSort(sortBy, sortOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const classes = classNames(
|
||||||
|
'manage-column',
|
||||||
|
{ 'column-primary': this.props.column.is_primary },
|
||||||
|
{ sortable: this.props.column.sortable },
|
||||||
|
this.props.column.sorted,
|
||||||
|
{ sorted: (this.props.sort_by === this.props.column.name) }
|
||||||
|
);
|
||||||
|
let label;
|
||||||
|
|
||||||
|
if (this.props.column.sortable === true) {
|
||||||
|
label = (
|
||||||
|
<a
|
||||||
|
onClick={this.handleSort}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
|
<span>{ this.props.column.label }</span>
|
||||||
|
<span className="sorting-indicator" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
label = this.props.column.label;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
role="columnheader"
|
||||||
|
className={classes}
|
||||||
|
id={this.props.column.name}
|
||||||
|
scope="col"
|
||||||
|
width={this.props.column.width || null}
|
||||||
|
>{label}</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingColumn.propTypes = {
|
||||||
|
column: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
sorted: PropTypes.string,
|
||||||
|
is_primary: PropTypes.bool,
|
||||||
|
sortable: PropTypes.bool,
|
||||||
|
label: PropTypes.string,
|
||||||
|
width: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
]),
|
||||||
|
}).isRequired,
|
||||||
|
sort_by: PropTypes.string,
|
||||||
|
onSort: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingColumn.defaultProps = {
|
||||||
|
sort_by: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingColumn;
|
215
assets/js/src/listing/listing_item.jsx
Normal file
215
assets/js/src/listing/listing_item.jsx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import { Link } from 'react-router';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
class ListingItem extends React.Component {
|
||||||
|
state = {
|
||||||
|
expanded: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelectItem = (e) => {
|
||||||
|
this.props.onSelectItem(
|
||||||
|
parseInt(e.target.value, 10),
|
||||||
|
e.target.checked
|
||||||
|
);
|
||||||
|
|
||||||
|
return !e.target.checked;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleRestoreItem = (id) => {
|
||||||
|
this.props.onRestoreItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTrashItem = (id) => {
|
||||||
|
this.props.onTrashItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDeleteItem = (id) => {
|
||||||
|
this.props.onDeleteItem(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleToggleItem = () => {
|
||||||
|
this.setState({ expanded: !this.state.expanded });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let checkbox = false;
|
||||||
|
|
||||||
|
if (this.props.is_selectable === true) {
|
||||||
|
checkbox = (
|
||||||
|
<th className="check-column" scope="row">
|
||||||
|
<label className="screen-reader-text" htmlFor={`listing-row-checkbox-${this.props.item.id}`}>{
|
||||||
|
`Select ${this.props.item[this.props.columns[0].name]}`
|
||||||
|
}</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
value={this.props.item.id}
|
||||||
|
checked={
|
||||||
|
this.props.item.selected || this.props.selection === 'all'
|
||||||
|
}
|
||||||
|
onChange={this.handleSelectItem}
|
||||||
|
disabled={this.props.selection === 'all'}
|
||||||
|
id={`listing-row-checkbox-${this.props.item.id}`}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customActions = this.props.item_actions;
|
||||||
|
let itemActions = false;
|
||||||
|
|
||||||
|
if (customActions.length > 0) {
|
||||||
|
let isFirst = true;
|
||||||
|
itemActions = customActions
|
||||||
|
.filter(action => action.display === undefined || action.display(this.props.item))
|
||||||
|
.map((action, index) => {
|
||||||
|
let customAction = null;
|
||||||
|
|
||||||
|
if (action.name === 'trash') {
|
||||||
|
customAction = (
|
||||||
|
<span key={`action-${action.name}`} className="trash">
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleTrashItem(this.props.item.id)}
|
||||||
|
>
|
||||||
|
{MailPoet.I18n.t('moveToTrash')}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (action.refresh) {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
onClick={this.props.onRefreshItems}
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
role="button"
|
||||||
|
tabIndex={index}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
{ action.link(this.props.item) }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else if (action.link) {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
{ action.link(this.props.item) }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
customAction = (
|
||||||
|
<span
|
||||||
|
key={`action-${action.name}`}
|
||||||
|
className={action.name}
|
||||||
|
>
|
||||||
|
{(!isFirst) ? ' | ' : ''}
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={
|
||||||
|
(action.onClick !== undefined)
|
||||||
|
? () => action.onClick(this.props.item, this.props.onRefreshItems)
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
>{ action.label }</a>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customAction !== null && isFirst === true) {
|
||||||
|
isFirst = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return customAction;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
itemActions = (
|
||||||
|
<span className="edit">
|
||||||
|
<Link to={`/edit/${this.props.item.id}`}>{MailPoet.I18n.t('edit')}</Link>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actions;
|
||||||
|
|
||||||
|
if (this.props.group === 'trash') {
|
||||||
|
actions = (
|
||||||
|
<div>
|
||||||
|
<div className="row-actions">
|
||||||
|
<span>
|
||||||
|
<a
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleRestoreItem(this.props.item.id)}
|
||||||
|
>{MailPoet.I18n.t('restore')}</a>
|
||||||
|
</span>
|
||||||
|
{ ' | ' }
|
||||||
|
<span className="delete">
|
||||||
|
<a
|
||||||
|
className="submitdelete"
|
||||||
|
href="javascript:;"
|
||||||
|
onClick={() => this.handleDeleteItem(this.props.item.id)}
|
||||||
|
>{MailPoet.I18n.t('deletePermanently')}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => this.handleToggleItem(this.props.item.id)}
|
||||||
|
className="toggle-row"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
actions = (
|
||||||
|
<div>
|
||||||
|
<div className="row-actions">
|
||||||
|
{ itemActions }
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => this.handleToggleItem(this.props.item.id)}
|
||||||
|
className="toggle-row"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span className="screen-reader-text">{MailPoet.I18n.t('showMoreDetails')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowClasses = classNames({ 'is-expanded': this.state.expanded });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr className={rowClasses} data-automation-id={`listing_item_${this.props.item.id}`}>
|
||||||
|
{ checkbox }
|
||||||
|
{ this.props.onRenderItem(this.props.item, actions) }
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingItem.propTypes = {
|
||||||
|
onSelectItem: PropTypes.func.isRequired,
|
||||||
|
onRestoreItem: PropTypes.func.isRequired,
|
||||||
|
onTrashItem: PropTypes.func.isRequired,
|
||||||
|
onDeleteItem: PropTypes.func.isRequired,
|
||||||
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
|
item: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onRefreshItems: PropTypes.func.isRequired,
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingItem;
|
140
assets/js/src/listing/listing_items.jsx
Normal file
140
assets/js/src/listing/listing_items.jsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import MailPoet from 'mailpoet';
|
||||||
|
import ListingItem from 'listing/listing_item.jsx';
|
||||||
|
|
||||||
|
class ListingItems extends React.Component { // eslint-disable-line react/prefer-stateless-function, max-len
|
||||||
|
render() {
|
||||||
|
if (this.props.items.length === 0) {
|
||||||
|
let message;
|
||||||
|
if (this.props.loading === true) {
|
||||||
|
message = (this.props.messages.onLoadingItems
|
||||||
|
&& this.props.messages.onLoadingItems(this.props.group))
|
||||||
|
|| MailPoet.I18n.t('loadingItems');
|
||||||
|
} else {
|
||||||
|
message = (this.props.messages.onNoItemsFound
|
||||||
|
&& this.props.messages.onNoItemsFound(this.props.group))
|
||||||
|
|| MailPoet.I18n.t('noItemsFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr className="no-items">
|
||||||
|
<td
|
||||||
|
colSpan={
|
||||||
|
this.props.columns.length
|
||||||
|
+ (this.props.is_selectable ? 1 : 0)
|
||||||
|
}
|
||||||
|
className="colspanchange"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const selectAllClasses = classNames(
|
||||||
|
'mailpoet_select_all',
|
||||||
|
{ mailpoet_hidden: (
|
||||||
|
this.props.selection === false
|
||||||
|
|| (this.props.count <= this.props.limit)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tbody>
|
||||||
|
<tr className={selectAllClasses}>
|
||||||
|
<td colSpan={
|
||||||
|
this.props.columns.length
|
||||||
|
+ (this.props.is_selectable ? 1 : 0)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
(this.props.selection !== 'all')
|
||||||
|
? MailPoet.I18n.t('selectAllLabel')
|
||||||
|
: MailPoet.I18n.t('selectedAllLabel').replace(
|
||||||
|
'%d',
|
||||||
|
this.props.count.toLocaleString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<a
|
||||||
|
onClick={this.props.onSelectAll}
|
||||||
|
href="javascript:;"
|
||||||
|
>{
|
||||||
|
(this.props.selection !== 'all')
|
||||||
|
? MailPoet.I18n.t('selectAllLink')
|
||||||
|
: MailPoet.I18n.t('clearSelection')
|
||||||
|
}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{this.props.items.map((item) => {
|
||||||
|
const renderItem = item;
|
||||||
|
renderItem.id = parseInt(item.id, 10);
|
||||||
|
renderItem.selected = (this.props.selected_ids.indexOf(renderItem.id) !== -1);
|
||||||
|
let key = `item-${renderItem.id}-${item.id}`;
|
||||||
|
if (typeof this.props.getListingItemKey === 'function') {
|
||||||
|
key = this.props.getListingItemKey(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListingItem
|
||||||
|
columns={this.props.columns}
|
||||||
|
onSelectItem={this.props.onSelectItem}
|
||||||
|
onRenderItem={this.props.onRenderItem}
|
||||||
|
onDeleteItem={this.props.onDeleteItem}
|
||||||
|
onRestoreItem={this.props.onRestoreItem}
|
||||||
|
onTrashItem={this.props.onTrashItem}
|
||||||
|
onRefreshItems={this.props.onRefreshItems}
|
||||||
|
selection={this.props.selection}
|
||||||
|
is_selectable={this.props.is_selectable}
|
||||||
|
item_actions={this.props.item_actions}
|
||||||
|
group={this.props.group}
|
||||||
|
key={key}
|
||||||
|
item={renderItem}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListingItems.propTypes = {
|
||||||
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
loading: PropTypes.bool.isRequired,
|
||||||
|
messages: PropTypes.shape({
|
||||||
|
onLoadingItems: PropTypes.func,
|
||||||
|
onNoItemsFound: PropTypes.func,
|
||||||
|
}).isRequired,
|
||||||
|
group: PropTypes.string.isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
is_selectable: PropTypes.bool.isRequired,
|
||||||
|
selection: PropTypes.oneOfType([
|
||||||
|
PropTypes.string,
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.bool,
|
||||||
|
]).isRequired,
|
||||||
|
count: PropTypes.number.isRequired,
|
||||||
|
limit: PropTypes.number.isRequired,
|
||||||
|
onSelectAll: PropTypes.func.isRequired,
|
||||||
|
selected_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
|
getListingItemKey: PropTypes.func,
|
||||||
|
onSelectItem: PropTypes.func.isRequired,
|
||||||
|
onRenderItem: PropTypes.func.isRequired,
|
||||||
|
onDeleteItem: PropTypes.func.isRequired,
|
||||||
|
onRestoreItem: PropTypes.func.isRequired,
|
||||||
|
onTrashItem: PropTypes.func.isRequired,
|
||||||
|
onRefreshItems: PropTypes.func.isRequired,
|
||||||
|
item_actions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ListingItems.defaultProps = {
|
||||||
|
getListingItemKey: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = ListingItems;
|
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MailPoet from 'mailpoet';
|
import MailPoet from 'mailpoet';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class ListingPages extends React.Component {
|
class ListingPages extends React.Component {
|
||||||
state = {
|
state = {
|
||||||
@@ -35,9 +36,7 @@ class ListingPages extends React.Component {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
constrainPage = (page) => {
|
getLastPage = () => Math.ceil(this.props.count / this.props.limit);
|
||||||
return Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSetManualPage = (e) => {
|
handleSetManualPage = (e) => {
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
@@ -55,9 +54,7 @@ class ListingPages extends React.Component {
|
|||||||
this.setPage(e.target.value);
|
this.setPage(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
getLastPage = () => {
|
constrainPage = page => Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
|
||||||
return Math.ceil(this.props.count / this.props.limit);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.count === 0) {
|
if (this.props.count === 0) {
|
||||||
@@ -193,4 +190,14 @@ class ListingPages extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListingPages.propTypes = {
|
||||||
|
onSetPage: PropTypes.func.isRequired,
|
||||||
|
page: PropTypes.oneOfType([
|
||||||
|
PropTypes.number,
|
||||||
|
PropTypes.string,
|
||||||
|
]).isRequired,
|
||||||
|
count: PropTypes.number.isRequired,
|
||||||
|
limit: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = ListingPages;
|
module.exports = ListingPages;
|
||||||
|
Reference in New Issue
Block a user