From 368de2b4c710e3249925177e093a4e86dacb3292 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 28 Oct 2018 22:54:08 +0100 Subject: [PATCH] Added order control to countries graph --- src/short-urls/ShortUrlsList.js | 5 +-- src/utils/SortingDropdown.js | 71 ++++++++++++++++++++---------- src/utils/SortingDropdown.scss | 8 ++++ src/visits/CountriesGraph.js | 49 +++++++++++++++++++++ src/visits/GraphCard.js | 5 ++- src/visits/ShortUrlVisits.js | 3 +- test/utils/SortingDropdown.test.js | 7 +-- test/visits/ShortUrlVisits.test.js | 5 ++- 8 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 src/visits/CountriesGraph.js diff --git a/src/short-urls/ShortUrlsList.js b/src/short-urls/ShortUrlsList.js index f9cb90dd..945923a1 100644 --- a/src/short-urls/ShortUrlsList.js +++ b/src/short-urls/ShortUrlsList.js @@ -43,10 +43,7 @@ export class ShortUrlsListComponent extends React.Component { }); }; handleOrderBy = (orderField, orderDir) => { - this.setState({ - orderDir, - orderField: orderDir !== undefined ? orderField : undefined, - }); + this.setState({ orderField, orderDir }); this.refreshList({ orderBy: { [orderField]: orderDir } }); }; orderByColumn = (columnName) => () => diff --git a/src/utils/SortingDropdown.js b/src/utils/SortingDropdown.js index 35233744..46b6462b 100644 --- a/src/utils/SortingDropdown.js +++ b/src/utils/SortingDropdown.js @@ -5,39 +5,64 @@ import PropTypes from 'prop-types'; import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp'; import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown'; +import classNames from 'classnames'; import { determineOrderDir } from '../utils/utils'; import './SortingDropdown.scss'; const propTypes = { - items: PropTypes.object, + items: PropTypes.object.isRequired, orderField: PropTypes.string, orderDir: PropTypes.oneOf([ 'ASC', 'DESC' ]), - onChange: PropTypes.func, + onChange: PropTypes.func.isRequired, + isButton: PropTypes.bool, + right: PropTypes.bool, +}; +const defaultProps = { + isButton: true, + right: false, }; -const SortingDropdown = ({ items, orderField, orderDir, onChange }) => ( - - Order by - - {toPairs(items).map(([ fieldKey, fieldValue ]) => ( - onChange(fieldKey, determineOrderDir(fieldKey, orderField, orderDir))} - > - {fieldValue} - {orderField === fieldKey && ( - - )} +const SortingDropdown = ({ items, orderField, orderDir, onChange, isButton, right }) => { + const handleItemClick = (fieldKey) => () => { + const newOrderDir = determineOrderDir(fieldKey, orderField, orderDir); + + onChange(newOrderDir ? fieldKey : undefined, newOrderDir); + }; + + return ( + + + Order by + + + {toPairs(items).map(([ fieldKey, fieldValue ]) => ( + + {fieldValue} + {orderField === fieldKey && ( + + )} + + ))} + + onChange()}> + Clear selection - ))} - - -); + + + ); +}; SortingDropdown.propTypes = propTypes; +SortingDropdown.defaultProps = defaultProps; export default SortingDropdown; diff --git a/src/utils/SortingDropdown.scss b/src/utils/SortingDropdown.scss index 6c5ac887..5f9c624e 100644 --- a/src/utils/SortingDropdown.scss +++ b/src/utils/SortingDropdown.scss @@ -2,7 +2,15 @@ width: 100%; } +.sorting-dropdown__menu--link.sorting-dropdown__menu--link { + min-width: 11rem; +} + .sorting-dropdown__sort-icon { margin: 3.5px 0 0; float: right; } + +.sorting-dropdown__paddingless.sorting-dropdown__paddingless { + padding: 0; +} diff --git a/src/visits/CountriesGraph.js b/src/visits/CountriesGraph.js new file mode 100644 index 00000000..f4f276cd --- /dev/null +++ b/src/visits/CountriesGraph.js @@ -0,0 +1,49 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { fromPairs, head, keys, prop, reverse, sortBy, toPairs } from 'ramda'; +import SortingDropdown from '../utils/SortingDropdown'; +import GraphCard from './GraphCard'; + +export default class CountriesGraph extends React.Component { + static propTypes = { + stats: PropTypes.any, + }; + + state = { + orderField: undefined, + orderDir: undefined, + }; + + render() { + const items = { + name: 'Country name', + amount: 'Visits amount', + }; + const { stats } = this.props; + const sortStats = () => { + if (!this.state.orderField) { + return stats; + } + + const sortedPairs = sortBy(prop(this.state.orderField === head(keys(items)) ? 0 : 1), toPairs(stats)); + + return fromPairs(this.state.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs)); + }; + + return ( + + Countries +
+ this.setState({ orderField, orderDir })} + /> +
+
+ ); + } +} diff --git a/src/visits/GraphCard.js b/src/visits/GraphCard.js index 4351fefa..b6e8a914 100644 --- a/src/visits/GraphCard.js +++ b/src/visits/GraphCard.js @@ -6,6 +6,7 @@ import { keys, values } from 'ramda'; const propTypes = { title: PropTypes.string, + children: PropTypes.node, isBarChart: PropTypes.bool, stats: PropTypes.object, matchMedia: PropTypes.func, @@ -80,9 +81,9 @@ const renderGraph = (title, isBarChart, stats, matchMedia) => { return ; }; -const GraphCard = ({ title, isBarChart, stats, matchMedia }) => ( +const GraphCard = ({ title, children, isBarChart, stats, matchMedia }) => ( - {title} + {children || title} {renderGraph(title, isBarChart, stats, matchMedia)} ); diff --git a/src/visits/ShortUrlVisits.js b/src/visits/ShortUrlVisits.js index 72d162cb..f65982d7 100644 --- a/src/visits/ShortUrlVisits.js +++ b/src/visits/ShortUrlVisits.js @@ -7,6 +7,7 @@ import { Card } from 'reactstrap'; import PropTypes from 'prop-types'; import DateInput from '../common/DateInput'; import MutedMessage from '../utils/MuttedMessage'; +import CountriesGraph from './CountriesGraph'; import { getShortUrlVisits, shortUrlVisitsType } from './reducers/shortUrlVisits'; import { processBrowserStats, @@ -95,7 +96,7 @@ export class ShortUrlsVisitsComponent extends React.Component {
- +
diff --git a/test/utils/SortingDropdown.test.js b/test/utils/SortingDropdown.test.js index e0e73fc3..c93ffd79 100644 --- a/test/utils/SortingDropdown.test.js +++ b/test/utils/SortingDropdown.test.js @@ -1,7 +1,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { DropdownItem } from 'reactstrap'; -import { values } from 'ramda'; +import { identity, values } from 'ramda'; import FontAwesomeIcon from '@fortawesome/react-fontawesome'; import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown'; import * as sinon from 'sinon'; @@ -15,7 +15,7 @@ describe('', () => { baz: 'Hello World', }; const createWrapper = (props) => { - wrapper = shallow(); + wrapper = shallow(); return wrapper; }; @@ -26,8 +26,9 @@ describe('', () => { const wrapper = createWrapper(); const dropdownItems = wrapper.find(DropdownItem); const secondIndex = 2; + const clearItemsCount = 2; - expect(dropdownItems).toHaveLength(values(items).length); + expect(dropdownItems).toHaveLength(values(items).length + clearItemsCount); expect(dropdownItems.at(0).html()).toContain('Foo'); expect(dropdownItems.at(1).html()).toContain('Bar'); expect(dropdownItems.at(secondIndex).html()).toContain('Hello World'); diff --git a/test/visits/ShortUrlVisits.test.js b/test/visits/ShortUrlVisits.test.js index 56837968..8b719bd4 100644 --- a/test/visits/ShortUrlVisits.test.js +++ b/test/visits/ShortUrlVisits.test.js @@ -7,6 +7,7 @@ import { ShortUrlsVisitsComponent as ShortUrlsVisits } from '../../src/visits/Sh import MutedMessage from '../../src/utils/MuttedMessage'; import GraphCard from '../../src/visits/GraphCard'; import DateInput from '../../src/common/DateInput'; +import CountriesGraph from '../../src/visits/CountriesGraph'; describe('', () => { let wrapper; @@ -69,9 +70,11 @@ describe('', () => { it('renders all graphics when visits are properly loaded', () => { const wrapper = createComponent({ loading: false, error: false, visits: [{}, {}, {}] }); const graphs = wrapper.find(GraphCard); - const expectedGraphsCount = 4; + const countriesGraphs = wrapper.find(CountriesGraph); + const expectedGraphsCount = 3; expect(graphs).toHaveLength(expectedGraphsCount); + expect(countriesGraphs).toHaveLength(1); }); it('reloads visits when selected dates change', () => {