mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-11 10:03:51 +00:00
Added visited URL column on visits table for orphan visits
This commit is contained in:
@@ -33,6 +33,7 @@ export const OrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercure
|
|||||||
baseUrl={url}
|
baseUrl={url}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
exportCsv={exportCsv}
|
exportCsv={exportCsv}
|
||||||
|
isOrphanVisits
|
||||||
>
|
>
|
||||||
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { faCalendarAlt, faMapMarkedAlt, faList, faChartPie, faFileDownload } fro
|
|||||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||||
import { Route, Switch, NavLink as RouterNavLink, Redirect } from 'react-router-dom';
|
import { Route, Switch, NavLink as RouterNavLink, Redirect } from 'react-router-dom';
|
||||||
import { Location } from 'history';
|
import { Location } from 'history';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
||||||
import Message from '../utils/Message';
|
import Message from '../utils/Message';
|
||||||
import { formatIsoDate } from '../utils/helpers/date';
|
import { formatIsoDate } from '../utils/helpers/date';
|
||||||
@@ -31,6 +32,7 @@ export interface VisitsStatsProps {
|
|||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
exportCsv: (visits: NormalizedVisit[]) => void;
|
exportCsv: (visits: NormalizedVisit[]) => void;
|
||||||
|
isOrphanVisits?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VisitsNavLinkProps {
|
interface VisitsNavLinkProps {
|
||||||
@@ -77,7 +79,7 @@ const VisitsNavLink: FC<VisitsNavLinkProps & { to: string }> = ({ subPath, title
|
|||||||
);
|
);
|
||||||
|
|
||||||
const VisitsStats: FC<VisitsStatsProps> = (
|
const VisitsStats: FC<VisitsStatsProps> = (
|
||||||
{ children, visitsInfo, getVisits, cancelGetVisits, baseUrl, domain, settings, exportCsv },
|
{ children, visitsInfo, getVisits, cancelGetVisits, baseUrl, domain, settings, exportCsv, isOrphanVisits = false },
|
||||||
) => {
|
) => {
|
||||||
const initialInterval: DateInterval = settings.visits?.defaultInterval ?? 'last30Days';
|
const initialInterval: DateInterval = settings.visits?.defaultInterval ?? 'last30Days';
|
||||||
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
|
||||||
@@ -171,13 +173,13 @@ const VisitsStats: FC<VisitsStatsProps> = (
|
|||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={`${baseUrl}${sections.byContext.subPath}`}>
|
<Route exact path={`${baseUrl}${sections.byContext.subPath}`}>
|
||||||
<div className="col-xl-4 col-lg-6 mt-4">
|
<div className={classNames('mt-4 col-lg-6', { 'col-xl-4': !isOrphanVisits })}>
|
||||||
<GraphCard title="Operating systems" stats={os} />
|
<GraphCard title="Operating systems" stats={os} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xl-4 col-lg-6 mt-4">
|
<div className={classNames('mt-4 col-lg-6', { 'col-xl-4': !isOrphanVisits })}>
|
||||||
<GraphCard title="Browsers" stats={browsers} />
|
<GraphCard title="Browsers" stats={browsers} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-xl-4 mt-4">
|
<div className={classNames('mt-4', { 'col-xl-4': !isOrphanVisits, 'col-lg-6': isOrphanVisits })}>
|
||||||
<SortableBarGraph
|
<SortableBarGraph
|
||||||
title="Referrers"
|
title="Referrers"
|
||||||
stats={referrers}
|
stats={referrers}
|
||||||
@@ -191,6 +193,18 @@ const VisitsStats: FC<VisitsStatsProps> = (
|
|||||||
onClick={highlightVisitsForProp('referer')}
|
onClick={highlightVisitsForProp('referer')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{isOrphanVisits && (
|
||||||
|
<div className="mt-4 col-lg-6">
|
||||||
|
<SortableBarGraph
|
||||||
|
title="Visited URLs"
|
||||||
|
stats={{}}
|
||||||
|
sortingItems={{
|
||||||
|
visitedUrl: 'Visited URL',
|
||||||
|
amount: 'Visits amount',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route exact path={`${baseUrl}${sections.byLocation.subPath}`}>
|
<Route exact path={`${baseUrl}${sections.byLocation.subPath}`}>
|
||||||
@@ -232,6 +246,7 @@ const VisitsStats: FC<VisitsStatsProps> = (
|
|||||||
visits={normalizedVisits}
|
visits={normalizedVisits}
|
||||||
selectedVisits={highlightedVisits}
|
selectedVisits={highlightedVisits}
|
||||||
setSelectedVisits={setSelectedVisits}
|
setSelectedVisits={setSelectedVisits}
|
||||||
|
isOrphanVisits={isOrphanVisits}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import SimplePaginator from '../common/SimplePaginator';
|
|||||||
import SearchField from '../utils/SearchField';
|
import SearchField from '../utils/SearchField';
|
||||||
import { determineOrderDir, OrderDir } from '../utils/utils';
|
import { determineOrderDir, OrderDir } from '../utils/utils';
|
||||||
import { prettify } from '../utils/helpers/numbers';
|
import { prettify } from '../utils/helpers/numbers';
|
||||||
import { NormalizedVisit } from './types';
|
import { NormalizedOrphanVisit, NormalizedVisit } from './types';
|
||||||
import './VisitsTable.scss';
|
import './VisitsTable.scss';
|
||||||
|
|
||||||
interface VisitsTableProps {
|
interface VisitsTableProps {
|
||||||
@@ -20,9 +20,10 @@ interface VisitsTableProps {
|
|||||||
selectedVisits?: NormalizedVisit[];
|
selectedVisits?: NormalizedVisit[];
|
||||||
setSelectedVisits: (visits: NormalizedVisit[]) => void;
|
setSelectedVisits: (visits: NormalizedVisit[]) => void;
|
||||||
matchMedia?: (query: string) => MediaQueryList;
|
matchMedia?: (query: string) => MediaQueryList;
|
||||||
|
isOrphanVisits?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderableFields = 'date' | 'country' | 'city' | 'browser' | 'os' | 'referer';
|
type OrderableFields = 'date' | 'country' | 'city' | 'browser' | 'os' | 'referer' | 'visitedUrl';
|
||||||
|
|
||||||
interface Order {
|
interface Order {
|
||||||
field?: OrderableFields;
|
field?: OrderableFields;
|
||||||
@@ -30,8 +31,10 @@ interface Order {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 20;
|
||||||
const visitMatchesSearch = ({ browser, os, referer, country, city }: NormalizedVisit, searchTerm: string) =>
|
const visitMatchesSearch = ({ browser, os, referer, country, city, ...rest }: NormalizedVisit, searchTerm: string) =>
|
||||||
`${browser} ${os} ${referer} ${country} ${city}`.toLowerCase().includes(searchTerm.toLowerCase());
|
`${browser} ${os} ${referer} ${country} ${city} ${(rest as NormalizedOrphanVisit).visitedUrl}`.toLowerCase().includes(
|
||||||
|
searchTerm.toLowerCase(),
|
||||||
|
);
|
||||||
const searchVisits = (searchTerm: string, visits: NormalizedVisit[]) =>
|
const searchVisits = (searchTerm: string, visits: NormalizedVisit[]) =>
|
||||||
visits.filter((visit) => visitMatchesSearch(visit, searchTerm));
|
visits.filter((visit) => visitMatchesSearch(visit, searchTerm));
|
||||||
const sortVisits = ({ field, dir }: Order, visits: NormalizedVisit[]) => !field || !dir ? visits : visits.sort(
|
const sortVisits = ({ field, dir }: Order, visits: NormalizedVisit[]) => !field || !dir ? visits : visits.sort(
|
||||||
@@ -39,7 +42,7 @@ const sortVisits = ({ field, dir }: Order, visits: NormalizedVisit[]) => !field
|
|||||||
const greaterThan = dir === 'ASC' ? 1 : -1;
|
const greaterThan = dir === 'ASC' ? 1 : -1;
|
||||||
const smallerThan = dir === 'ASC' ? -1 : 1;
|
const smallerThan = dir === 'ASC' ? -1 : 1;
|
||||||
|
|
||||||
return a[field] > b[field] ? greaterThan : smallerThan;
|
return (a as NormalizedOrphanVisit)[field] > (b as NormalizedOrphanVisit)[field] ? greaterThan : smallerThan;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
const calculateVisits = (allVisits: NormalizedVisit[], searchTerm: string | undefined, order: Order) => {
|
const calculateVisits = (allVisits: NormalizedVisit[], searchTerm: string | undefined, order: Order) => {
|
||||||
@@ -56,6 +59,7 @@ const VisitsTable = ({
|
|||||||
selectedVisits = [],
|
selectedVisits = [],
|
||||||
setSelectedVisits,
|
setSelectedVisits,
|
||||||
matchMedia = window.matchMedia,
|
matchMedia = window.matchMedia,
|
||||||
|
isOrphanVisits = false,
|
||||||
}: VisitsTableProps) => {
|
}: VisitsTableProps) => {
|
||||||
const headerCellsClass = 'visits-table__header-cell visits-table__sticky';
|
const headerCellsClass = 'visits-table__header-cell visits-table__sticky';
|
||||||
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
|
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
|
||||||
@@ -132,9 +136,15 @@ const VisitsTable = ({
|
|||||||
Referrer
|
Referrer
|
||||||
{renderOrderIcon('referer')}
|
{renderOrderIcon('referer')}
|
||||||
</th>
|
</th>
|
||||||
|
{isOrphanVisits && (
|
||||||
|
<th className={headerCellsClass} onClick={orderByColumn('visitedUrl')}>
|
||||||
|
Visited URL
|
||||||
|
{renderOrderIcon('visitedUrl')}
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="p-0">
|
<td colSpan={isOrphanVisits ? 8 : 7} className="p-0">
|
||||||
<SearchField noBorder large={false} onChange={setSearchTerm} />
|
<SearchField noBorder large={false} onChange={setSearchTerm} />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -142,7 +152,7 @@ const VisitsTable = ({
|
|||||||
<tbody>
|
<tbody>
|
||||||
{(!resultSet.visitsGroups[page - 1] || resultSet.visitsGroups[page - 1].length === 0) && (
|
{(!resultSet.visitsGroups[page - 1] || resultSet.visitsGroups[page - 1].length === 0) && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="text-center">
|
<td colSpan={isOrphanVisits ? 8 : 7} className="text-center">
|
||||||
No visits found with current filtering
|
No visits found with current filtering
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -170,6 +180,7 @@ const VisitsTable = ({
|
|||||||
<td>{visit.browser}</td>
|
<td>{visit.browser}</td>
|
||||||
<td>{visit.os}</td>
|
<td>{visit.os}</td>
|
||||||
<td>{visit.referer}</td>
|
<td>{visit.referer}</td>
|
||||||
|
{isOrphanVisits && <td>{(visit as NormalizedOrphanVisit).visitedUrl}</td>}
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -177,7 +188,7 @@ const VisitsTable = ({
|
|||||||
{resultSet.total > PAGE_SIZE && (
|
{resultSet.total > PAGE_SIZE && (
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="visits-table__footer-cell visits-table__sticky">
|
<td colSpan={isOrphanVisits ? 8 : 7} className="visits-table__footer-cell visits-table__sticky">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<SimplePaginator
|
<SimplePaginator
|
||||||
|
|||||||
Reference in New Issue
Block a user