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
|
|
|
|
19
|
2 |
|
const propTypes = { |
20
|
|
|
title: PropTypes.string, |
21
|
|
|
visits: PropTypes.arrayOf(VisitType), |
22
|
|
|
highlightedVisits: PropTypes.arrayOf(VisitType), |
23
|
|
|
}; |
24
|
|
|
|
25
|
2 |
|
const steps = [ |
26
|
|
|
{ value: 'monthly', menuText: 'Month' }, |
27
|
|
|
{ value: 'weekly', menuText: 'Week' }, |
28
|
|
|
{ value: 'daily', menuText: 'Day' }, |
29
|
|
|
{ value: 'hourly', menuText: 'Hour' }, |
30
|
|
|
]; |
31
|
|
|
|
32
|
2 |
|
const STEP_TO_DATE_FORMAT = { |
33
|
|
|
hourly: (date) => moment(date).format('YYYY-MM-DD HH:00'), |
34
|
|
|
daily: (date) => moment(date).format('YYYY-MM-DD'), |
35
|
|
|
weekly(date) { |
36
|
|
|
const firstWeekDay = moment(date).isoWeekday(1).format('YYYY-MM-DD'); |
37
|
|
|
const lastWeekDay = moment(date).isoWeekday(7).format('YYYY-MM-DD'); |
38
|
|
|
|
39
|
|
|
return `${firstWeekDay} - ${lastWeekDay}`; |
40
|
|
|
}, |
41
|
3 |
|
monthly: (date) => moment(date).format('YYYY-MM'), |
42
|
|
|
}; |
43
|
|
|
|
44
|
10 |
|
const groupVisitsByStep = (step, visits) => visits.reduce((acc, visit) => { |
45
|
3 |
|
const key = STEP_TO_DATE_FORMAT[step](visit.date); |
46
|
|
|
|
47
|
3 |
|
acc[key] = acc[key] ? acc[key] + 1 : 1; |
48
|
|
|
|
49
|
3 |
|
return acc; |
50
|
|
|
}, {}); |
51
|
|
|
|
52
|
6 |
|
const generateDataset = (stats, label, color) => ({ |
53
|
|
|
label, |
54
|
|
|
data: Object.values(stats), |
55
|
|
|
fill: false, |
56
|
|
|
lineTension: 0.2, |
57
|
|
|
borderColor: color, |
58
|
|
|
backgroundColor: color, |
59
|
|
|
}); |
60
|
|
|
|
61
|
2 |
|
const LineChartCard = ({ title, visits, highlightedVisits }) => { |
62
|
5 |
|
const [ step, setStep ] = useState(steps[0].value); |
63
|
5 |
|
const groupedVisits = useMemo(() => groupVisitsByStep(step, reverse(visits)), [ visits, step ]); |
64
|
5 |
|
const labels = useMemo(() => Object.keys(groupedVisits), [ groupedVisits ]); |
65
|
5 |
|
const groupedHighlighted = useMemo( |
66
|
5 |
|
() => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels), |
67
|
|
|
[ highlightedVisits, step, labels ] |
68
|
|
|
); |
69
|
|
|
|
70
|
5 |
|
const data = { |
71
|
|
|
labels, |
72
|
|
|
datasets: [ |
73
|
|
|
generateDataset(groupedVisits, 'Visits', '#4696e5'), |
74
|
|
|
highlightedVisits.length > 0 && generateDataset(groupedHighlighted, 'Selected', '#F77F28'), |
75
|
|
|
].filter(Boolean), |
76
|
|
|
}; |
77
|
5 |
|
const options = { |
78
|
|
|
maintainAspectRatio: false, |
79
|
|
|
legend: { display: false }, |
80
|
|
|
scales: { |
81
|
|
|
yAxes: [ |
82
|
|
|
{ |
83
|
|
|
ticks: { beginAtZero: true, precision: 0 }, |
84
|
|
|
}, |
85
|
|
|
], |
86
|
|
|
}, |
87
|
|
|
}; |
88
|
|
|
|
89
|
5 |
|
return ( |
90
|
|
|
<Card> |
91
|
|
|
<CardHeader> |
92
|
|
|
{title} |
93
|
|
|
<div className="float-right"> |
94
|
|
|
<UncontrolledDropdown> |
95
|
|
|
<DropdownToggle caret color="link" className="btn-sm p-0"> |
96
|
|
|
Group by |
97
|
|
|
</DropdownToggle> |
98
|
|
|
<DropdownMenu right> |
99
|
|
|
{steps.map(({ menuText, value }) => ( |
100
|
20 |
|
<DropdownItem key={value} active={step === value} onClick={() => setStep(value)}> |
101
|
|
|
{menuText} |
102
|
|
|
</DropdownItem> |
103
|
|
|
))} |
104
|
|
|
</DropdownMenu> |
105
|
|
|
</UncontrolledDropdown> |
106
|
|
|
</div> |
107
|
|
|
</CardHeader> |
108
|
|
|
<CardBody className="line-chart-card__body"> |
109
|
|
|
<Line data={data} options={options} /> |
110
|
|
|
</CardBody> |
111
|
|
|
</Card> |
112
|
|
|
); |
113
|
|
|
}; |
114
|
|
|
|
115
|
2 |
|
LineChartCard.propTypes = propTypes; |
116
|
|
|
|
117
|
|
|
export default LineChartCard; |
118
|
|
|
|