From b60908a5e9c55e07d27c86d158ca88a2a21a1281 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 14 Jan 2020 19:59:25 +0100 Subject: [PATCH] Added filtering by date to short URLs list --- public/index.html | 8 ++-- src/short-urls/SearchBar.js | 31 +++++++++++--- src/short-urls/ShortUrlsList.js | 6 ++- src/short-urls/helpers/ShortUrlsRow.scss | 4 -- .../reducers/shortUrlsListParams.js | 3 ++ src/utils/DateInput.js | 3 +- src/utils/DateRangeRow.js | 40 +++++++++++++++++++ .../DateRangeRow.scss} | 2 +- src/utils/SearchField.js | 4 +- src/utils/services/ShlinkApiClient.js | 9 +++-- src/utils/utils.js | 2 + src/visits/ShortUrlVisits.js | 37 +++++------------ 12 files changed, 99 insertions(+), 50 deletions(-) create mode 100644 src/utils/DateRangeRow.js rename src/{visits/ShortUrlVisits.scss => utils/DateRangeRow.scss} (72%) diff --git a/public/index.html b/public/index.html index e13f0465..0f50c941 100644 --- a/public/index.html +++ b/public/index.html @@ -11,12 +11,12 @@ --> - //FavIcon itself + - //Apple Touch + @@ -44,7 +44,7 @@ - //Normal + @@ -72,7 +72,7 @@ - //MS + diff --git a/src/short-urls/SearchBar.js b/src/short-urls/SearchBar.js index c6537535..f831b783 100644 --- a/src/short-urls/SearchBar.js +++ b/src/short-urls/SearchBar.js @@ -1,10 +1,13 @@ import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React from 'react'; -import { isEmpty } from 'ramda'; +import { isEmpty, pipe } from 'ramda'; import PropTypes from 'prop-types'; +import moment from 'moment'; import SearchField from '../utils/SearchField'; import Tag from '../tags/helpers/Tag'; +import DateRangeRow from '../utils/DateRangeRow'; +import { formatDate } from '../utils/utils'; import { shortUrlsListParamsType } from './reducers/shortUrlsListParams'; import './SearchBar.scss'; @@ -13,19 +16,35 @@ const propTypes = { shortUrlsListParams: shortUrlsListParamsType, }; +const dateOrUndefined = (date) => date ? moment(date) : undefined; + const SearchBar = (colorGenerator) => { const SearchBar = ({ listShortUrls, shortUrlsListParams }) => { const selectedTags = shortUrlsListParams.tags || []; + const setDate = (dateName) => pipe( + formatDate(), + (date) => listShortUrls({ ...shortUrlsListParams, [dateName]: date }) + ); return ( -
- listShortUrls({ ...shortUrlsListParams, searchTerm }) - } +
+ listShortUrls({ ...shortUrlsListParams, searchTerm }) + } /> +
+ +
+ {!isEmpty(selectedTags) && ( -

+

  {selectedTags.map((tag) => ( diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js index 09746f36..ffc9cd21 100644 --- a/src/short-urls/ShortUrlsList.js +++ b/src/short-urls/ShortUrlsList.js @@ -40,12 +40,15 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon ...extraParams, }); }; + handleOrderBy = (orderField, orderDir) => { this.setState({ orderField, orderDir }); this.refreshList({ orderBy: { [orderField]: orderDir } }); }; + orderByColumn = (columnName) => () => this.handleOrderBy(columnName, determineOrderDir(columnName, this.state.orderField, this.state.orderDir)); + renderOrderIcon = (field) => { if (this.state.orderField !== field) { return null; @@ -77,8 +80,9 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon componentDidMount() { const { match: { params }, location, shortUrlsListParams } = this.props; const query = qs.parse(location.search, { ignoreQueryPrefix: true }); + const tags = query.tag ? [ query.tag ] : shortUrlsListParams.tags; - this.refreshList({ page: params.page, tags: query.tag ? [ query.tag ] : shortUrlsListParams.tags }); + this.refreshList({ page: params.page, tags }); } componentWillUnmount() { diff --git a/src/short-urls/helpers/ShortUrlsRow.scss b/src/short-urls/helpers/ShortUrlsRow.scss index 7b53362e..da6e8f86 100644 --- a/src/short-urls/helpers/ShortUrlsRow.scss +++ b/src/short-urls/helpers/ShortUrlsRow.scss @@ -51,7 +51,3 @@ right: calc(100% + 10px); } } - -.short-urls-row__max-visits-control { - cursor: help; -} diff --git a/src/short-urls/reducers/shortUrlsListParams.js b/src/short-urls/reducers/shortUrlsListParams.js index 14962362..6a6aa5e2 100644 --- a/src/short-urls/reducers/shortUrlsListParams.js +++ b/src/short-urls/reducers/shortUrlsListParams.js @@ -8,6 +8,9 @@ export const shortUrlsListParamsType = PropTypes.shape({ page: PropTypes.string, tags: PropTypes.arrayOf(PropTypes.string), searchTerm: PropTypes.string, + startDate: PropTypes.string, + endDate: PropTypes.string, + orderBy: PropTypes.object, }); const initialState = { page: '1' }; diff --git a/src/utils/DateInput.js b/src/utils/DateInput.js index 6d1ba2f2..382ae753 100644 --- a/src/utils/DateInput.js +++ b/src/utils/DateInput.js @@ -4,6 +4,7 @@ import DatePicker from 'react-datepicker'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCalendarAlt as calendarIcon } from '@fortawesome/free-regular-svg-icons'; import * as PropTypes from 'prop-types'; +import classNames from 'classnames'; import './DateInput.scss'; const propTypes = { @@ -21,7 +22,7 @@ const DateInput = (props) => {
( +
+
+ +
+
+ +
+
+); + +DateRangeRow.propTypes = propTypes; + +export default DateRangeRow; diff --git a/src/visits/ShortUrlVisits.scss b/src/utils/DateRangeRow.scss similarity index 72% rename from src/visits/ShortUrlVisits.scss rename to src/utils/DateRangeRow.scss index c75b3b78..bcd94e78 100644 --- a/src/visits/ShortUrlVisits.scss +++ b/src/utils/DateRangeRow.scss @@ -1,6 +1,6 @@ @import '../utils/base'; -.short-url-visits__date-input { +.date-range-row__date-input { @media (max-width: $smMax) { margin-top: .5rem; } diff --git a/src/utils/SearchField.js b/src/utils/SearchField.js index 4cc32acb..a9e4a527 100644 --- a/src/utils/SearchField.js +++ b/src/utils/SearchField.js @@ -2,7 +2,7 @@ import React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSearch as searchIcon } from '@fortawesome/free-solid-svg-icons'; import PropTypes from 'prop-types'; -import classnames from 'classnames'; +import classNames from 'classnames'; import './SearchField.scss'; const DEFAULT_SEARCH_INTERVAL = 500; @@ -44,7 +44,7 @@ export default class SearchField extends React.Component { const { className, placeholder } = this.props; return ( -
+
- this._performRequest('/short-urls', 'GET', options) - .then((resp) => resp.data.shortUrls); + listShortUrls = pipe( + (options = {}) => reject(isNil, options), + (options = {}) => this._performRequest('/short-urls', 'GET', options).then((resp) => resp.data.shortUrls) + ); createShortUrl = (options) => { const filteredOptions = reject((value) => isEmpty(value) || isNil(value), options); diff --git a/src/utils/utils.js b/src/utils/utils.js index 304116fe..917873f9 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -68,3 +68,5 @@ export const versionIsValidSemVer = (version) => { return false; } }; + +export const formatDate = (format = 'YYYY-MM-DD') => (date) => date && date.format ? date.format(format) : date; diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index bd291590..f1104446 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -4,14 +4,14 @@ import { isEmpty, mapObjIndexed, values } from 'ramda'; import React from 'react'; import { Card } from 'reactstrap'; import PropTypes from 'prop-types'; -import DateInput from '../utils/DateInput'; +import DateRangeRow from '../utils/DateRangeRow'; import MutedMessage from '../utils/MuttedMessage'; +import { formatDate } from '../utils/utils'; import SortableBarGraph from './SortableBarGraph'; import { shortUrlVisitsType } from './reducers/shortUrlVisits'; import VisitsHeader from './VisitsHeader'; import GraphCard from './GraphCard'; import { shortUrlDetailType } from './reducers/shortUrlDetail'; -import './ShortUrlVisits.scss'; const ShortUrlVisits = ( { processStatsFromVisits }, @@ -32,10 +32,7 @@ const ShortUrlVisits = ( loadVisits = () => { const { match: { params }, getShortUrlVisits } = this.props; const { shortCode } = params; - const dates = mapObjIndexed( - (value) => value && value.format ? value.format('YYYY-MM-DD') : value, - this.state - ); + const dates = mapObjIndexed(formatDate(), this.state); const { startDate, endDate } = dates; // While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calcs @@ -131,33 +128,19 @@ const ShortUrlVisits = (
); }; + const setDate = (dateField) => (date) => this.setState({ [dateField]: date }, this.loadVisits); return (
-
-
- this.setState({ startDate: date }, this.loadVisits)} - /> -
-
- this.setState({ endDate: date }, this.loadVisits)} - /> -
-
+