Completed
Push — master ( 158ed8...34f194 )
by Alejandro
04:53 queued 02:19
created

src/visits/SortableBarGraph.js   A

Complexity

Total Complexity 11
Complexity/F 2.75

Size

Lines of Code 125
Function Count 4

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 82.76%

Importance

Changes 0
Metric Value
wmc 11
eloc 104
mnd 7
bc 7
fnc 4
dl 0
loc 125
ccs 24
cts 29
cp 0.8276
rs 10
bpm 1.75
cpm 2.75
noi 0
c 0
b 0
f 0

4 Functions

Rating   Name   Duplication   Size   Complexity  
A SortableBarGraph.renderPagination 0 18 1
B SortableBarGraph.determineStats 0 22 6
A SortableBarGraph.determineCurrentPagePairs 0 12 2
A SortableBarGraph.render 0 37 2
1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import { fromPairs, head, keys, pipe, prop, reverse, sortBy, splitEvery, toLower, toPairs, type } from 'ramda';
4
import { Pagination, PaginationItem, PaginationLink } from 'reactstrap';
5
import SortingDropdown from '../utils/SortingDropdown';
6
import PaginationDropdown from '../utils/PaginationDropdown';
7
import { rangeOf, roundTen } from '../utils/utils';
8
import GraphCard from './GraphCard';
9
10 2
const { max } = Math;
11 8
const toLowerIfString = (value) => type(value) === 'String' ? toLower(value) : value;
12 320
const pickValueFromPair = ([ , value ]) => value;
13
14
export default class SortableBarGraph extends React.Component {
15
  static propTypes = {
16
    stats: PropTypes.object.isRequired,
17
    title: PropTypes.string.isRequired,
18
    sortingItems: PropTypes.object.isRequired,
19
    extraHeaderContent: PropTypes.func,
20
    withPagination: PropTypes.bool,
21
  };
22
23
  state = {
24
    orderField: undefined,
25
    orderDir: undefined,
26
    currentPage: 1,
27
    itemsPerPage: Infinity,
28
  };
29
30
  determineStats(stats, sortingItems) {
31 17
    const pairs = toPairs(stats);
32 17
    const sortedPairs = !this.state.orderField ? pairs : sortBy(
33
      pipe(
34
        prop(this.state.orderField === head(keys(sortingItems)) ? 0 : 1),
35
        toLowerIfString
36
      ),
37
      pairs
38
    );
39 17
    const directionalPairs = !this.state.orderDir || this.state.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs);
40
41 17
    if (directionalPairs.length <= this.state.itemsPerPage) {
42 15
      return { currentPageStats: fromPairs(directionalPairs) };
43
    }
44
45 2
    const pages = splitEvery(this.state.itemsPerPage, directionalPairs);
46
47 2
    return {
48
      currentPageStats: fromPairs(this.determineCurrentPagePairs(pages)),
49
      pagination: this.renderPagination(pages.length),
50
      max: roundTen(max(...directionalPairs.map(pickValueFromPair))),
51
    };
52
  }
53
54
  determineCurrentPagePairs(pages) {
55 2
    const page = pages[this.state.currentPage - 1];
56
57 2
    if (this.state.currentPage < pages.length) {
58 2
      return page;
59
    }
60
61
    const firstPageLength = pages[0].length;
62
63
    // Using the "hidden" key, the chart will just replace the label by an empty string
64
    return [ ...page, ...rangeOf(firstPageLength - page.length, (i) => [ `hidden_${i}`, 0 ]) ];
65
  }
66
67
  renderPagination(pagesCount) {
68 2
    const { currentPage } = this.state;
69
70 2
    return (
71
      <Pagination listClassName="flex-wrap mb-0">
72
        <PaginationItem disabled={currentPage === 1}>
73
          <PaginationLink previous tag="span" onClick={() => this.setState({ currentPage: currentPage - 1 })} />
74
        </PaginationItem>
75
        {rangeOf(pagesCount, (page) => (
76 6
          <PaginationItem key={page} active={page === currentPage}>
77
            <PaginationLink tag="span" onClick={() => this.setState({ currentPage: page })}>{page}</PaginationLink>
78
          </PaginationItem>
79
        ))}
80
        <PaginationItem disabled={currentPage >= pagesCount}>
81
          <PaginationLink next tag="span" onClick={() => this.setState({ currentPage: currentPage + 1 })} />
82
        </PaginationItem>
83
      </Pagination>
84
    );
85
  }
86
87
  render() {
88 17
    const { stats, sortingItems, title, extraHeaderContent, withPagination = true } = this.props;
89 17
    const { currentPageStats, pagination, max } = this.determineStats(stats, sortingItems);
90 17
    const activeCities = keys(currentPageStats);
91 17
    const computeTitle = () => (
92 8
      <React.Fragment>
93
        {title}
94
        <div className="float-right">
95
          <SortingDropdown
96
            isButton={false}
97
            right
98
            items={sortingItems}
99
            orderField={this.state.orderField}
100
            orderDir={this.state.orderDir}
101 4
            onChange={(orderField, orderDir) => this.setState({ orderField, orderDir, currentPage: 1 })}
102
          />
103
        </div>
104
        {withPagination && keys(stats).length > 50 && (
105
          <div className="float-right">
106
            <PaginationDropdown
107
              toggleClassName="btn-sm paddingless mr-3"
108
              ranges={[ 50, 100, 200, 500 ]}
109
              value={this.state.itemsPerPage}
110 4
              setValue={(itemsPerPage) => this.setState({ itemsPerPage, currentPage: 1 })}
111
            />
112
          </div>
113
        )}
114
        {extraHeaderContent && (
115
          <div className="float-right">
116
            {extraHeaderContent(pagination ? activeCities : undefined)}
117
          </div>
118
        )}
119
      </React.Fragment>
120
    );
121
122 17
    return <GraphCard isBarChart title={computeTitle} stats={currentPageStats} footer={pagination} max={max} />;
123
  }
124
}
125