mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-31 21:38:19 +03:00
Added ordering capabilities to short URLs list
This commit is contained in:
parent
6e6e54fa36
commit
86a1cdf4d1
7 changed files with 93 additions and 22 deletions
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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> </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"> </th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue