mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Added filtering by date range to visit stats page
This commit is contained in:
parent
a7bd66827a
commit
0b15fba640
4 changed files with 70 additions and 30 deletions
|
@ -15,7 +15,7 @@ export default class DateInput extends React.Component {
|
||||||
<div className="date-input-container">
|
<div className="date-input-container">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{...this.props}
|
{...this.props}
|
||||||
className="date-input-container__input form-control"
|
className={`date-input-container__input form-control ${this.props.className || ''}`}
|
||||||
dateFormat="YYYY-MM-DD"
|
dateFormat="YYYY-MM-DD"
|
||||||
readOnly
|
readOnly
|
||||||
ref={this.inputRef}
|
ref={this.inputRef}
|
||||||
|
|
|
@ -14,6 +14,7 @@ export default class DeleteServerButton extends React.Component {
|
||||||
<span
|
<span
|
||||||
className={this.props.className}
|
className={this.props.className}
|
||||||
onClick={() => this.setState({ isModalOpen: true })}
|
onClick={() => this.setState({ isModalOpen: true })}
|
||||||
|
key="deleteServerBtn"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={deleteIcon} />
|
<FontAwesomeIcon icon={deleteIcon} />
|
||||||
<span className="aside-menu__item-text">Delete this server</span>
|
<span className="aside-menu__item-text">Delete this server</span>
|
||||||
|
@ -25,6 +26,7 @@ export default class DeleteServerButton extends React.Component {
|
||||||
toggle={() => this.setState({ isModalOpen: !this.state.isModalOpen })}
|
toggle={() => this.setState({ isModalOpen: !this.state.isModalOpen })}
|
||||||
history={history}
|
history={history}
|
||||||
server={server}
|
server={server}
|
||||||
|
key="deleteServerModal"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,20 +1,37 @@
|
||||||
|
import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch';
|
||||||
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
|
import { isEmpty, mapObjIndexed, pick } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
||||||
import Moment from 'react-moment';
|
import Moment from 'react-moment';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { pick } from 'ramda';
|
|
||||||
import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap';
|
import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap';
|
||||||
import { getShortUrlVisits } from './reducers/shortUrlVisits';
|
import DateInput from '../common/DateInput';
|
||||||
import VisitsParser from '../visits/services/VisitsParser';
|
import VisitsParser from '../visits/services/VisitsParser';
|
||||||
import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch';
|
import { getShortUrlVisits } from './reducers/shortUrlVisits';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import './ShortUrlVisits.scss';
|
||||||
|
|
||||||
|
const MutedMessage = ({ children }) =>
|
||||||
|
<div className="col-md-10 offset-md-1">
|
||||||
|
<Card className="bg-light mt-4" body>
|
||||||
|
<h3 className="text-center text-muted mb-0">
|
||||||
|
{children}
|
||||||
|
</h3>
|
||||||
|
</Card>
|
||||||
|
</div>;
|
||||||
|
|
||||||
export class ShortUrlsVisits extends React.Component {
|
export class ShortUrlsVisits extends React.Component {
|
||||||
state = { startDate: '', endDate: '' };
|
state = { startDate: undefined, endDate: undefined };
|
||||||
|
loadVisits = (dates = {}) => {
|
||||||
|
const { match: { params } } = this.props;
|
||||||
|
this.props.getShortUrlVisits(params.shortCode, mapObjIndexed(
|
||||||
|
value => value && value.format ? value.format('YYYY-MM-DD') : value,
|
||||||
|
{ ...this.state, ...dates }
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { match: { params } } = this.props;
|
this.loadVisits();
|
||||||
this.props.getShortUrlVisits(params.shortCode, this.state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -24,15 +41,6 @@ export class ShortUrlsVisits extends React.Component {
|
||||||
visitsParser,
|
visitsParser,
|
||||||
shortUrlVisits: { visits, loading, error, shortUrl }
|
shortUrlVisits: { visits, loading, error, shortUrl }
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const colors = [
|
|
||||||
'#97BBCD',
|
|
||||||
'#DCDCDC',
|
|
||||||
'#F7464A',
|
|
||||||
'#46BFBD',
|
|
||||||
'#FDB45C',
|
|
||||||
'#949FB1',
|
|
||||||
'#4D5360'
|
|
||||||
];
|
|
||||||
const serverUrl = selectedServer ? selectedServer.url : '';
|
const serverUrl = selectedServer ? selectedServer.url : '';
|
||||||
const shortLink = `${serverUrl}/${params.shortCode}`;
|
const shortLink = `${serverUrl}/${params.shortCode}`;
|
||||||
const generateGraphData = (stats, label, isBarChart) => ({
|
const generateGraphData = (stats, label, isBarChart) => ({
|
||||||
|
@ -41,7 +49,15 @@ export class ShortUrlsVisits extends React.Component {
|
||||||
{
|
{
|
||||||
label,
|
label,
|
||||||
data: Object.values(stats),
|
data: Object.values(stats),
|
||||||
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : colors,
|
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
|
||||||
|
'#97BBCD',
|
||||||
|
'#DCDCDC',
|
||||||
|
'#F7464A',
|
||||||
|
'#46BFBD',
|
||||||
|
'#FDB45C',
|
||||||
|
'#949FB1',
|
||||||
|
'#4D5360'
|
||||||
|
],
|
||||||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||||
borderWidth: 2
|
borderWidth: 2
|
||||||
}
|
}
|
||||||
|
@ -67,15 +83,7 @@ export class ShortUrlsVisits extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> Loading...</MutedMessage>;
|
||||||
<div className="col-md-10 offset-md-1">
|
|
||||||
<Card className="bg-light mt-4" body>
|
|
||||||
<h3 className="text-center text-muted">
|
|
||||||
<FontAwesomeIcon icon={preloader} spin /> Loading...
|
|
||||||
</h3>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -86,6 +94,10 @@ export class ShortUrlsVisits extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEmpty(visits)) {
|
||||||
|
return <MutedMessage>There have been no visits matching current filter :(</MutedMessage>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{renderGraphCard('Operating systems', visitsParser.processOsStats(visits), false)}
|
{renderGraphCard('Operating systems', visitsParser.processOsStats(visits), false)}
|
||||||
|
@ -132,6 +144,31 @@ export class ShortUrlsVisits extends React.Component {
|
||||||
</Card>
|
</Card>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<form onSubmit={e => e.preventDefault()} className="form-inline mt-4 float-md-right">
|
||||||
|
<label>Period</label>
|
||||||
|
<DateInput
|
||||||
|
selected={this.state.startDate}
|
||||||
|
placeholderText="Since"
|
||||||
|
onChange={date => {
|
||||||
|
this.setState({ startDate: date });
|
||||||
|
this.loadVisits({ startDate: date });
|
||||||
|
}}
|
||||||
|
className="short-url-visits__date-input"
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
selected={this.state.endDate}
|
||||||
|
placeholderText="Until"
|
||||||
|
onChange={date => {
|
||||||
|
this.setState({ endDate: date });
|
||||||
|
this.loadVisits({ endDate: date });
|
||||||
|
}}
|
||||||
|
className="short-url-visits__date-input"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
<div className="clearfix" />
|
||||||
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</section>
|
</section>
|
||||||
|
@ -144,6 +181,4 @@ ShortUrlsVisits.defaultProps = {
|
||||||
visitsParser: VisitsParser
|
visitsParser: VisitsParser
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(pick(['selectedServer', 'shortUrlVisits']), {
|
export default connect(pick(['selectedServer', 'shortUrlVisits']), { getShortUrlVisits })(ShortUrlsVisits);
|
||||||
getShortUrlVisits
|
|
||||||
})(ShortUrlsVisits);
|
|
||||||
|
|
3
src/short-urls/ShortUrlVisits.scss
Normal file
3
src/short-urls/ShortUrlVisits.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.short-url-visits__date-input {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
Loading…
Reference in a new issue