mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 18:27:25 +03:00
Ensured info for selected visit in visits table gets highlighted in bar charts
This commit is contained in:
parent
bd4255108d
commit
f5cc1abe75
5 changed files with 56 additions and 15 deletions
|
@ -2,7 +2,7 @@ import { Card, CardHeader, CardBody, CardFooter } from 'reactstrap';
|
|||
import { Doughnut, HorizontalBar } from 'react-chartjs-2';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { keys, values } from 'ramda';
|
||||
import { keys, values, zipObj } from 'ramda';
|
||||
import './GraphCard.scss';
|
||||
|
||||
const propTypes = {
|
||||
|
@ -11,9 +11,10 @@ const propTypes = {
|
|||
isBarChart: PropTypes.bool,
|
||||
stats: PropTypes.object,
|
||||
max: PropTypes.number,
|
||||
highlightedStats: PropTypes.object,
|
||||
};
|
||||
|
||||
const generateGraphData = (title, isBarChart, labels, data) => ({
|
||||
const generateGraphData = (title, isBarChart, labels, data, highlightedData) => ({
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
|
@ -31,23 +32,41 @@ const generateGraphData = (title, isBarChart, labels, data) => ({
|
|||
borderColor: isBarChart ? 'rgba(70, 150, 229, 1)' : 'white',
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
highlightedData && {
|
||||
title,
|
||||
label: 'Selected',
|
||||
data: highlightedData,
|
||||
backgroundColor: 'rgba(247, 127, 40, 0.4)',
|
||||
borderColor: '#F77F28',
|
||||
borderWidth: 2,
|
||||
},
|
||||
].filter(Boolean),
|
||||
});
|
||||
|
||||
const dropLabelIfHidden = (label) => label.startsWith('hidden') ? '' : label;
|
||||
|
||||
const renderGraph = (title, isBarChart, stats, max) => {
|
||||
const renderGraph = (title, isBarChart, stats, max, highlightedStats) => {
|
||||
const Component = isBarChart ? HorizontalBar : Doughnut;
|
||||
const labels = keys(stats).map(dropLabelIfHidden);
|
||||
const data = values(stats);
|
||||
const data = values(!highlightedStats ? stats : keys(highlightedStats).reduce((acc, highlightedKey) => {
|
||||
if (acc[highlightedKey]) {
|
||||
acc[highlightedKey] -= 1;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, stats));
|
||||
const highlightedData = highlightedStats && values({ ...zipObj(labels, labels.map(() => 0)), ...highlightedStats });
|
||||
|
||||
const options = {
|
||||
legend: isBarChart ? { display: false } : { position: 'right' },
|
||||
scales: isBarChart && {
|
||||
xAxes: [
|
||||
{
|
||||
ticks: { beginAtZero: true, max },
|
||||
stacked: true,
|
||||
},
|
||||
],
|
||||
yAxes: [{ stacked: true }],
|
||||
},
|
||||
tooltips: {
|
||||
intersect: !isBarChart,
|
||||
|
@ -56,17 +75,17 @@ const renderGraph = (title, isBarChart, stats, max) => {
|
|||
filter: ({ yLabel }) => !isBarChart || yLabel !== '',
|
||||
},
|
||||
};
|
||||
const graphData = generateGraphData(title, isBarChart, labels, data);
|
||||
const graphData = generateGraphData(title, isBarChart, labels, data, highlightedData);
|
||||
const height = isBarChart && labels.length > 20 ? labels.length * 8 : null;
|
||||
|
||||
// Provide a key based on the height, so that every time the dataset changes, a new graph is rendered
|
||||
return <Component key={height} data={graphData} options={options} height={height} />;
|
||||
};
|
||||
|
||||
const GraphCard = ({ title, footer, isBarChart, stats, max }) => (
|
||||
const GraphCard = ({ title, footer, isBarChart, stats, max, highlightedStats }) => (
|
||||
<Card className="mt-4">
|
||||
<CardHeader className="graph-card__header">{typeof title === 'function' ? title() : title}</CardHeader>
|
||||
<CardBody>{renderGraph(title, isBarChart, stats, max)}</CardBody>
|
||||
<CardBody>{renderGraph(title, isBarChart, stats, max, highlightedStats)}</CardBody>
|
||||
{footer && <CardFooter className="graph-card__footer--sticky">{footer}</CardFooter>}
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,8 @@ import GraphCard from './GraphCard';
|
|||
import { shortUrlDetailType } from './reducers/shortUrlDetail';
|
||||
import VisitsTable from './VisitsTable';
|
||||
|
||||
const highlightedVisitToStats = (highlightedVisit, prop) => highlightedVisit && { [highlightedVisit[prop]]: 1 };
|
||||
|
||||
const ShortUrlVisits = (
|
||||
{ processStatsFromVisits },
|
||||
OpenMapModalBtn
|
||||
|
@ -40,6 +42,7 @@ const ShortUrlVisits = (
|
|||
showTable: false,
|
||||
tableIsSticky: false,
|
||||
isMobileDevice: false,
|
||||
highlightedVisit: undefined,
|
||||
};
|
||||
|
||||
loadVisits = (loadDetail = false) => {
|
||||
|
@ -114,9 +117,10 @@ const ShortUrlVisits = (
|
|||
</div>
|
||||
<div className="col-xl-4">
|
||||
<SortableBarGraph
|
||||
title="Referrers"
|
||||
stats={referrers}
|
||||
withPagination={false}
|
||||
title="Referrers"
|
||||
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'referer')}
|
||||
sortingItems={{
|
||||
name: 'Referrer name',
|
||||
amount: 'Visits amount',
|
||||
|
@ -125,8 +129,9 @@ const ShortUrlVisits = (
|
|||
</div>
|
||||
<div className="col-lg-6">
|
||||
<SortableBarGraph
|
||||
stats={countries}
|
||||
title="Countries"
|
||||
stats={countries}
|
||||
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'country')}
|
||||
sortingItems={{
|
||||
name: 'Country name',
|
||||
amount: 'Visits amount',
|
||||
|
@ -135,8 +140,9 @@ const ShortUrlVisits = (
|
|||
</div>
|
||||
<div className="col-lg-6">
|
||||
<SortableBarGraph
|
||||
stats={cities}
|
||||
title="Cities"
|
||||
stats={cities}
|
||||
highlightedStats={highlightedVisitToStats(this.state.highlightedVisit, 'city')}
|
||||
extraHeaderContent={(activeCities) =>
|
||||
mapLocations.length > 0 &&
|
||||
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
|
||||
|
@ -188,7 +194,11 @@ const ShortUrlVisits = (
|
|||
onEntered={() => this.setState({ tableIsSticky: true })}
|
||||
onExiting={() => this.setState({ tableIsSticky: false })}
|
||||
>
|
||||
<VisitsTable visits={visits} isSticky={this.state.tableIsSticky} />
|
||||
<VisitsTable
|
||||
visits={visits}
|
||||
isSticky={this.state.tableIsSticky}
|
||||
onVisitSelected={(highlightedVisit) => this.setState({ highlightedVisit })}
|
||||
/>
|
||||
</Collapse>
|
||||
)}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ const pickValueFromPair = ([ , value ]) => value;
|
|||
export default class SortableBarGraph extends React.Component {
|
||||
static propTypes = {
|
||||
stats: PropTypes.object.isRequired,
|
||||
highlightedStats: PropTypes.object,
|
||||
title: PropTypes.string.isRequired,
|
||||
sortingItems: PropTypes.object.isRequired,
|
||||
extraHeaderContent: PropTypes.func,
|
||||
|
@ -73,7 +74,7 @@ export default class SortableBarGraph extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { stats, sortingItems, title, extraHeaderContent, withPagination = true } = this.props;
|
||||
const { stats, sortingItems, title, extraHeaderContent, highlightedStats, withPagination = true } = this.props;
|
||||
const { currentPageStats, pagination, max } = this.determineStats(stats, sortingItems);
|
||||
const activeCities = keys(currentPageStats);
|
||||
const computeTitle = () => (
|
||||
|
@ -107,6 +108,15 @@ export default class SortableBarGraph extends React.Component {
|
|||
</React.Fragment>
|
||||
);
|
||||
|
||||
return <GraphCard isBarChart title={computeTitle} stats={currentPageStats} footer={pagination} max={max} />;
|
||||
return (
|
||||
<GraphCard
|
||||
isBarChart
|
||||
title={computeTitle}
|
||||
stats={currentPageStats}
|
||||
footer={pagination}
|
||||
max={max}
|
||||
highlightedStats={highlightedStats}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ const VisitsTable = ({ visits, onVisitSelected, isSticky = false, matchMedia = w
|
|||
'visits-table__sticky': isSticky,
|
||||
})}
|
||||
>
|
||||
<FontAwesomeIcon icon={checkIcon} />
|
||||
<FontAwesomeIcon icon={checkIcon} className={classNames({ 'text-primary': selectedVisit !== undefined })} />
|
||||
</th>
|
||||
<th className={headerCellsClass} onClick={orderByColumn('date')}>
|
||||
Date
|
||||
|
|
|
@ -59,8 +59,10 @@ describe('<GraphCard />', () => {
|
|||
xAxes: [
|
||||
{
|
||||
ticks: { beginAtZero: true },
|
||||
stacked: true,
|
||||
},
|
||||
],
|
||||
yAxes: [{ stacked: true }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue