mirror of
https://github.com/umami-software/umami.git
synced 2026-02-06 05:37:20 +01:00
Typescript refactor.
This commit is contained in:
parent
b578162cb6
commit
7c42f0da82
173 changed files with 968 additions and 549 deletions
|
|
@ -36,11 +36,11 @@ export function DataTable({
|
|||
const hasData = Boolean(!isLoading && data?.length);
|
||||
const noResults = Boolean(!isLoading && query && !hasData);
|
||||
|
||||
const handleSearch = query => {
|
||||
const handleSearch = (query: string) => {
|
||||
setParams({ ...params, query, page: params.page ? page : 1 });
|
||||
};
|
||||
|
||||
const handlePageChange = page => {
|
||||
const handlePageChange = (page: number) => {
|
||||
setParams({ ...params, query, page });
|
||||
};
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ export function DataTable({
|
|||
<SearchField
|
||||
className={styles.search}
|
||||
value={query}
|
||||
onChange={handleSearch}
|
||||
onSearch={handleSearch}
|
||||
delay={searchDelay || DEFAULT_SEARCH_DELAY}
|
||||
autoFocus={true}
|
||||
placeholder={formatMessage(labels.search)}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import { Icon, Text, Flexbox } from 'react-basics';
|
|||
import Logo from 'assets/logo.svg';
|
||||
|
||||
export interface EmptyPlaceholderProps {
|
||||
message: string;
|
||||
message?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function EmptyPlaceholder({ message, children }) {
|
||||
export function EmptyPlaceholder({ message, children }: EmptyPlaceholderProps) {
|
||||
return (
|
||||
<Flexbox direction="column" alignItems="center" justifyContent="center" gap={60} height={600}>
|
||||
<Icon size="xl">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
|||
export interface FilterButtonsProps {
|
||||
items: any[];
|
||||
selectedKey?: Key;
|
||||
onSelect: () => void;
|
||||
onSelect: (key: any) => void;
|
||||
}
|
||||
|
||||
export function FilterButtons({ items, selectedKey, onSelect }: FilterButtonsProps) {
|
||||
|
|
|
|||
1
src/components/declarations.d.ts
vendored
1
src/components/declarations.d.ts
vendored
|
|
@ -1,3 +1,4 @@
|
|||
declare module '*.css';
|
||||
declare module '*.svg';
|
||||
declare module '*.json';
|
||||
declare module 'uuid';
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
export function useApiFilter() {
|
||||
const [filter, setFilter] = useState();
|
||||
const [filterType, setFilterType] = useState('All');
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const handleFilterChange = value => setFilter(value);
|
||||
const handlePageChange = value => setPage(value);
|
||||
const handlePageSizeChange = value => setPageSize(value);
|
||||
|
||||
return {
|
||||
filter,
|
||||
setFilter,
|
||||
filterType,
|
||||
setFilterType,
|
||||
page,
|
||||
setPage,
|
||||
pageSize,
|
||||
setPageSize,
|
||||
handleFilterChange,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
};
|
||||
}
|
||||
|
||||
export default useApiFilter;
|
||||
|
|
@ -6,7 +6,7 @@ import websiteStore, { setWebsiteDateRange } from 'store/websites';
|
|||
import appStore, { setDateRange } from 'store/app';
|
||||
import useApi from './useApi';
|
||||
|
||||
export function useDateRange(websiteId: string) {
|
||||
export function useDateRange(websiteId?: string) {
|
||||
const { get } = useApi();
|
||||
const { locale } = useLocale();
|
||||
const websiteConfig = websiteStore(state => state[websiteId]?.dateRange);
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useState, Dispatch, SetStateAction } from 'react';
|
||||
import { useApi } from 'components/hooks/useApi';
|
||||
import { SearchFilter, FilterResult } from 'lib/types';
|
||||
import { FilterResult, SearchFilter } from 'lib/types';
|
||||
|
||||
export interface FilterQueryResult<T> {
|
||||
result: FilterResult<any[]>;
|
||||
result: FilterResult<T>;
|
||||
query: any;
|
||||
params: SearchFilter;
|
||||
setParams: Dispatch<SetStateAction<T | SearchFilter>>;
|
||||
}
|
||||
|
||||
export function useFilterQuery<T>(props = {}): FilterQueryResult<T> {
|
||||
export function useFilterQuery<T>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
...options
|
||||
}: UseQueryOptions): FilterQueryResult<T> {
|
||||
const [params, setParams] = useState<T | SearchFilter>({
|
||||
query: '',
|
||||
page: 1,
|
||||
});
|
||||
const { useQuery } = useApi();
|
||||
const { data, ...query } = useQuery<FilterResult<any[]>>({ ...props });
|
||||
const { data, ...query } = useQuery({
|
||||
queryKey: [...queryKey, params],
|
||||
queryFn: (data: any) => queryFn({ ...data, ...params }),
|
||||
...options,
|
||||
});
|
||||
|
||||
return {
|
||||
result: data,
|
||||
result: data as FilterResult<any>,
|
||||
query,
|
||||
params,
|
||||
setParams,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,12 @@ import { useMemo } from 'react';
|
|||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
import { buildUrl } from 'next-basics';
|
||||
|
||||
export function useNavigation() {
|
||||
export function useNavigation(): {
|
||||
pathname: string;
|
||||
query: { [key: string]: string };
|
||||
router: any;
|
||||
makeUrl: (params: any, reset?: boolean) => string;
|
||||
} {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const params = useSearchParams();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { useState } from 'react';
|
||||
import useApi from './useApi';
|
||||
import useApiFilter from 'components/hooks/useApiFilter';
|
||||
import useFilterQuery from 'components/hooks/useFilterQuery';
|
||||
|
||||
export function useReports() {
|
||||
export function useReports(websiteId?: string) {
|
||||
const [modified, setModified] = useState(Date.now());
|
||||
const { get, useQuery, del, useMutation } = useApi();
|
||||
const { mutate } = useMutation(reportId => del(`/reports/${reportId}`));
|
||||
const { filter, page, pageSize, handleFilterChange, handlePageChange, handlePageSizeChange } =
|
||||
useApiFilter();
|
||||
const { data, error, isLoading } = useQuery({
|
||||
queryKey: ['reports', { modified, filter, page, pageSize }],
|
||||
queryFn: () => get(`/reports`, { filter, page, pageSize }),
|
||||
const { get, del, useMutation } = useApi();
|
||||
const { mutate } = useMutation({ mutationFn: (reportId: string) => del(`/reports/${reportId}`) });
|
||||
const queryResult = useFilterQuery({
|
||||
queryKey: ['reports', { websiteId, modified }],
|
||||
queryFn: (params: any) => {
|
||||
return get(websiteId ? `/websites/${websiteId}/reports` : `/reports`, params);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteReport = id => {
|
||||
const deleteReport = (id: any) => {
|
||||
mutate(id, {
|
||||
onSuccess: () => {
|
||||
setModified(Date.now());
|
||||
|
|
@ -22,16 +22,8 @@ export function useReports() {
|
|||
};
|
||||
|
||||
return {
|
||||
reports: data,
|
||||
error,
|
||||
isLoading,
|
||||
...queryResult,
|
||||
deleteReport,
|
||||
filter,
|
||||
page,
|
||||
pageSize,
|
||||
handleFilterChange,
|
||||
handlePageChange,
|
||||
handlePageSizeChange,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ import useApi from './useApi';
|
|||
|
||||
const selector = (state: { shareToken: string }) => state.shareToken;
|
||||
|
||||
export function useShareToken(shareId: string) {
|
||||
export function useShareToken(shareId: string): {
|
||||
shareToken: any;
|
||||
isLoading?: boolean;
|
||||
error?: Error;
|
||||
} {
|
||||
const shareToken = useStore(selector);
|
||||
const { get, useQuery } = useApi();
|
||||
const { isLoading, error } = useQuery({
|
||||
|
|
|
|||
|
|
@ -3,9 +3,20 @@ import { Icon, Modal, Dropdown, Item, Text, Flexbox } from 'react-basics';
|
|||
import { endOfYear, isSameDay } from 'date-fns';
|
||||
import DatePickerForm from 'components/metrics/DatePickerForm';
|
||||
import useLocale from 'components/hooks/useLocale';
|
||||
import { formatDate } from 'lib/date';
|
||||
import Icons from 'components/icons';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
import Icons from 'components/icons';
|
||||
import { formatDate } from 'lib/date';
|
||||
|
||||
export interface DateFilterProps {
|
||||
value: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
className?: string;
|
||||
onChange?: (value: string) => void;
|
||||
selectedUnit?: string;
|
||||
showAllTime?: boolean;
|
||||
alignment?: 'start' | 'center' | 'end';
|
||||
}
|
||||
|
||||
export function DateFilter({
|
||||
value,
|
||||
|
|
@ -16,7 +27,7 @@ export function DateFilter({
|
|||
selectedUnit,
|
||||
showAllTime = false,
|
||||
alignment = 'end',
|
||||
}) {
|
||||
}: DateFilterProps) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
||||
|
|
@ -65,7 +76,7 @@ export function DateFilter({
|
|||
},
|
||||
].filter(n => n);
|
||||
|
||||
const renderValue = value => {
|
||||
const renderValue = (value: string) => {
|
||||
return value.startsWith('range') ? (
|
||||
<CustomRange
|
||||
startDate={startDate}
|
||||
|
|
@ -78,7 +89,7 @@ export function DateFilter({
|
|||
);
|
||||
};
|
||||
|
||||
const handleChange = value => {
|
||||
const handleChange = (value: string) => {
|
||||
if (value === 'custom') {
|
||||
setShowPicker(true);
|
||||
return;
|
||||
|
|
@ -86,7 +97,7 @@ export function DateFilter({
|
|||
onChange(value);
|
||||
};
|
||||
|
||||
const handlePickerChange = value => {
|
||||
const handlePickerChange = (value: string) => {
|
||||
setShowPicker(false);
|
||||
onChange(value);
|
||||
};
|
||||
|
|
@ -102,7 +113,7 @@ export function DateFilter({
|
|||
value={value}
|
||||
alignment={alignment}
|
||||
placeholder={formatMessage(labels.selectDate)}
|
||||
onChange={handleChange}
|
||||
onChange={key => handleChange(key as any)}
|
||||
>
|
||||
{({ label, value, divider }) => (
|
||||
<Item key={value} divider={divider}>
|
||||
|
|
@ -9,7 +9,7 @@ export function LanguageButton() {
|
|||
const { locale, saveLocale, dir } = useLocale();
|
||||
const items = Object.keys(languages).map(key => ({ ...languages[key], value: key }));
|
||||
|
||||
function handleSelect(value, close, e) {
|
||||
function handleSelect(value: string, close: () => void, e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
saveLocale(value);
|
||||
close();
|
||||
|
|
@ -23,7 +23,7 @@ export function LanguageButton() {
|
|||
</Icon>
|
||||
</Button>
|
||||
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
|
||||
{close => {
|
||||
{(close: () => void) => {
|
||||
return (
|
||||
<div className={styles.menu}>
|
||||
{items.map(({ value, label }) => {
|
||||
|
|
@ -31,7 +31,7 @@ export function LanguageButton() {
|
|||
<div
|
||||
key={value}
|
||||
className={classNames(styles.item, { [styles.selected]: value === locale })}
|
||||
onClick={handleSelect.bind(null, value, close)}
|
||||
onClick={(e: any) => handleSelect(value, close, e)}
|
||||
>
|
||||
<Text>{label}</Text>
|
||||
{value === locale && (
|
||||
|
|
@ -2,7 +2,11 @@ import { Button, Icon, Icons, TooltipPopup } from 'react-basics';
|
|||
import Link from 'next/link';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
export function LogoutButton({ tooltipPosition = 'top' }) {
|
||||
export function LogoutButton({
|
||||
tooltipPosition = 'top',
|
||||
}: {
|
||||
tooltipPosition?: 'top' | 'bottom' | 'left' | 'right';
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
return (
|
||||
<Link href="/src/app/logout/logout">
|
||||
|
|
@ -20,7 +20,7 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
|||
const year = date.getFullYear();
|
||||
const ref = useRef();
|
||||
|
||||
const handleChange = (close, date) => {
|
||||
const handleChange = (close: () => void, date: Date) => {
|
||||
onChange(`range:${startOfMonth(date).getTime()}:${endOfMonth(date).getTime()}`);
|
||||
close();
|
||||
};
|
||||
|
|
@ -53,12 +53,8 @@ export function MonthSelect({ date = new Date(), onChange }) {
|
|||
</Icon>
|
||||
</Button>
|
||||
<Popup className={styles.popup} alignment="start">
|
||||
{close => (
|
||||
<CalendarYearSelect
|
||||
date={date}
|
||||
locale={dateLocale}
|
||||
onSelect={handleChange.bind(null, close)}
|
||||
/>
|
||||
{(close: any) => (
|
||||
<CalendarYearSelect date={date} onSelect={handleChange.bind(null, close)} />
|
||||
)}
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
|
|
@ -4,7 +4,13 @@ import useDateRange from 'components/hooks/useDateRange';
|
|||
import Icons from 'components/icons';
|
||||
import useMessages from 'components/hooks/useMessages';
|
||||
|
||||
export function RefreshButton({ websiteId, isLoading }) {
|
||||
export function RefreshButton({
|
||||
websiteId,
|
||||
isLoading,
|
||||
}: {
|
||||
websiteId: string;
|
||||
isLoading?: boolean;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const [dateRange] = useDateRange(websiteId);
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ import { Button, Icon, Icons } from 'react-basics';
|
|||
import DateFilter from './DateFilter';
|
||||
import styles from './WebsiteDateFilter.module.css';
|
||||
|
||||
export function WebsiteDateFilter({ websiteId }) {
|
||||
export function WebsiteDateFilter({ websiteId }: { websiteId: string }) {
|
||||
const [dateRange, setDateRange] = useDateRange(websiteId);
|
||||
const { value, startDate, endDate, selectedUnit } = dateRange;
|
||||
const isFutureDate =
|
||||
|
|
@ -3,7 +3,13 @@ import useApi from 'components/hooks/useApi';
|
|||
import useMessages from 'components/hooks/useMessages';
|
||||
import styles from './WebsiteSelect.module.css';
|
||||
|
||||
export function WebsiteSelect({ websiteId, onSelect }) {
|
||||
export function WebsiteSelect({
|
||||
websiteId,
|
||||
onSelect,
|
||||
}: {
|
||||
websiteId: string;
|
||||
onSelect?: (key: any) => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { get, useQuery } = useApi();
|
||||
const { data } = useQuery({
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import { mapChildren } from 'react-basics';
|
||||
import styles from './Grid.module.css';
|
||||
|
||||
export function Grid({ className, ...otherProps }) {
|
||||
return <div {...otherProps} className={classNames(styles.grid, className)} />;
|
||||
}
|
||||
|
||||
export function GridRow(props) {
|
||||
const { columns = 'two', className, children, ...otherProps } = props;
|
||||
return (
|
||||
<div {...otherProps} className={classNames(styles.row, className)}>
|
||||
{mapChildren(children, child => {
|
||||
return <div className={classNames(styles.col, { [styles[columns]]: true })}>{child}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
src/components/layout/Grid.tsx
Normal file
34
src/components/layout/Grid.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { CSSProperties } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { mapChildren } from 'react-basics';
|
||||
import styles from './Grid.module.css';
|
||||
|
||||
export interface GridProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export function Grid({ className, style, children }: GridProps) {
|
||||
return (
|
||||
<div className={classNames(styles.grid, className)} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GridRow(props: {
|
||||
[x: string]: any;
|
||||
columns?: 'one' | 'two' | 'three' | 'one-two' | 'two-one';
|
||||
className?: string;
|
||||
children?: any;
|
||||
}) {
|
||||
const { columns = 'two', className, children, ...otherProps } = props;
|
||||
return (
|
||||
<div {...otherProps} className={classNames(styles.row, className)}>
|
||||
{mapChildren(children, child => {
|
||||
return <div className={classNames(styles.col, { [styles[columns]]: true })}>{child}</div>;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,13 +6,21 @@ import Link from 'next/link';
|
|||
import Icons from 'components/icons';
|
||||
import styles from './NavGroup.module.css';
|
||||
|
||||
export interface NavGroupProps {
|
||||
title: string;
|
||||
items: any[];
|
||||
defaultExpanded?: boolean;
|
||||
allowExpand?: boolean;
|
||||
minimized?: boolean;
|
||||
}
|
||||
|
||||
export function NavGroup({
|
||||
title,
|
||||
items,
|
||||
defaultExpanded = true,
|
||||
allowExpand = true,
|
||||
minimized = false,
|
||||
}) {
|
||||
}: NavGroupProps) {
|
||||
const pathname = usePathname();
|
||||
const [expanded, setExpanded] = useState(defaultExpanded);
|
||||
|
||||
|
|
@ -4,6 +4,15 @@ import { usePathname } from 'next/navigation';
|
|||
import Link from 'next/link';
|
||||
import styles from './SideNav.module.css';
|
||||
|
||||
export interface SideNavProps {
|
||||
selectedKey: string;
|
||||
items: any[];
|
||||
shallow?: boolean;
|
||||
scroll?: boolean;
|
||||
className?: boolean;
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
export function SideNav({
|
||||
selectedKey,
|
||||
items,
|
||||
|
|
@ -11,7 +20,7 @@ export function SideNav({
|
|||
scroll = false,
|
||||
className,
|
||||
onSelect = () => {},
|
||||
}) {
|
||||
}: SideNavProps) {
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<Menu
|
||||
|
|
@ -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(() => {
|
||||
|
|
@ -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} />
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
@ -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)}
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
@ -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>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 (
|
||||
|
|
@ -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">
|
||||
|
|
@ -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}
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
@ -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)}
|
||||
/>
|
||||
|
|
@ -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 ? (
|
||||
|
|
@ -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} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
Loading…
Add table
Add a link
Reference in a new issue