Added ordering capabilities to short URLs list

This commit is contained in:
Alejandro Celaya 2018-07-22 09:37:57 +02:00
parent 6e6e54fa36
commit 86a1cdf4d1
7 changed files with 93 additions and 22 deletions

View file

@ -40,6 +40,7 @@
"postcss-flexbugs-fixes": "3.2.0", "postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8", "postcss-loader": "2.0.8",
"promise": "8.0.1", "promise": "8.0.1",
"qs": "^6.5.2",
"raf": "3.4.0", "raf": "3.4.0",
"ramda": "^0.25.0", "ramda": "^0.25.0",
"react": "^16.3.2", "react": "^16.3.2",

View file

@ -1,5 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import { isEmpty } from 'ramda'; import { isEmpty } from 'ramda';
import qs from 'qs';
export class ShlinkApiClient { export class ShlinkApiClient {
constructor(axios) { constructor(axios) {
@ -21,16 +22,16 @@ export class ShlinkApiClient {
/** /**
* Returns the list of short URLs * Returns the list of short URLs
* @param params * @param options
* @returns {Promise<Array>} * @returns {Promise<Array>}
*/ */
listShortUrls = (params = {}) => { listShortUrls = (options = {}) => {
return this._performRequest('/rest/short-codes', 'GET', params) return this._performRequest('/rest/short-codes', 'GET', options)
.then(resp => resp.data.shortUrls) .then(resp => resp.data.shortUrls)
.catch(e => this._handleAuthError(e, this.listShortUrls, [params])); .catch(e => this._handleAuthError(e, this.listShortUrls, [options]));
}; };
_performRequest = async (url, method = 'GET', params = {}) => { _performRequest = async (url, method = 'GET', params = {}, data = {}) => {
if (isEmpty(this._token)) { if (isEmpty(this._token)) {
this._token = await this._authenticate(); this._token = await this._authenticate();
} }
@ -39,7 +40,9 @@ export class ShlinkApiClient {
method, method,
url: `${this._baseUrl}${url}`, url: `${this._baseUrl}${url}`,
headers: { 'Authorization': `Bearer ${this._token}` }, headers: { 'Authorization': `Bearer ${this._token}` },
params params,
data,
paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' })
}).then(resp => { }).then(resp => {
// Save new token // Save new token
const { authorization = '' } = resp.headers; const { authorization = '' } = resp.headers;

View file

@ -4,6 +4,8 @@ import Moment from 'react-moment';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
import { isEmpty } from 'ramda'; import { isEmpty } from 'ramda';
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp';
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown';
import pieChartIcon from '@fortawesome/fontawesome-free-solid/faChartPie'; import pieChartIcon from '@fortawesome/fontawesome-free-solid/faChartPie';
import pictureIcon from '@fortawesome/fontawesome-free-regular/faImage'; import pictureIcon from '@fortawesome/fontawesome-free-regular/faImage';
import qrIcon from '@fortawesome/fontawesome-free-solid/faQrcode'; import qrIcon from '@fortawesome/fontawesome-free-solid/faQrcode';
@ -15,25 +17,81 @@ import { listShortUrls } from './reducers/shortUrlsList';
import './ShortUrlsList.scss'; import './ShortUrlsList.scss';
export class ShortUrlsList extends React.Component { export class ShortUrlsList extends React.Component {
refreshList = extraParams => {
const { listShortUrls, shortUrlsListParams, match: { params } } = this.props;
listShortUrls(params.serverId, {
...shortUrlsListParams,
...extraParams
});
};
constructor(props) {
super(props);
const orderBy = props.shortUrlsListParams.orderBy;
this.state = {
orderField: orderBy ? Object.keys(orderBy)[0] : 'dateCreated',
orderDir: orderBy ? Object.values(orderBy)[0] : 'ASC',
}
}
componentDidMount() { componentDidMount() {
const { match: { params } } = this.props; const { match: { params } } = this.props;
this.props.listShortUrls(params.serverId, { this.refreshList({ page: params.page });
...this.props.shortUrlsListParams,
page: params.page
});
} }
render() { render() {
const orderBy = field => {
const newOrderDir = this.state.orderField !== field ? 'ASC' : (this.state.orderDir === 'DESC' ? 'ASC' : 'DESC');
this.setState({ orderField: field, orderDir: newOrderDir });
this.refreshList({ orderBy: { [field]: newOrderDir } })
};
const renderOrderIcon = field => {
if (this.state.orderField !== field) {
return null;
}
return (
<FontAwesomeIcon
icon={this.state.orderDir === 'ASC' ? caretUpIcon : caretDownIcon}
className="short-urls-list__header-icon"
/>
);
};
return ( return (
<table className="table table-striped table-hover"> <table className="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Created at</th> <th
<th>Short URL</th> className="short-urls-list__header short-urls-list__header--with-action"
<th>Original URL</th> onClick={() => orderBy('dateCreated')}
<th>Tags</th> >
<th>Visits</th> {renderOrderIcon('dateCreated')}
<th>&nbsp;</th> Created at
</th>
<th
className="short-urls-list__header short-urls-list__header--with-action"
onClick={() => orderBy('shortCode')}
>
{renderOrderIcon('shortCode')}
Short URL
</th>
<th
className="short-urls-list__header short-urls-list__header--with-action"
onClick={() => orderBy('originalUrl')}
>
{renderOrderIcon('originalUrl')}
Original URL
</th>
<th className="short-urls-list__header">Tags</th>
<th
className="short-urls-list__header short-urls-list__header--with-action"
onClick={() => orderBy('visits')}
>
<span className="nowrap">{renderOrderIcon('visits')} Visits</span>
</th>
<th className="short-urls-list__header">&nbsp;</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -4,6 +4,14 @@
vertical-align: middle !important; vertical-align: middle !important;
} }
.short-urls-list__header--with-action {
cursor: pointer;
}
.short-urls-list__header-icon {
margin-right: 5px;
}
.short-urls-list__dropdown-toggle:before { .short-urls-list__dropdown-toggle:before {
display: none !important; display: none !important;
} }

View file

@ -32,7 +32,7 @@ export const listShortUrls = (serverId, params = {}) => {
ShlinkApiClient.setConfig(selectedServer); ShlinkApiClient.setConfig(selectedServer);
const shortUrls = await ShlinkApiClient.listShortUrls(params); const shortUrls = await ShlinkApiClient.listShortUrls(params);
dispatch({ type: LIST_SHORT_URLS, shortUrls, selectedServer }); dispatch({ type: LIST_SHORT_URLS, shortUrls, selectedServer, params });
}; };
}; };

View file

@ -1,8 +1,9 @@
import { UPDATE_SHORT_URLS_LIST } from './shortUrlsList'; import { UPDATE_SHORT_URLS_LIST, LIST_SHORT_URLS } from './shortUrlsList';
export default function reducer(state = { page: 1 }, action) { export default function reducer(state = { page: 1 }, action) {
switch (action.type) { switch (action.type) {
case UPDATE_SHORT_URLS_LIST: case UPDATE_SHORT_URLS_LIST:
case LIST_SHORT_URLS:
return { ...state, ...action.params }; return { ...state, ...action.params };
default: default:
return state; return state;

View file

@ -5975,6 +5975,10 @@ qs@6.5.1:
version "6.5.1" version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
qs@^6.5.2, qs@~6.5.1:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
qs@~6.3.0: qs@~6.3.0:
version "6.3.2" version "6.3.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c"
@ -5983,10 +5987,6 @@ qs@~6.4.0:
version "6.4.0" version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
qs@~6.5.1:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
query-string@^4.1.0: query-string@^4.1.0:
version "4.3.4" version "4.3.4"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"