Completed
Push — master ( 984c1e...16ffbc )
by Alejandro
19s queued 10s
created

src/visits/helpers/SortableBarGraph.js   A

Complexity

Total Complexity 8
Complexity/F 0

Size

Lines of Code 147
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 92.11%

Importance

Changes 0
Metric Value
wmc 8
eloc 117
mnd 8
bc 8
fnc 0
dl 0
loc 147
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
ccs 35
cts 38
cp 0.9211
rs 10
1
import React, { useState } from 'react';
2
import PropTypes from 'prop-types';
3
import { fromPairs, head, keys, pipe, prop, reverse, sortBy, splitEvery, toLower, toPairs, type, zipObj } from 'ramda';
4
import SortingDropdown from '../../utils/SortingDropdown';
5
import PaginationDropdown from '../../utils/PaginationDropdown';
6
import { rangeOf } from '../../utils/utils';
7
import { roundTen } from '../../utils/helpers/numbers';
8
import SimplePaginator from '../../common/SimplePaginator';
9
import GraphCard from './GraphCard';
10
11 2
const propTypes = {
12
  stats: PropTypes.object.isRequired,
13
  highlightedStats: PropTypes.object,
14
  highlightedLabel: PropTypes.string,
15
  title: PropTypes.string.isRequired,
16
  sortingItems: PropTypes.object.isRequired,
17
  extraHeaderContent: PropTypes.func,
18
  withPagination: PropTypes.bool,
19
  onClick: PropTypes.func,
20
};
21
22 16
const toLowerIfString = (value) => type(value) === 'String' ? toLower(value) : value;
23 1946
const pickKeyFromPair = ([ key ]) => key;
24 1280
const pickValueFromPair = ([ , value ]) => value;
25
26 2
const SortableBarGraph = ({
27
  stats,
28
  highlightedStats,
29
  title,
30
  sortingItems,
31
  extraHeaderContent,
32
  withPagination = true,
33
  ...rest
34
}) => {
35 25
  const [ order, setOrder ] = useState({
36
    orderField: undefined,
37
    orderDir: undefined,
38
  });
39 25
  const [ currentPage, setCurrentPage ] = useState(1);
40 25
  const [ itemsPerPage, setItemsPerPage ] = useState(50);
41
42 25
  const getSortedPairsForStats = (stats, sortingItems) => {
43 25
    const pairs = toPairs(stats);
44 25
    const sortedPairs = !order.orderField ? pairs : sortBy(
45
      pipe(
46
        prop(order.orderField === head(keys(sortingItems)) ? 0 : 1),
47
        toLowerIfString
48
      ),
49
      pairs
50
    );
51
52 25
    return !order.orderDir || order.orderDir === 'ASC' ? sortedPairs : reverse(sortedPairs);
53
  };
54 25
  const determineStats = (stats, highlightedStats, sortingItems) => {
55 25
    const sortedPairs = getSortedPairsForStats(stats, sortingItems);
56 25
    const sortedKeys = sortedPairs.map(pickKeyFromPair);
57
    // The highlighted stats have to be ordered based on the regular stats, not on its own values
58 25
    const sortedHighlightedPairs = highlightedStats && toPairs(
59
      { ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...highlightedStats }
60
    );
61
62 25
    if (sortedPairs.length <= itemsPerPage) {
63 17
      return {
64
        currentPageStats: fromPairs(sortedPairs),
65
        currentPageHighlightedStats: sortedHighlightedPairs && fromPairs(sortedHighlightedPairs),
66
      };
67
    }
68
69 8
    const pages = splitEvery(itemsPerPage, sortedPairs);
70 8
    const highlightedPages = sortedHighlightedPairs && splitEvery(itemsPerPage, sortedHighlightedPairs);
71
72 8
    return {
73
      currentPageStats: fromPairs(determineCurrentPagePairs(pages)),
74
      currentPageHighlightedStats: highlightedPages && fromPairs(determineCurrentPagePairs(highlightedPages)),
75
      pagination: renderPagination(pages.length),
76
      max: roundTen(Math.max(...sortedPairs.map(pickValueFromPair))),
77
    };
78
  };
79 25
  const determineCurrentPagePairs = (pages) => {
80 8
    const page = pages[currentPage - 1];
81
82 8
    if (currentPage < pages.length) {
83 8
      return page;
84
    }
85
86
    const firstPageLength = pages[0].length;
87
88
    // Using the "hidden" key, the chart will just replace the label by an empty string
89
    return [ ...page, ...rangeOf(firstPageLength - page.length, (i) => [ `hidden_${i}`, 0 ]) ];
90
  };
91 25
  const renderPagination = (pagesCount) =>
92 8
    <SimplePaginator currentPage={currentPage} pagesCount={pagesCount} setCurrentPage={setCurrentPage} />;
93
94 25
  const { currentPageStats, currentPageHighlightedStats, pagination, max } = determineStats(
95
    stats,
96
    highlightedStats && keys(highlightedStats).length > 0 ? highlightedStats : undefined,
97
    sortingItems
98
  );
99 25
  const activeCities = keys(currentPageStats);
100 25
  const computeTitle = () => (
101 8
    <React.Fragment>
102
      {title}
103
      <div className="float-right">
104
        <SortingDropdown
105
          isButton={false}
106
          right
107
          items={sortingItems}
108
          orderField={order.orderField}
109
          orderDir={order.orderDir}
110 4
          onChange={(orderField, orderDir) => setOrder({ orderField, orderDir }) || setCurrentPage(1)}
111
        />
112
      </div>
113
      {withPagination && keys(stats).length > 50 && (
114
        <div className="float-right">
115
          <PaginationDropdown
116
            toggleClassName="btn-sm p-0 mr-3"
117
            ranges={[ 50, 100, 200, 500 ]}
118
            value={itemsPerPage}
119 4
            setValue={(itemsPerPage) => setItemsPerPage(itemsPerPage) || setCurrentPage(1)}
120
          />
121
        </div>
122
      )}
123
      {extraHeaderContent && (
124
        <div className="float-right">
125
          {extraHeaderContent(pagination ? activeCities : undefined)}
126
        </div>
127
      )}
128
    </React.Fragment>
129
  );
130
131 25
  return (
132
    <GraphCard
133
      isBarChart
134
      title={computeTitle}
135
      stats={currentPageStats}
136
      highlightedStats={currentPageHighlightedStats}
137
      footer={pagination}
138
      max={max}
139
      {...rest}
140
    />
141
  );
142
};
143
144 2
SortableBarGraph.propTypes = propTypes;
145
146
export default SortableBarGraph;
147