2019-01-06 00:25:54 +03:00
|
|
|
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
|
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
2019-01-07 23:00:28 +03:00
|
|
|
import { isEmpty, mapObjIndexed, values } from 'ramda';
|
2018-08-26 00:39:27 +03:00
|
|
|
import React from 'react';
|
2018-09-01 12:08:27 +03:00
|
|
|
import { Card } from 'reactstrap';
|
2018-08-26 00:39:27 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2018-11-01 11:05:20 +03:00
|
|
|
import DateInput from '../utils/DateInput';
|
2018-09-01 11:33:16 +03:00
|
|
|
import MutedMessage from '../utils/MuttedMessage';
|
2018-10-29 01:04:52 +03:00
|
|
|
import SortableBarGraph from './SortableBarGraph';
|
2018-12-18 16:32:02 +03:00
|
|
|
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
2018-09-01 12:08:27 +03:00
|
|
|
import { VisitsHeader } from './VisitsHeader';
|
2018-10-19 20:04:22 +03:00
|
|
|
import GraphCard from './GraphCard';
|
2018-12-18 16:32:02 +03:00
|
|
|
import { shortUrlDetailType } from './reducers/shortUrlDetail';
|
2018-09-01 12:26:35 +03:00
|
|
|
import './ShortUrlVisits.scss';
|
2019-01-07 21:43:25 +03:00
|
|
|
import OpenMapModalBtn from './helpers/OpenMapModalBtn';
|
2018-07-29 19:39:00 +03:00
|
|
|
|
2018-12-18 16:32:02 +03:00
|
|
|
const ShortUrlVisits = ({
|
|
|
|
processOsStats,
|
|
|
|
processBrowserStats,
|
|
|
|
processCountriesStats,
|
2019-01-07 13:53:14 +03:00
|
|
|
processCitiesStats,
|
2018-12-18 16:32:02 +03:00
|
|
|
processReferrersStats,
|
2019-01-07 23:00:28 +03:00
|
|
|
processCitiesStatsForMap,
|
2018-12-18 16:32:02 +03:00
|
|
|
}) => class ShortUrlVisits extends React.Component {
|
2018-08-27 17:53:09 +03:00
|
|
|
static propTypes = {
|
2018-09-08 14:28:40 +03:00
|
|
|
match: PropTypes.shape({
|
|
|
|
params: PropTypes.object,
|
|
|
|
}),
|
2018-09-01 12:26:35 +03:00
|
|
|
getShortUrlVisits: PropTypes.func,
|
2018-08-27 17:53:09 +03:00
|
|
|
shortUrlVisits: shortUrlVisitsType,
|
2018-09-01 12:26:35 +03:00
|
|
|
getShortUrlDetail: PropTypes.func,
|
|
|
|
shortUrlDetail: shortUrlDetailType,
|
2018-08-27 17:53:09 +03:00
|
|
|
};
|
|
|
|
|
2018-07-31 23:04:20 +03:00
|
|
|
state = { startDate: undefined, endDate: undefined };
|
2018-08-01 19:32:21 +03:00
|
|
|
loadVisits = () => {
|
2018-08-26 00:39:27 +03:00
|
|
|
const { match: { params }, getShortUrlVisits } = this.props;
|
|
|
|
|
|
|
|
getShortUrlVisits(params.shortCode, mapObjIndexed(
|
|
|
|
(value) => value && value.format ? value.format('YYYY-MM-DD') : value,
|
2018-08-01 19:32:21 +03:00
|
|
|
this.state
|
2018-08-26 00:39:27 +03:00
|
|
|
));
|
2018-07-31 23:04:20 +03:00
|
|
|
};
|
2018-07-30 21:31:48 +03:00
|
|
|
|
2018-07-29 20:25:22 +03:00
|
|
|
componentDidMount() {
|
2018-09-01 12:26:35 +03:00
|
|
|
const { match: { params }, getShortUrlDetail } = this.props;
|
|
|
|
|
2018-07-31 23:04:20 +03:00
|
|
|
this.loadVisits();
|
2018-09-01 12:26:35 +03:00
|
|
|
getShortUrlDetail(params.shortCode);
|
2018-07-29 20:25:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2018-12-18 16:32:02 +03:00
|
|
|
const { shortUrlVisits, shortUrlDetail } = this.props;
|
2018-09-01 12:08:27 +03:00
|
|
|
|
2018-09-01 12:26:35 +03:00
|
|
|
const renderVisitsContent = () => {
|
|
|
|
const { visits, loading, error } = shortUrlVisits;
|
|
|
|
|
2018-07-30 21:52:03 +03:00
|
|
|
if (loading) {
|
2018-07-31 23:04:20 +03:00
|
|
|
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> Loading...</MutedMessage>;
|
2018-07-30 21:52:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
return (
|
|
|
|
<Card className="mt-4" body inverse color="danger">
|
|
|
|
An error occurred while loading visits :(
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
}
|
2018-07-30 21:31:48 +03:00
|
|
|
|
2018-07-31 23:04:20 +03:00
|
|
|
if (isEmpty(visits)) {
|
2018-09-01 12:26:35 +03:00
|
|
|
return <MutedMessage>There are no visits matching current filter :(</MutedMessage>;
|
2018-07-31 23:04:20 +03:00
|
|
|
}
|
|
|
|
|
2018-07-30 21:52:03 +03:00
|
|
|
return (
|
2018-07-30 21:31:48 +03:00
|
|
|
<div className="row">
|
2019-01-07 13:53:14 +03:00
|
|
|
<div className="col-xl-4 col-lg-6">
|
2018-09-01 12:08:27 +03:00
|
|
|
<GraphCard title="Operating systems" stats={processOsStats(visits)} />
|
|
|
|
</div>
|
2019-01-07 13:53:14 +03:00
|
|
|
<div className="col-xl-4 col-lg-6">
|
2018-09-01 12:08:27 +03:00
|
|
|
<GraphCard title="Browsers" stats={processBrowserStats(visits)} />
|
|
|
|
</div>
|
2019-01-07 13:53:14 +03:00
|
|
|
<div className="col-xl-4">
|
|
|
|
<SortableBarGraph
|
|
|
|
stats={processReferrersStats(visits)}
|
|
|
|
title="Referrers"
|
|
|
|
sortingItems={{
|
|
|
|
name: 'Referrer name',
|
|
|
|
amount: 'Visits amount',
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="col-lg-6">
|
2018-10-29 01:04:52 +03:00
|
|
|
<SortableBarGraph
|
|
|
|
stats={processCountriesStats(visits)}
|
|
|
|
title="Countries"
|
|
|
|
sortingItems={{
|
|
|
|
name: 'Country name',
|
|
|
|
amount: 'Visits amount',
|
|
|
|
}}
|
|
|
|
/>
|
2018-09-01 12:08:27 +03:00
|
|
|
</div>
|
2019-01-07 13:53:14 +03:00
|
|
|
<div className="col-lg-6">
|
2018-10-29 01:04:52 +03:00
|
|
|
<SortableBarGraph
|
2019-01-07 13:53:14 +03:00
|
|
|
stats={processCitiesStats(visits)}
|
|
|
|
title="Cities"
|
2019-01-07 23:00:28 +03:00
|
|
|
extraHeaderContent={[
|
|
|
|
() => (
|
|
|
|
<OpenMapModalBtn
|
2019-01-09 09:59:56 +03:00
|
|
|
modalTitle="Cities"
|
2019-01-07 23:00:28 +03:00
|
|
|
locations={values(processCitiesStatsForMap(visits))}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
]}
|
2018-10-29 01:04:52 +03:00
|
|
|
sortingItems={{
|
2019-01-07 13:53:14 +03:00
|
|
|
name: 'City name',
|
2018-10-29 01:04:52 +03:00
|
|
|
amount: 'Visits amount',
|
|
|
|
}}
|
|
|
|
/>
|
2018-09-01 12:08:27 +03:00
|
|
|
</div>
|
2018-07-29 20:25:22 +03:00
|
|
|
</div>
|
2018-07-30 21:52:03 +03:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
2018-08-16 19:59:00 +03:00
|
|
|
<div className="shlink-container">
|
2018-09-08 10:31:44 +03:00
|
|
|
<VisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} />
|
2018-07-30 21:52:03 +03:00
|
|
|
|
2018-08-09 20:50:22 +03:00
|
|
|
<section className="mt-4">
|
|
|
|
<div className="row">
|
|
|
|
<div className="col-xl-3 col-lg-4 col-md-6 offset-xl-6 offset-lg-4">
|
|
|
|
<DateInput
|
|
|
|
selected={this.state.startDate}
|
|
|
|
placeholderText="Since"
|
2018-08-09 21:13:46 +03:00
|
|
|
isClearable
|
2018-09-05 21:17:46 +03:00
|
|
|
maxDate={this.state.endDate}
|
2018-08-26 00:39:27 +03:00
|
|
|
onChange={(date) => this.setState({ startDate: date }, () => this.loadVisits())}
|
2018-08-09 20:50:22 +03:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="col-xl-3 col-lg-4 col-md-6">
|
|
|
|
<DateInput
|
2018-10-07 10:22:15 +03:00
|
|
|
className="short-url-visits__date-input"
|
2018-08-09 20:50:22 +03:00
|
|
|
selected={this.state.endDate}
|
|
|
|
placeholderText="Until"
|
2018-08-09 21:13:46 +03:00
|
|
|
isClearable
|
2018-09-05 21:17:46 +03:00
|
|
|
minDate={this.state.startDate}
|
2018-08-26 00:39:27 +03:00
|
|
|
onChange={(date) => this.setState({ endDate: date }, () => this.loadVisits())}
|
2018-08-09 20:50:22 +03:00
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2018-07-31 23:04:20 +03:00
|
|
|
</section>
|
|
|
|
|
2018-07-30 22:34:06 +03:00
|
|
|
<section>
|
2018-09-01 12:26:35 +03:00
|
|
|
{renderVisitsContent()}
|
2018-07-30 22:34:06 +03:00
|
|
|
</section>
|
2018-07-29 20:25:22 +03:00
|
|
|
</div>
|
|
|
|
);
|
2018-07-29 19:39:00 +03:00
|
|
|
}
|
2018-12-18 16:32:02 +03:00
|
|
|
};
|
2018-08-26 00:39:27 +03:00
|
|
|
|
2018-12-18 16:32:02 +03:00
|
|
|
export default ShortUrlVisits;
|