diff --git a/package.json b/package.json index d11e57ab..5b690609 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "postcss-flexbugs-fixes": "3.2.0", "postcss-loader": "2.0.8", "promise": "8.0.1", + "qs": "^6.5.2", "raf": "3.4.0", "ramda": "^0.25.0", "react": "^16.3.2", diff --git a/src/api/ShlinkApiClient.js b/src/api/ShlinkApiClient.js index 494bcc89..50a7e989 100644 --- a/src/api/ShlinkApiClient.js +++ b/src/api/ShlinkApiClient.js @@ -1,5 +1,6 @@ import axios from 'axios'; import { isEmpty } from 'ramda'; +import qs from 'qs'; export class ShlinkApiClient { constructor(axios) { @@ -21,16 +22,16 @@ export class ShlinkApiClient { /** * Returns the list of short URLs - * @param params + * @param options * @returns {Promise} */ - listShortUrls = (params = {}) => { - return this._performRequest('/rest/short-codes', 'GET', params) + listShortUrls = (options = {}) => { + return this._performRequest('/rest/short-codes', 'GET', options) .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)) { this._token = await this._authenticate(); } @@ -39,7 +40,9 @@ export class ShlinkApiClient { method, url: `${this._baseUrl}${url}`, headers: { 'Authorization': `Bearer ${this._token}` }, - params + params, + data, + paramsSerializer: params => qs.stringify(params, { arrayFormat: 'brackets' }) }).then(resp => { // Save new token const { authorization = '' } = resp.headers; diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js index 2ff1b20a..9ad93443 100644 --- a/src/short-urls/ShortUrlsList.js +++ b/src/short-urls/ShortUrlsList.js @@ -4,6 +4,8 @@ import Moment from 'react-moment'; import { connect } from 'react-redux'; import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; 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 pictureIcon from '@fortawesome/fontawesome-free-regular/faImage'; import qrIcon from '@fortawesome/fontawesome-free-solid/faQrcode'; @@ -15,25 +17,81 @@ import { listShortUrls } from './reducers/shortUrlsList'; import './ShortUrlsList.scss'; 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() { const { match: { params } } = this.props; - this.props.listShortUrls(params.serverId, { - ...this.props.shortUrlsListParams, - page: params.page - }); + this.refreshList({ page: params.page }); } 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 ( + + ); + }; + return ( - - - - - - + + + + + + diff --git a/src/short-urls/ShortUrlsList.scss b/src/short-urls/ShortUrlsList.scss index 3f95358b..76149e83 100644 --- a/src/short-urls/ShortUrlsList.scss +++ b/src/short-urls/ShortUrlsList.scss @@ -4,6 +4,14 @@ 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 { display: none !important; } diff --git a/src/short-urls/reducers/shortUrlsList.js b/src/short-urls/reducers/shortUrlsList.js index 26a5ffe4..c5f4b2fc 100644 --- a/src/short-urls/reducers/shortUrlsList.js +++ b/src/short-urls/reducers/shortUrlsList.js @@ -32,7 +32,7 @@ export const listShortUrls = (serverId, params = {}) => { ShlinkApiClient.setConfig(selectedServer); const shortUrls = await ShlinkApiClient.listShortUrls(params); - dispatch({ type: LIST_SHORT_URLS, shortUrls, selectedServer }); + dispatch({ type: LIST_SHORT_URLS, shortUrls, selectedServer, params }); }; }; diff --git a/src/short-urls/reducers/shortUrlsListParams.js b/src/short-urls/reducers/shortUrlsListParams.js index 9c3e790d..0aafd86e 100644 --- a/src/short-urls/reducers/shortUrlsListParams.js +++ b/src/short-urls/reducers/shortUrlsListParams.js @@ -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) { switch (action.type) { case UPDATE_SHORT_URLS_LIST: + case LIST_SHORT_URLS: return { ...state, ...action.params }; default: return state; diff --git a/yarn.lock b/yarn.lock index 49f362d0..1d32185d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5975,6 +5975,10 @@ qs@6.5.1: version "6.5.1" 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: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -5983,10 +5987,6 @@ qs@~6.4.0: version "6.4.0" 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: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
Created atShort URLOriginal URLTagsVisits  orderBy('dateCreated')} + > + {renderOrderIcon('dateCreated')} + Created at + orderBy('shortCode')} + > + {renderOrderIcon('shortCode')} + Short URL + orderBy('originalUrl')} + > + {renderOrderIcon('originalUrl')} + Original URL + Tags orderBy('visits')} + > + {renderOrderIcon('visits')} Visits +