Fix listing related eslint PropTypes violations

This commit is contained in:
Tautvidas Sipavičius
2018-11-01 00:39:24 +02:00
parent 103714a035
commit 70743b98b6
10 changed files with 666 additions and 462 deletions

View File

@@ -123,7 +123,10 @@ FormField.propTypes = {
field: PropTypes.shape({
name: PropTypes.string.isRequired,
values: PropTypes.object,
tip: PropTypes.string,
tip: PropTypes.oneOfType([
PropTypes.array,
PropTypes.string,
]),
label: PropTypes.string,
fields: PropTypes.array,
description: PropTypes.string,

View File

@@ -26,6 +26,11 @@ class Form extends React.Component {
id: '',
onSubmit: undefined,
automationId: '',
messages: {
onUpdate: () => { /* no-op */ },
onCreate: () => { /* no-op */ },
},
endpoint: undefined,
};
state = {
@@ -67,6 +72,7 @@ class Form extends React.Component {
loadItem = (id) => {
this.setState({ loading: true });
if (!this.props.endpoint) return;
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint,
@@ -120,6 +126,8 @@ class Form extends React.Component {
item.id = this.props.params.id;
}
if (!this.props.endpoint) return;
MailPoet.Ajax.post({
api_version: window.mailpoet_api_version,
endpoint: this.props.endpoint,
@@ -257,14 +265,14 @@ Form.propTypes = {
}).isRequired,
item: PropTypes.object, // eslint-disable-line react/forbid-prop-types
errors: PropTypes.arrayOf(PropTypes.object),
endpoint: PropTypes.string.isRequired,
endpoint: PropTypes.string,
fields: PropTypes.arrayOf(PropTypes.object),
messages: PropTypes.shape({
onUpdate: PropTypes.func,
onCreate: PropTypes.func,
}).isRequired,
loading: PropTypes.bool,
children: PropTypes.element,
children: PropTypes.array, // eslint-disable-line react/forbid-prop-types
id: PropTypes.string,
automationId: PropTypes.string,
beforeFormContent: PropTypes.func,

View File

@@ -1,5 +1,6 @@
import React from 'react';
import MailPoet from 'mailpoet';
import PropTypes from 'prop-types';
class ListingBulkActions extends React.Component {
state = {
@@ -7,20 +8,16 @@ class ListingBulkActions extends React.Component {
extra: false,
};
handleChangeAction = (e) => {
this.setState({
action: e.target.value,
extra: false,
}, () => {
const action = this.getSelectedAction();
getSelectedAction = () => {
const selectedAction = this.action.value;
if (selectedAction.length > 0) {
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
// action on select callback
if (action !== null && action.onSelect !== undefined) {
this.setState({
extra: action.onSelect(e),
});
if (action.length > 0) {
return action[0];
}
});
}
return null;
};
handleApplyAction = (e) => {
@@ -60,16 +57,20 @@ class ListingBulkActions extends React.Component {
});
};
getSelectedAction = () => {
const selectedAction = this.action.value;
if (selectedAction.length > 0) {
const action = this.props.bulk_actions.filter(act => (act.name === selectedAction));
handleChangeAction = (e) => {
this.setState({
action: e.target.value,
extra: false,
}, () => {
const action = this.getSelectedAction();
if (action.length > 0) {
return action[0];
// action on select callback
if (action !== null && action.onSelect !== undefined) {
this.setState({
extra: action.onSelect(e),
});
}
}
return null;
});
};
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;

View File

@@ -1,34 +1,9 @@
import React from 'react';
import jQuery from 'jquery';
import MailPoet from 'mailpoet';
import PropTypes from 'prop-types';
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() {
const selectedFilters = this.props.filter;
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() {
const filters = this.props.filters;
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;

View File

@@ -1,13 +1,10 @@
import MailPoet from 'mailpoet';
import React from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import ListingColumn from './listing_column.jsx';
class ListingHeader extends React.Component {
handleSelectItems = () => {
return this.props.onSelectItems(
this.toggle.checked
);
};
handleSelectItems = () => this.props.onSelectItems(this.toggle.checked);
render() {
const columns = this.props.columns.map((column, index) => {
@@ -57,47 +54,23 @@ class ListingHeader extends React.Component {
}
}
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);
};
ListingHeader.propTypes = {
onSelectItems: PropTypes.func.isRequired,
onSort: PropTypes.func.isRequired,
columns: PropTypes.arrayOf(PropTypes.object),
sort_by: PropTypes.string,
sort_order: PropTypes.string,
is_selectable: PropTypes.bool.isRequired,
selection: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]).isRequired,
};
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>
);
}
}
ListingHeader.defaultProps = {
columns: [],
sort_by: undefined,
sort_order: 'desc',
};
module.exports = ListingHeader;

View File

@@ -1,312 +1,76 @@
import MailPoet from 'mailpoet';
import jQuery from 'jquery';
import React from 'react';
import createReactClass from 'create-react-class';
import _ from 'underscore';
import { Link } from 'react-router';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import MailPoet from 'mailpoet';
import ListingBulkActions from 'listing/bulk_actions.jsx';
import ListingHeader from 'listing/header.jsx';
import ListingPages from 'listing/pages.jsx';
import ListingSearch from 'listing/search.jsx';
import ListingGroups from 'listing/groups.jsx';
import ListingFilters from 'listing/filters.jsx';
import ListingItems from 'listing/listing_items.jsx';
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>
);
}
}
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()
)
}
&nbsp;
<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({
const Listing = createReactClass({ // eslint-disable-line react/prefer-es6-class
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: {
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() {
return {
loading: false,
@@ -327,64 +91,25 @@ const Listing = createReactClass({
};
},
getParam: function getParam(param) {
const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param);
return [matches[1], matches[2]];
},
componentDidMount: function componentDidMount() {
this.isComponentMounted = true;
const params = this.props.params || {};
this.initWithParams(params);
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;
}
if (this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', () => {
this.getItems();
});
}
// 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() {
// get all route parameters (without the "splat")
const params = _.omit(this.props.params, 'splat');
// TODO:
// find a way to set the "type" in the routes definition
// so that it appears in `this.props.params`
if (this.props.type) {
params.type = this.props.type;
}
return params;
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
const params = nextProps.params || {};
this.initWithParams(params);
},
componentWillUnmount: function componentWillUnmount() {
this.isComponentMounted = false;
},
setParams: function setParams() {
@@ -451,25 +176,22 @@ const Listing = createReactClass({
return ret;
},
componentDidMount: function componentDidMount() {
this.isComponentMounted = true;
const params = this.props.params || {};
this.initWithParams(params);
if (this.props.auto_refresh) {
jQuery(document).on('heartbeat-tick.mailpoet', () => {
this.getItems();
});
getParams: function getParams() {
// get all route parameters (without the "splat")
const params = _.omit(this.props.params, 'splat');
// TODO:
// find a way to set the "type" in the routes definition
// so that it appears in `this.props.params`
if (this.props.type) {
params.type = this.props.type;
}
return params;
},
componentWillUnmount: function componentWillUnmount() {
this.isComponentMounted = false;
},
componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
const params = nextProps.params || {};
this.initWithParams(params);
getParam: function getParam(param) {
const regex = /(.*)\[(.*)\]/;
const matches = regex.exec(param);
return [matches[1], matches[2]];
},
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) {
this.setState({
loading: true,

View 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;

View 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;

View 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()
)
}
&nbsp;
<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;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import MailPoet from 'mailpoet';
import PropTypes from 'prop-types';
class ListingPages extends React.Component {
state = {
@@ -35,9 +36,7 @@ class ListingPages extends React.Component {
);
};
constrainPage = (page) => {
return Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
};
getLastPage = () => Math.ceil(this.props.count / this.props.limit);
handleSetManualPage = (e) => {
if (e.which === 13) {
@@ -55,9 +54,7 @@ class ListingPages extends React.Component {
this.setPage(e.target.value);
};
getLastPage = () => {
return Math.ceil(this.props.count / this.props.limit);
};
constrainPage = page => Math.min(Math.max(1, Math.abs(Number(page))), this.getLastPage());
render() {
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;