Completed
Pull Request — master (#272)
by Alejandro
06:46
created

src/visits/helpers/LineChartCard.js   A

Complexity

Total Complexity 3
Complexity/F 3

Size

Lines of Code 171
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 87.8%

Importance

Changes 0
Metric Value
wmc 3
eloc 147
mnd 2
bc 2
fnc 1
dl 0
loc 171
bpm 2
cpm 3
noi 0
c 0
b 0
f 0
ccs 36
cts 41
cp 0.878
rs 10

1 Function

Rating   Name   Duplication   Size   Complexity  
A LineChartCard.js ➔ weekly 0 5 1
1
import React, { useState, useMemo } from 'react';
2
import PropTypes from 'prop-types';
3
import {
4
  Card,
5
  CardHeader,
6
  CardBody,
7
  UncontrolledDropdown,
8
  DropdownToggle,
9
  DropdownMenu,
10
  DropdownItem,
11
} from 'reactstrap';
12
import { Line } from 'react-chartjs-2';
13
import { reverse } from 'ramda';
14
import moment from 'moment';
15
import { VisitType } from '../types';
16
import { fillTheGaps } from '../../utils/helpers/visits';
17
import './LineChartCard.scss';
18
import { useToggle } from '../../utils/helpers/hooks';
19
import { rangeOf } from '../../utils/utils';
20
import Checkbox from '../../utils/Checkbox';
21
22 2
const propTypes = {
23
  title: PropTypes.string,
24
  visits: PropTypes.arrayOf(VisitType),
25
  highlightedVisits: PropTypes.arrayOf(VisitType),
26
};
27
28 2
const STEPS_MAP = {
29
  monthly: 'Month',
30
  weekly: 'Week',
31
  daily: 'Day',
32
  hourly: 'Hour',
33
};
34
35 2
const STEP_TO_DATE_UNIT_MAP = {
36
  hourly: 'hour',
37
  daily: 'day',
38
  weekly: 'week',
39
  monthly: 'month',
40
};
41
42 2
const STEP_TO_DATE_FORMAT = {
43
  hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'),
44
  daily: (date) => moment(date).format('YYYY-MM-DD'),
45
  weekly(date) {
46
    const firstWeekDay = moment(date).isoWeekday(1).format('YYYY-MM-DD');
47
    const lastWeekDay = moment(date).isoWeekday(7).format('YYYY-MM-DD');
48
49
    return `${firstWeekDay} - ${lastWeekDay}`;
50
  },
51 11
  monthly: (date) => moment(date).format('YYYY-MM'),
52
};
53
54 14
const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => {
55 7
  const key = STEP_TO_DATE_FORMAT[step](visit.date);
56
57 7
  acc[key] = acc[key] ? acc[key] + 1 : 1;
58
59 7
  return acc;
60
}, {});
61
62 2
const generateLabels = (step, visits) => {
63 1
  const unit = STEP_TO_DATE_UNIT_MAP[step];
64 1
  const formatter = STEP_TO_DATE_FORMAT[step];
65 1
  const newerDate = moment(visits[0].date);
66 1
  const oldestDate = moment(visits[visits.length - 1].date);
67 1
  const size = newerDate.diff(oldestDate, unit);
68
69 1
  return [
70
    formatter(oldestDate),
71 3
    ...rangeOf(size, () => formatter(oldestDate.add(1, unit))),
72
  ];
73
};
74
75 2
const generateLabelsAndGroupedVisits = (visits, step, skipNoElements) => {
76 7
  const groupedVisits = groupVisitsByStep(step, reverse(visits));
77
78 7
  if (skipNoElements) {
79 6
    return [ Object.keys(groupedVisits), groupedVisits ];
80
  }
81
82 1
  const labels = generateLabels(step, visits);
83
84 1
  return [ labels, fillTheGaps(groupedVisits, labels) ];
85
};
86
87 8
const generateDataset = (stats, label, color) => ({
88
  label,
89
  data: Object.values(stats),
90
  fill: false,
91
  lineTension: 0.2,
92
  borderColor: color,
93
  backgroundColor: color,
94
});
95
96 2
const LineChartCard = ({ title, visits, highlightedVisits }) => {
97 7
  const [ step, setStep ] = useState('monthly');
98 7
  const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true);
99
100 7
  const [ labels, groupedVisits ] = useMemo(
101 7
    () => generateLabelsAndGroupedVisits(visits, step, skipNoVisits),
102
    [ visits, step, skipNoVisits ]
103
  );
104 7
  const groupedHighlighted = useMemo(
105 7
    () => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels),
106
    [ highlightedVisits, step, labels ]
107
  );
108
109 7
  const data = {
110
    labels,
111
    datasets: [
112
      generateDataset(groupedVisits, 'Visits', '#4696e5'),
113
      highlightedVisits.length > 0 && generateDataset(groupedHighlighted, 'Selected', '#F77F28'),
114
    ].filter(Boolean),
115
  };
116 7
  const options = {
117
    maintainAspectRatio: false,
118
    legend: { display: false },
119
    scales: {
120
      yAxes: [
121
        {
122
          ticks: { beginAtZero: true, precision: 0 },
123
        },
124
      ],
125
      xAxes: [
126
        {
127
          scaleLabel: { display: true, labelString: STEPS_MAP[step] },
128
        },
129
      ],
130
    },
131
    tooltips: {
132
      intersect: false,
133
      axis: 'x',
134
    },
135
  };
136
137 7
  return (
138
    <Card>
139
      <CardHeader>
140
        {title}
141
        <div className="float-right">
142
          <UncontrolledDropdown>
143
            <DropdownToggle caret color="link" className="btn-sm p-0">
144
              Group by
145
            </DropdownToggle>
146
            <DropdownMenu right>
147
              {Object.entries(STEPS_MAP).map(([ value, menuText ]) => (
148 28
                <DropdownItem key={value} active={step === value} onClick={() => setStep(value)}>
149
                  {menuText}
150
                </DropdownItem>
151
              ))}
152
            </DropdownMenu>
153
          </UncontrolledDropdown>
154
        </div>
155
        <div className="float-right mr-2">
156
          <Checkbox checked={skipNoVisits} onChange={toggleSkipNoVisits}>
157
            <small>Skip dates with no visits</small>
158
          </Checkbox>
159
        </div>
160
      </CardHeader>
161
      <CardBody className="line-chart-card__body">
162
        <Line data={data} options={options} />
163
      </CardBody>
164
    </Card>
165
  );
166
};
167
168 2
LineChartCard.propTypes = propTypes;
169
170
export default LineChartCard;
171