Typescript refactor.

This commit is contained in:
Mike Cao 2023-12-03 03:07:03 -08:00
parent b578162cb6
commit 7c42f0da82
173 changed files with 968 additions and 549 deletions

View file

@ -4,14 +4,22 @@ import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import styles from './ActiveUsers.module.css';
export function ActiveUsers({ websiteId, value, refetchInterval = 60000 }) {
export function ActiveUsers({
websiteId,
value,
refetchInterval = 60000,
}: {
websiteId: string;
value?: number;
refetchInterval?: number;
}) {
const { formatMessage, messages } = useMessages();
const { get, useQuery } = useApi();
const { data } = useQuery({
queryKey: ['websites:active', websiteId],
queryFn: () => get(`/websites/${websiteId}/active`),
refetchInterval,
enabled: !!websiteId,
refetchInterval,
});
const count = useMemo(() => {

View file

@ -10,12 +10,28 @@ import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
import { renderNumberLabels } from 'lib/charts';
import styles from './BarChart.module.css';
export interface BarChartProps {
datasets?: any[];
unit?: string;
animationDuration?: number;
stacked?: boolean;
isLoading?: boolean;
renderXLabel?: (label: string, index: number, values: any[]) => string;
renderYLabel?: (label: string, index: number, values: any[]) => string;
XAxisType?: string;
YAxisType?: string;
renderTooltipPopup?: (setTooltipPopup: (data: any) => void, model: any) => void;
onCreate?: (chart: any) => void;
onUpdate?: (chart: any) => void;
className?: string;
}
export function BarChart({
datasets,
datasets = [],
unit,
animationDuration = DEFAULT_ANIMATION_DURATION,
stacked = false,
loading = false,
isLoading = false,
renderXLabel,
renderYLabel,
XAxisType = 'time',
@ -24,7 +40,7 @@ export function BarChart({
onCreate,
onUpdate,
className,
}) {
}: BarChartProps) {
const canvas = useRef();
const chart = useRef(null);
const [tooltip, setTooltipPopup] = useState(null);
@ -85,7 +101,7 @@ export function BarChart({
color: colors.chart.line,
},
ticks: {
color: colors.text,
color: colors.chart.text,
callback: renderYLabel || renderNumberLabels,
},
},
@ -106,16 +122,15 @@ export function BarChart({
const createChart = () => {
Chart.defaults.font.family = 'Inter';
const options = getOptions();
chart.current = new Chart(canvas.current, {
type: 'bar',
data: {
datasets,
},
options,
});
chart.current.options = getOptions();
onCreate?.(chart.current);
};
@ -147,7 +162,7 @@ export function BarChart({
return (
<div className={styles.container}>
<div className={classNames(styles.chart, className)}>
{loading && <Loading position="page" icon="dots" />}
{isLoading && <Loading position="page" icon="dots" />}
<canvas ref={canvas} />
</div>
<Legend chart={chart.current} />

View file

@ -1,9 +1,9 @@
import FilterLink from 'components/common/FilterLink';
import MetricsTable from 'components/metrics/MetricsTable';
import MetricsTable, { MetricsTableProps } from 'components/metrics/MetricsTable';
import useMessages from 'components/hooks/useMessages';
import useFormat from 'components/hooks/useFormat';
export function BrowsersTable({ websiteId, ...props }) {
export function BrowsersTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
const { formatBrowser } = useFormat();
@ -26,7 +26,6 @@ export function BrowsersTable({ websiteId, ...props }) {
title={formatMessage(labels.browsers)}
type="browser"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
renderLabel={renderLink}
/>
);

View file

@ -1,16 +1,16 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import { emptyFilter } from 'lib/filters';
import FilterLink from 'components/common/FilterLink';
import useLocale from 'components/hooks/useLocale';
import useMessages from 'components/hooks/useMessages';
import useCountryNames from 'components/hooks/useCountryNames';
export function CitiesTable({ websiteId, ...props }) {
export function CitiesTable(props: MetricsTableProps) {
const { locale } = useLocale();
const { formatMessage, labels } = useMessages();
const countryNames = useCountryNames(locale);
const renderLabel = (city, country) => {
const renderLabel = (city: string, country: string) => {
const name = countryNames[country];
return name ? `${city}, ${name}` : city;
};
@ -34,7 +34,6 @@ export function CitiesTable({ websiteId, ...props }) {
title={formatMessage(labels.cities)}
type="city"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
dataFilter={emptyFilter}
renderLabel={renderLink}
/>

View file

@ -1,15 +1,24 @@
import FilterLink from 'components/common/FilterLink';
import useCountryNames from 'components/hooks/useCountryNames';
import { useLocale, useMessages, useFormat } from 'components/hooks';
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
export function CountriesTable({ websiteId, ...props }) {
export function CountriesTable({
onDataLoad,
...props
}: {
onDataLoad: (data: any) => void;
} & MetricsTableProps) {
const { locale } = useLocale();
const countryNames = useCountryNames(locale);
const { formatMessage, labels } = useMessages();
const { formatCountry } = useFormat();
function renderLink({ x: code }) {
const handleDataLoad = (data: any) => {
onDataLoad?.(data);
};
const renderLink = ({ x: code }) => {
return (
<FilterLink
id="country"
@ -23,7 +32,7 @@ export function CountriesTable({ websiteId, ...props }) {
/>
</FilterLink>
);
}
};
return (
<MetricsTable
@ -31,8 +40,8 @@ export function CountriesTable({ websiteId, ...props }) {
title={formatMessage(labels.countries)}
type="country"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
renderLabel={renderLink}
onDataLoad={handleDataLoad}
/>
);
}

View file

@ -39,7 +39,7 @@ export function DatePickerForm({
return (
<div className={styles.container}>
<div className={styles.filter}>
<ButtonGroup selectedKey={selected} onSelect={setSelected}>
<ButtonGroup selectedKey={selected} onSelect={key => setSelected(key as any)}>
<Button key={FILTER_DAY}>{formatMessage(labels.singleDay)}</Button>
<Button key={FILTER_RANGE}>{formatMessage(labels.dateRange)}</Button>
</ButtonGroup>

View file

@ -1,9 +1,9 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import FilterLink from 'components/common/FilterLink';
import useMessages from 'components/hooks/useMessages';
import { useFormat } from 'components/hooks';
export function DevicesTable({ websiteId, ...props }) {
export function DevicesTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
const { formatDevice } = useFormat();
@ -26,7 +26,6 @@ export function DevicesTable({ websiteId, ...props }) {
title={formatMessage(labels.devices)}
type="device"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
renderLabel={renderLink}
/>
);

View file

@ -7,7 +7,13 @@ import { useApi, useLocale, useDateRange, useTimezone, useNavigation } from 'com
import { EVENT_COLORS } from 'lib/constants';
import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts';
export function EventsChart({ websiteId, className, token }) {
export interface EventsChartProps {
websiteId: string;
className?: string;
token?: string;
}
export function EventsChart({ websiteId, className, token }: EventsChartProps) {
const { get, useQuery } = useApi();
const [{ startDate, endDate, unit, modified }] = useDateRange(websiteId);
const { locale } = useLocale();
@ -71,7 +77,6 @@ export function EventsChart({ websiteId, className, token }) {
className={className}
datasets={datasets}
unit={unit}
height={300}
loading={isLoading}
stacked
renderXLabel={renderDateLabels(unit, locale)}

View file

@ -1,10 +1,10 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import useMessages from 'components/hooks/useMessages';
export function EventsTable({ websiteId, ...props }) {
export function EventsTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
function handleDataLoad(data) {
function handleDataLoad(data: any) {
props.onDataLoad?.(data);
}
@ -14,7 +14,6 @@ export function EventsTable({ websiteId, ...props }) {
title={formatMessage(labels.events)}
type="event"
metric={formatMessage(labels.actions)}
websiteId={websiteId}
onDataLoad={handleDataLoad}
/>
);

View file

@ -18,7 +18,7 @@ export function FilterTags({ params }) {
return null;
}
function handleCloseFilter(param) {
function handleCloseFilter(param?: string) {
if (!param) {
router.push(makeUrl({ view }, true));
} else {
@ -44,7 +44,7 @@ export function FilterTags({ params }) {
</div>
);
})}
<Button size="sm" variant="quiet" onClick={() => handleCloseFilter()}>
<Button size="sm" variant="quiet" onClick={handleCloseFilter}>
<Icon>
<Icons.Close />
</Icon>

View file

@ -1,10 +1,13 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import { percentFilter } from 'lib/filters';
import useLanguageNames from 'components/hooks/useLanguageNames';
import useLocale from 'components/hooks/useLocale';
import useMessages from 'components/hooks/useMessages';
export function LanguagesTable({ websiteId, onDataLoad, ...props }) {
export function LanguagesTable({
onDataLoad,
...props
}: { onDataLoad: (data: any) => void } & MetricsTableProps) {
const { formatMessage, labels } = useMessages();
const { locale } = useLocale();
const languageNames = useLanguageNames(locale);
@ -19,7 +22,6 @@ export function LanguagesTable({ websiteId, onDataLoad, ...props }) {
title={formatMessage(labels.languages)}
type="language"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
onDataLoad={data => onDataLoad?.(percentFilter(data))}
renderLabel={renderLabel}
/>

View file

@ -5,9 +5,22 @@ import Empty from 'components/common/Empty';
import { formatLongNumber } from 'lib/format';
import useMessages from 'components/hooks/useMessages';
import styles from './ListTable.module.css';
import { ReactNode } from 'react';
const ITEM_SIZE = 30;
export interface ListTableProps {
data?: any[];
title?: string;
metric?: string;
className?: string;
renderLabel?: (row: any) => ReactNode;
animate?: boolean;
virtualize?: boolean;
showPercentage?: boolean;
itemCount?: number;
}
export function ListTable({
data = [],
title,
@ -18,7 +31,7 @@ export function ListTable({
virtualize = false,
showPercentage = true,
itemCount = 10,
}) {
}: ListTableProps) {
const { formatMessage, labels } = useMessages();
const getRow = row => {
@ -76,7 +89,7 @@ const AnimatedRow = ({ label, value = 0, percent, animate, showPercentage = true
<div className={styles.row}>
<div className={styles.label}>{label}</div>
<div className={styles.value}>
<animated.div className={styles.value} title={props?.y}>
<animated.div className={styles.value} title={props?.y as any}>
{props.y?.to(formatLongNumber)}
</animated.div>
</div>

View file

@ -3,6 +3,16 @@ import { useSpring, animated } from '@react-spring/web';
import { formatNumber } from 'lib/format';
import styles from './MetricCard.module.css';
export interface MetricCardProps {
value: number;
change?: number;
label: string;
reverseColors?: boolean;
format?: typeof formatNumber;
hideComparison?: boolean;
className?: string;
}
export const MetricCard = ({
value = 0,
change = 0,
@ -11,13 +21,13 @@ export const MetricCard = ({
format = formatNumber,
hideComparison = false,
className,
}) => {
}: MetricCardProps) => {
const props = useSpring({ x: Number(value) || 0, from: { x: 0 } });
const changeProps = useSpring({ x: Number(change) || 0, from: { x: 0 } });
return (
<div className={classNames(styles.card, className)}>
<animated.div className={styles.value} title={props?.x}>
<animated.div className={styles.value} title={props?.x as any}>
{props?.x?.to(x => format(x))}
</animated.div>
<div className={styles.label}>
@ -29,7 +39,7 @@ export const MetricCard = ({
[styles.negative]: change * (reverseColors ? -1 : 1) < 0,
[styles.plusSign]: change > 0,
})}
title={changeProps?.x}
title={changeProps?.x as any}
>
{changeProps?.x?.to(x => format(x))}
</animated.span>

View file

@ -1,9 +1,17 @@
import { ReactNode } from 'react';
import { Loading, cloneChildren } from 'react-basics';
import ErrorMessage from 'components/common/ErrorMessage';
import { formatLongNumber } from 'lib/format';
import styles from './MetricsBar.module.css';
export function MetricsBar({ children, isLoading, isFetched, error }) {
export interface MetricsBarProps {
isLoading?: boolean;
isFetched?: boolean;
error?: unknown;
children?: ReactNode;
}
export function MetricsBar({ children, isLoading, isFetched, error }: MetricsBarProps) {
const formatFunc = n => (n >= 0 ? formatLongNumber(n) : `-${formatLongNumber(Math.abs(n))}`);
return (

View file

@ -1,6 +1,5 @@
import { useMemo } from 'react';
import { Loading, Icon, Text } from 'react-basics';
import firstBy from 'thenby';
import classNames from 'classnames';
import useApi from 'components/hooks/useApi';
import { percentFilter } from 'lib/filters';
@ -8,24 +7,33 @@ import useDateRange from 'components/hooks/useDateRange';
import useNavigation from 'components/hooks/useNavigation';
import ErrorMessage from 'components/common/ErrorMessage';
import LinkButton from 'components/common/LinkButton';
import ListTable from './ListTable';
import ListTable, { ListTableProps } from './ListTable';
import { DEFAULT_ANIMATION_DURATION } from 'lib/constants';
import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages';
import useLocale from 'components/hooks/useLocale';
import styles from './MetricsTable.module.css';
export interface MetricsTableProps extends ListTableProps {
websiteId: string;
type?: string;
className?: string;
dataFilter?: (data: any) => any;
limit?: number;
delay?: number;
onDataLoad?: (data: any) => void;
}
export function MetricsTable({
websiteId,
type,
className,
dataFilter,
filterOptions,
limit,
onDataLoad,
delay = null,
...props
}) {
}: MetricsTableProps) {
const [{ startDate, endDate, modified }] = useDateRange(websiteId);
const {
makeUrl,
@ -53,26 +61,28 @@ export function MetricsTable({
city,
},
],
queryFn: () => {
queryFn: async () => {
const filters = { url, title, referrer, os, browser, device, country, region, city };
filters[type] = undefined;
onDataLoad?.();
return get(`/websites/${websiteId}/metrics`, {
const data = await get(`/websites/${websiteId}/metrics`, {
type,
startAt: +startDate,
endAt: +endDate,
...filters,
});
onDataLoad?.(data);
return data;
},
retryDelay: delay || DEFAULT_ANIMATION_DURATION,
});
const filteredData = useMemo(() => {
if (data) {
let items = data;
let items: any[] = data;
if (dataFilter) {
if (Array.isArray(dataFilter)) {
@ -89,20 +99,19 @@ export function MetricsTable({
if (limit) {
items = items.filter((e, i) => i < limit);
}
if (filterOptions?.sort === false) {
return items;
}
return items.sort(firstBy('y', -1).thenBy('x'));
return items;
}
return [];
}, [data, error, dataFilter, filterOptions, limit]);
}, [data, error, dataFilter, limit]);
return (
<div className={classNames(styles.container, className)}>
{!data && isLoading && !isFetched && <Loading icon="dots" />}
{error && <ErrorMessage />}
{data && !error && <ListTable {...props} data={filteredData} className={className} />}
{data && !error && (
<ListTable {...(props as ListTableProps)} data={filteredData} className={className} />
)}
<div className={styles.footer}>
{data && !error && limit && (
<LinkButton href={makeUrl({ view: type })} variant="quiet">

View file

@ -2,7 +2,7 @@ import MetricsTable from './MetricsTable';
import FilterLink from 'components/common/FilterLink';
import useMessages from 'components/hooks/useMessages';
export function OSTable({ websiteId, ...props }) {
export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) {
const { formatMessage, labels } = useMessages();
function renderLink({ x: os }) {
@ -10,7 +10,7 @@ export function OSTable({ websiteId, ...props }) {
<FilterLink id="os" value={os}>
<img
src={`${process.env.basePath}/images/os/${
os?.toLowerCase().replaceAll(/[^\w]+/g, '-') || 'unknown'
os?.toLowerCase().replaceAll(/\W/g, '-') || 'unknown'
}.png`}
alt={os}
width={16}
@ -22,8 +22,8 @@ export function OSTable({ websiteId, ...props }) {
return (
<MetricsTable
{...props}
websiteId={websiteId}
limit={limit}
title={formatMessage(labels.os)}
metric={formatMessage(labels.visitors)}
renderLabel={renderLink}

View file

@ -1,11 +1,15 @@
import FilterLink from 'components/common/FilterLink';
import FilterButtons from 'components/common/FilterButtons';
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import useMessages from 'components/hooks/useMessages';
import useNavigation from 'components/hooks/useNavigation';
import { emptyFilter } from 'lib/filters';
export function PagesTable({ websiteId, showFilters, ...props }) {
export interface PagesTableProps extends MetricsTableProps {
showFilters?: boolean;
}
export function PagesTable({ showFilters, ...props }: PagesTableProps) {
const {
router,
makeUrl,
@ -40,7 +44,6 @@ export function PagesTable({ websiteId, showFilters, ...props }) {
title={formatMessage(labels.pages)}
type={view}
metric={formatMessage(labels.views)}
websiteId={websiteId}
dataFilter={emptyFilter}
renderLabel={renderLink}
/>

View file

@ -1,9 +1,18 @@
import { useMemo } from 'react';
import BarChart from './BarChart';
import BarChart, { BarChartProps } from './BarChart';
import { useLocale, useTheme, useMessages } from 'components/hooks';
import { renderDateLabels, renderStatusTooltipPopup } from 'lib/charts';
export function PageviewsChart({ websiteId, data, unit, loading, ...props }) {
export interface PageviewsChartProps extends BarChartProps {
data: {
sessions: any[];
pageviews: any[];
};
unit: string;
isLoading?: boolean;
}
export function PageviewsChart({ data, unit, isLoading, ...props }: PageviewsChartProps) {
const { formatMessage, labels } = useMessages();
const { colors } = useTheme();
const { locale } = useLocale();
@ -30,10 +39,9 @@ export function PageviewsChart({ websiteId, data, unit, loading, ...props }) {
return (
<BarChart
{...props}
key={websiteId}
datasets={datasets}
unit={unit}
loading={loading}
isLoading={isLoading}
renderXLabel={renderDateLabels(unit, locale)}
renderTooltipPopup={renderStatusTooltipPopup(unit, locale)}
/>

View file

@ -3,7 +3,7 @@ import { safeDecodeURI } from 'next-basics';
import FilterButtons from 'components/common/FilterButtons';
import { emptyFilter, paramFilter } from 'lib/filters';
import { FILTER_RAW, FILTER_COMBINED } from 'lib/constants';
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import useMessages from 'components/hooks/useMessages';
import styles from './QueryParametersTable.module.css';
@ -12,7 +12,10 @@ const filters = {
[FILTER_COMBINED]: [emptyFilter, paramFilter],
};
export function QueryParametersTable({ websiteId, showFilters, ...props }) {
export function QueryParametersTable({
showFilters,
...props
}: { showFilters: boolean } & MetricsTableProps) {
const [filter, setFilter] = useState(FILTER_COMBINED);
const { formatMessage, labels } = useMessages();
@ -32,7 +35,6 @@ export function QueryParametersTable({ websiteId, showFilters, ...props }) {
title={formatMessage(labels.query)}
type="query"
metric={formatMessage(labels.views)}
websiteId={websiteId}
dataFilter={filters[filter]}
renderLabel={({ x, p, v }) =>
filter === FILTER_RAW ? (

View file

@ -4,7 +4,7 @@ import PageviewsChart from './PageviewsChart';
import { getDateArray } from 'lib/date';
import { DEFAULT_ANIMATION_DURATION, REALTIME_RANGE } from 'lib/constants';
function mapData(data) {
function mapData(data: any[]) {
let last = 0;
const arr = [];
@ -23,7 +23,15 @@ function mapData(data) {
return arr;
}
export function RealtimeChart({ data, unit, ...props }) {
export interface RealtimeChartProps {
data: {
pageviews: any[];
visitors: any[];
};
unit: string;
}
export function RealtimeChart({ data, unit, ...props }: RealtimeChartProps) {
const endDate = startOfMinute(new Date());
const startDate = subMinutes(endDate, REALTIME_RANGE);
const prevEndDate = useRef(endDate);
@ -49,13 +57,7 @@ export function RealtimeChart({ data, unit, ...props }) {
}, [endDate]);
return (
<PageviewsChart
{...props}
height={200}
unit={unit}
data={chartData}
animationDuration={animationDuration}
/>
<PageviewsChart {...props} unit={unit} data={chartData} animationDuration={animationDuration} />
);
}

View file

@ -1,8 +1,8 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import FilterLink from 'components/common/FilterLink';
import useMessages from 'components/hooks/useMessages';
export function ReferrersTable({ websiteId, ...props }) {
export function ReferrersTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
const renderLink = ({ x: referrer }) => {
@ -23,7 +23,6 @@ export function ReferrersTable({ websiteId, ...props }) {
title={formatMessage(labels.referrers)}
type="referrer"
metric={formatMessage(labels.views)}
websiteId={websiteId}
renderLabel={renderLink}
/>
</>

View file

@ -3,15 +3,15 @@ import { emptyFilter } from 'lib/filters';
import useLocale from 'components/hooks/useLocale';
import useMessages from 'components/hooks/useMessages';
import useCountryNames from 'components/hooks/useCountryNames';
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import regions from 'public/iso-3166-2.json';
export function RegionsTable({ websiteId, ...props }) {
export function RegionsTable(props: MetricsTableProps) {
const { locale } = useLocale();
const { formatMessage, labels } = useMessages();
const countryNames = useCountryNames(locale);
const renderLabel = (code, country) => {
const renderLabel = (code: string, country: string) => {
const region = code.includes('-') ? code : `${country}-${code}`;
return regions[region] ? `${regions[region]}, ${countryNames[country]}` : region;
};
@ -33,7 +33,6 @@ export function RegionsTable({ websiteId, ...props }) {
title={formatMessage(labels.regions)}
type="region"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
dataFilter={emptyFilter}
renderLabel={renderLink}
/>

View file

@ -1,7 +1,7 @@
import MetricsTable from './MetricsTable';
import MetricsTable, { MetricsTableProps } from './MetricsTable';
import useMessages from 'components/hooks/useMessages';
export function ScreenTable({ websiteId, ...props }) {
export function ScreenTable(props: MetricsTableProps) {
const { formatMessage, labels } = useMessages();
return (
@ -10,7 +10,6 @@ export function ScreenTable({ websiteId, ...props }) {
title={formatMessage(labels.screens)}
type="screen"
metric={formatMessage(labels.visitors)}
websiteId={websiteId}
/>
);
}

View file

@ -12,7 +12,7 @@ import { formatLongNumber } from 'lib/format';
import { percentFilter } from 'lib/filters';
import styles from './WorldMap.module.css';
export function WorldMap({ data, className }) {
export function WorldMap({ data = [], className }: { data?: any[]; className?: string }) {
const [tooltip, setTooltipPopup] = useState();
const { theme, colors } = useTheme();
const { locale } = useLocale();
@ -21,7 +21,7 @@ export function WorldMap({ data, className }) {
const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale);
const metrics = useMemo(() => (data ? percentFilter(data) : []), [data]);
function getFillColor(code) {
function getFillColor(code: string) {
if (code === 'AQ') return;
const country = metrics?.find(({ x }) => x === code);
@ -41,7 +41,9 @@ export function WorldMap({ data, className }) {
function handleHover(code) {
if (code === 'AQ') return;
const country = metrics?.find(({ x }) => x === code);
setTooltipPopup(`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} ${visitorsLabel}`);
setTooltipPopup(
`${countryNames[code]}: ${formatLongNumber(country?.y || 0)} ${visitorsLabel}` as any,
);
}
return (