shlink-web-client/src/visits/ShortUrlVisits.js

155 lines
4.9 KiB
JavaScript
Raw Normal View History

2019-01-06 00:25:54 +03:00
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isEmpty, mapObjIndexed, values } from 'ramda';
import React from 'react';
import { Card } from 'reactstrap';
import PropTypes from 'prop-types';
import DateRangeRow from '../utils/DateRangeRow';
import MutedMessage from '../utils/MuttedMessage';
import { formatDate } from '../utils/utils';
2018-10-29 01:04:52 +03:00
import SortableBarGraph from './SortableBarGraph';
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
import VisitsHeader from './VisitsHeader';
import GraphCard from './GraphCard';
import { shortUrlDetailType } from './reducers/shortUrlDetail';
2018-07-29 19:39:00 +03:00
const ShortUrlVisits = (
{ processStatsFromVisits },
OpenMapModalBtn
) => class ShortUrlVisits extends React.PureComponent {
static propTypes = {
2018-09-08 14:28:40 +03:00
match: PropTypes.shape({
params: PropTypes.object,
}),
getShortUrlVisits: PropTypes.func,
shortUrlVisits: shortUrlVisitsType,
getShortUrlDetail: PropTypes.func,
shortUrlDetail: shortUrlDetailType,
cancelGetShortUrlVisits: PropTypes.func,
};
state = { startDate: undefined, endDate: undefined };
loadVisits = () => {
const { match: { params }, getShortUrlVisits } = this.props;
const { shortCode } = params;
const dates = mapObjIndexed(formatDate(), this.state);
const { startDate, endDate } = dates;
// While the "page" is loaded, use the timestamp + filtering dates as memoization IDs for stats calcs
this.memoizationId = `${this.timeWhenMounted}_${shortCode}_${startDate}_${endDate}`;
getShortUrlVisits(shortCode, dates);
};
2018-07-29 20:25:22 +03:00
componentDidMount() {
const { match: { params }, getShortUrlDetail } = this.props;
const { shortCode } = params;
this.timeWhenMounted = new Date().getTime();
this.loadVisits();
getShortUrlDetail(shortCode);
2018-07-29 20:25:22 +03:00
}
componentWillUnmount() {
this.props.cancelGetShortUrlVisits();
}
2018-07-29 20:25:22 +03:00
render() {
const { shortUrlVisits, shortUrlDetail } = this.props;
const renderVisitsContent = () => {
const { visits, loading, loadingLarge, error } = shortUrlVisits;
2018-07-30 21:52:03 +03:00
if (loading) {
const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...';
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> {message}</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>
);
}
if (isEmpty(visits)) {
return <MutedMessage>There are no visits matching current filter :(</MutedMessage>;
}
const { os, browsers, referrers, countries, cities, citiesForMap } = processStatsFromVisits(
{ id: this.memoizationId, visits }
);
const mapLocations = values(citiesForMap);
2018-07-30 21:52:03 +03:00
return (
<div className="row">
<div className="col-xl-4 col-lg-6">
<GraphCard title="Operating systems" stats={os} />
</div>
<div className="col-xl-4 col-lg-6">
<GraphCard title="Browsers" stats={browsers} />
</div>
<div className="col-xl-4">
<SortableBarGraph
stats={referrers}
2019-03-10 11:54:40 +03:00
withPagination={false}
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={countries}
2018-10-29 01:04:52 +03:00
title="Countries"
sortingItems={{
name: 'Country name',
amount: 'Visits amount',
}}
/>
</div>
<div className="col-lg-6">
2018-10-29 01:04:52 +03:00
<SortableBarGraph
stats={cities}
title="Cities"
extraHeaderContent={(activeCities) =>
mapLocations.length > 0 &&
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
}
2018-10-29 01:04:52 +03:00
sortingItems={{
name: 'City name',
2018-10-29 01:04:52 +03:00
amount: 'Visits amount',
}}
/>
</div>
2018-07-29 20:25:22 +03:00
</div>
2018-07-30 21:52:03 +03:00
);
};
const setDate = (dateField) => (date) => this.setState({ [dateField]: date }, this.loadVisits);
2018-07-30 21:52:03 +03:00
return (
2018-08-16 19:59:00 +03:00
<div className="shlink-container">
<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">
<DateRangeRow
startDate={this.state.startDate}
endDate={this.state.endDate}
2020-01-14 22:12:30 +03:00
onStartDateChange={setDate('startDate')}
onEndDateChange={setDate('endDate')}
/>
</section>
<section>
{renderVisitsContent()}
</section>
2018-07-29 20:25:22 +03:00
</div>
);
2018-07-29 19:39:00 +03:00
}
};
export default ShortUrlVisits;