mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Extracted components in ShortUrlVisits to simplify maintainability
This commit is contained in:
parent
b7ca32ff8f
commit
0d97c084c2
4 changed files with 124 additions and 96 deletions
48
src/visits/GraphCard.js
Normal file
48
src/visits/GraphCard.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Card, CardHeader, CardBody } from 'reactstrap';
|
||||||
|
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
isBarChart: PropTypes.bool,
|
||||||
|
stats: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function GraphCard({ title, isBarChart, stats }) {
|
||||||
|
const generateGraphData = (stats) => ({
|
||||||
|
labels: Object.keys(stats),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
data: Object.values(stats),
|
||||||
|
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
|
||||||
|
'#97BBCD',
|
||||||
|
'#DCDCDC',
|
||||||
|
'#F7464A',
|
||||||
|
'#46BFBD',
|
||||||
|
'#FDB45C',
|
||||||
|
'#949FB1',
|
||||||
|
'#4D5360',
|
||||||
|
],
|
||||||
|
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||||
|
borderWidth: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const renderGraph = () => {
|
||||||
|
const Component = isBarChart ? HorizontalBar : Doughnut;
|
||||||
|
const legend = isBarChart ? { display: false } : { position: 'right' };
|
||||||
|
|
||||||
|
return <Component data={generateGraphData(stats)} options={{ legend }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mt-4">
|
||||||
|
<CardHeader>{title}</CardHeader>
|
||||||
|
<CardBody>{renderGraph()}</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphCard.propTypes = propTypes;
|
|
@ -2,23 +2,22 @@ import preloader from '@fortawesome/fontawesome-free-solid/faCircleNotch';
|
||||||
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
import FontAwesomeIcon from '@fortawesome/react-fontawesome';
|
||||||
import { isEmpty, mapObjIndexed, pick } from 'ramda';
|
import { isEmpty, mapObjIndexed, pick } from 'ramda';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
|
||||||
import Moment from 'react-moment';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap';
|
import { Card } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import DateInput from '../common/DateInput';
|
import DateInput from '../common/DateInput';
|
||||||
import MutedMessage from '../utils/MuttedMessage';
|
import MutedMessage from '../utils/MuttedMessage';
|
||||||
import ExternalLink from '../utils/ExternalLink';
|
|
||||||
import { serverType } from '../servers/prop-types/index';
|
import { serverType } from '../servers/prop-types/index';
|
||||||
import { getShortUrlVisits, shortUrlVisitsType } from './reducers/shortUrlVisits';
|
import { getShortUrlVisits, shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||||
import {
|
import {
|
||||||
processOsStats,
|
|
||||||
processBrowserStats,
|
processBrowserStats,
|
||||||
processCountriesStats,
|
processCountriesStats,
|
||||||
|
processOsStats,
|
||||||
processReferrersStats,
|
processReferrersStats,
|
||||||
} from './services/VisitsParser';
|
} from './services/VisitsParser';
|
||||||
import './ShortUrlVisits.scss';
|
import './ShortUrlVisits.scss';
|
||||||
|
import { VisitsHeader } from './VisitsHeader';
|
||||||
|
import { GraphCard } from './GraphCard';
|
||||||
|
|
||||||
export class ShortUrlsVisitsComponent extends React.Component {
|
export class ShortUrlsVisitsComponent extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -60,59 +59,12 @@ export class ShortUrlsVisitsComponent extends React.Component {
|
||||||
processBrowserStats,
|
processBrowserStats,
|
||||||
processCountriesStats,
|
processCountriesStats,
|
||||||
processReferrersStats,
|
processReferrersStats,
|
||||||
shortUrlVisits: { visits, loading, error, shortUrl },
|
shortUrlVisits,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { visits, loading, error } = shortUrlVisits;
|
||||||
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) => ({
|
|
||||||
labels: Object.keys(stats),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label,
|
|
||||||
data: Object.values(stats),
|
|
||||||
backgroundColor: isBarChart ? 'rgba(70, 150, 229, 0.4)' : [
|
|
||||||
'#97BBCD',
|
|
||||||
'#DCDCDC',
|
|
||||||
'#F7464A',
|
|
||||||
'#46BFBD',
|
|
||||||
'#FDB45C',
|
|
||||||
'#949FB1',
|
|
||||||
'#4D5360',
|
|
||||||
],
|
|
||||||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
const renderGraphCard = (title, stats, isBarChart, label) => (
|
|
||||||
<div className="col-md-6">
|
|
||||||
<Card className="mt-4">
|
|
||||||
<CardHeader>{title}</CardHeader>
|
|
||||||
<CardBody>
|
|
||||||
{!isBarChart && (
|
|
||||||
<Doughnut
|
|
||||||
data={generateGraphData(stats, label || title, isBarChart)}
|
|
||||||
options={{
|
|
||||||
legend: {
|
|
||||||
position: 'right',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isBarChart && (
|
|
||||||
<HorizontalBar
|
|
||||||
data={generateGraphData(stats, label || title, isBarChart)}
|
|
||||||
options={{
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> Loading...</MutedMessage>;
|
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> Loading...</MutedMessage>;
|
||||||
|
@ -132,53 +84,25 @@ export class ShortUrlsVisitsComponent extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{renderGraphCard('Operating systems', processOsStats(visits), false)}
|
<div className="col-md-6">
|
||||||
{renderGraphCard('Browsers', processBrowserStats(visits), false)}
|
<GraphCard title="Operating systems" stats={processOsStats(visits)} />
|
||||||
{renderGraphCard('Countries', processCountriesStats(visits), true, 'Visits')}
|
</div>
|
||||||
{renderGraphCard('Referrers', processReferrersStats(visits), true, 'Visits')}
|
<div className="col-md-6">
|
||||||
|
<GraphCard title="Browsers" stats={processBrowserStats(visits)} />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6">
|
||||||
|
<GraphCard title="Countries" stats={processCountriesStats(visits)} isBarChart />
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6">
|
||||||
|
<GraphCard title="Referrers" stats={processReferrersStats(visits)} isBarChart />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCreated = () => (
|
|
||||||
<span>
|
|
||||||
<b id="created"><Moment fromNow>{shortUrl.dateCreated}</Moment></b>
|
|
||||||
<UncontrolledTooltip placement="bottom" target="created">
|
|
||||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
|
||||||
</UncontrolledTooltip>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shlink-container">
|
<div className="shlink-container">
|
||||||
<header>
|
<VisitsHeader shortUrlVisits={shortUrlVisits} shortLink={shortLink} />
|
||||||
<Card className="bg-light">
|
|
||||||
<CardBody>
|
|
||||||
<h2>
|
|
||||||
{
|
|
||||||
shortUrl.visitsCount &&
|
|
||||||
<span className="badge badge-main float-right">Visits: {shortUrl.visitsCount}</span>
|
|
||||||
}
|
|
||||||
Visit stats for <ExternalLink href={shortLink}>{shortLink}</ExternalLink>
|
|
||||||
</h2>
|
|
||||||
<hr />
|
|
||||||
{shortUrl.dateCreated && (
|
|
||||||
<div>
|
|
||||||
Created:
|
|
||||||
|
|
||||||
{loading && <small>Loading...</small>}
|
|
||||||
{!loading && renderCreated()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
Long URL:
|
|
||||||
|
|
||||||
{loading && <small>Loading...</small>}
|
|
||||||
{!loading && <ExternalLink href={shortUrl.longUrl}>{shortUrl.longUrl}</ExternalLink>}
|
|
||||||
</div>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section className="mt-4">
|
<section className="mt-4">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|
53
src/visits/VisitsHeader.js
Normal file
53
src/visits/VisitsHeader.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { Card, UncontrolledTooltip } from 'reactstrap';
|
||||||
|
import Moment from 'react-moment';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ExternalLink from '../utils/ExternalLink';
|
||||||
|
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||||
|
import './VisitsHeader.scss';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
shortUrlVisits: shortUrlVisitsType,
|
||||||
|
shortLink: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function VisitsHeader({ shortUrlVisits, shortLink }) {
|
||||||
|
const { shortUrl, loading } = shortUrlVisits;
|
||||||
|
const renderDate = () => (
|
||||||
|
<span>
|
||||||
|
<b id="created" className="visits-header__created-at"><Moment fromNow>{shortUrl.dateCreated}</Moment></b>
|
||||||
|
<UncontrolledTooltip placement="bottom" target="created">
|
||||||
|
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header>
|
||||||
|
<Card className="bg-light" body>
|
||||||
|
<h2>
|
||||||
|
{shortUrl.visitsCount &&
|
||||||
|
<span className="badge badge-main float-right">Visits: {shortUrl.visitsCount}</span>}
|
||||||
|
Visit stats for <ExternalLink href={shortLink}>{shortLink}</ExternalLink>
|
||||||
|
</h2>
|
||||||
|
<hr />
|
||||||
|
{shortUrl.dateCreated && (
|
||||||
|
<div>
|
||||||
|
Created:
|
||||||
|
|
||||||
|
{loading && <small>Loading...</small>}
|
||||||
|
{!loading && renderDate()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
Long URL:
|
||||||
|
|
||||||
|
{loading && <small>Loading...</small>}
|
||||||
|
{!loading && <ExternalLink href={shortUrl.longUrl}>{shortUrl.longUrl}</ExternalLink>}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
VisitsHeader.propTypes = propTypes;
|
3
src/visits/VisitsHeader.scss
Normal file
3
src/visits/VisitsHeader.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.visits-header__created-at {
|
||||||
|
cursor: default;
|
||||||
|
}
|
Loading…
Reference in a new issue