From cad719fd235f400d78fe0c4459864498b411f669 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sun, 10 Dec 2023 02:02:24 -0800 Subject: [PATCH] Added search to metrics table. --- .../(main)/websites/[id]/WebsiteDetails.tsx | 4 +- ...ule.css => WebsiteExpandedView.module.css} | 0 ...teMenuView.tsx => WebsiteExpandedView.tsx} | 11 ++--- src/components/hooks/useDateRange.ts | 2 +- src/components/hooks/useFormat.ts | 11 ++++- src/components/layout/SideNav.tsx | 2 +- src/components/metrics/CitiesTable.tsx | 4 +- .../metrics/MetricsTable.module.css | 20 +++++++++ src/components/metrics/MetricsTable.tsx | 38 +++++++++++++--- src/components/metrics/OSTable.tsx | 9 ++-- src/components/metrics/PagesTable.tsx | 25 +++++------ .../metrics/QueryParametersTable.tsx | 45 +++++++++---------- 12 files changed, 111 insertions(+), 60 deletions(-) rename src/app/(main)/websites/[id]/{WebsiteMenuView.module.css => WebsiteExpandedView.module.css} (100%) rename src/app/(main)/websites/[id]/{WebsiteMenuView.tsx => WebsiteExpandedView.tsx} (93%) diff --git a/src/app/(main)/websites/[id]/WebsiteDetails.tsx b/src/app/(main)/websites/[id]/WebsiteDetails.tsx index 4d3a18e7..7d8d2d99 100644 --- a/src/app/(main)/websites/[id]/WebsiteDetails.tsx +++ b/src/app/(main)/websites/[id]/WebsiteDetails.tsx @@ -6,7 +6,7 @@ import FilterTags from 'components/metrics/FilterTags'; import useNavigation from 'components/hooks/useNavigation'; import { useWebsite } from 'components/hooks'; import WebsiteChart from './WebsiteChart'; -import WebsiteMenuView from './WebsiteMenuView'; +import WebsiteExpandedView from './WebsiteExpandedView'; import WebsiteHeader from './WebsiteHeader'; import WebsiteMetricsBar from './WebsiteMetricsBar'; import WebsiteTableView from './WebsiteTableView'; @@ -34,7 +34,7 @@ export default function WebsiteDetails({ websiteId }: { websiteId: string }) { {website && ( <> {!view && } - {view && } + {view && } )} diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.module.css b/src/app/(main)/websites/[id]/WebsiteExpandedView.module.css similarity index 100% rename from src/app/(main)/websites/[id]/WebsiteMenuView.module.css rename to src/app/(main)/websites/[id]/WebsiteExpandedView.module.css diff --git a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx similarity index 93% rename from src/app/(main)/websites/[id]/WebsiteMenuView.tsx rename to src/app/(main)/websites/[id]/WebsiteExpandedView.tsx index 670ea469..e97cd002 100644 --- a/src/app/(main)/websites/[id]/WebsiteMenuView.tsx +++ b/src/app/(main)/websites/[id]/WebsiteExpandedView.tsx @@ -15,7 +15,7 @@ import SideNav from 'components/layout/SideNav'; import useNavigation from 'components/hooks/useNavigation'; import useMessages from 'components/hooks/useMessages'; import LinkButton from 'components/common/LinkButton'; -import styles from './WebsiteMenuView.module.css'; +import styles from './WebsiteExpandedView.module.css'; const views = { url: PagesTable, @@ -33,7 +33,7 @@ const views = { query: QueryParametersTable, }; -export default function WebsiteMenuView({ +export default function WebsiteExpandedView({ websiteId, websiteDomain, }: { @@ -113,11 +113,11 @@ export default function WebsiteMenuView({ const DetailsComponent = views[view] || (() => null); - const handleChange = view => { + const handleChange = (view: any) => { router.push(makeUrl({ view })); }; - const renderValue = value => items.find(({ key }) => key === value)?.label; + const renderValue = (value: string) => items.find(({ key }) => key === value)?.label; return (
@@ -146,9 +146,10 @@ export default function WebsiteMenuView({ websiteDomain={websiteDomain} limit={false} animate={false} - showFilters={true} virtualize={true} itemCount={25} + allowFilter={true} + allowSearch={true} />
diff --git a/src/components/hooks/useDateRange.ts b/src/components/hooks/useDateRange.ts index d8a49331..efaa717f 100644 --- a/src/components/hooks/useDateRange.ts +++ b/src/components/hooks/useDateRange.ts @@ -46,7 +46,7 @@ export function useDateRange(websiteId?: string) { }; return [dateRange, saveDateRange] as [ - { startDate: Date; endDate: Date }, + { startDate: Date; endDate: Date; modified?: number }, (value: string | DateRange) => void, ]; } diff --git a/src/components/hooks/useFormat.ts b/src/components/hooks/useFormat.ts index f804eb72..06585e49 100644 --- a/src/components/hooks/useFormat.ts +++ b/src/components/hooks/useFormat.ts @@ -18,14 +18,19 @@ export function useFormat() { }; const formatRegion = (value: string): string => { - return regions[value] ? regions[value] : value; + const [country] = value.split('-'); + return regions[value] ? `${regions[value]}, ${countryNames[country]}` : value; + }; + + const formatCity = (value: string, country?: string): string => { + return `${value}, ${countryNames[country]}`; }; const formatDevice = (value: string): string => { return formatMessage(labels[value] || labels.unknown); }; - const formatValue = (value: string, type: string): string => { + const formatValue = (value: string, type: string, data?: { [key: string]: any }): string => { switch (type) { case 'browser': return formatBrowser(value); @@ -33,6 +38,8 @@ export function useFormat() { return formatCountry(value); case 'region': return formatRegion(value); + case 'city': + return formatCity(value, data?.country); case 'device': return formatDevice(value); default: diff --git a/src/components/layout/SideNav.tsx b/src/components/layout/SideNav.tsx index f38bdba0..0b5c9856 100644 --- a/src/components/layout/SideNav.tsx +++ b/src/components/layout/SideNav.tsx @@ -9,7 +9,7 @@ export interface SideNavProps { items: any[]; shallow?: boolean; scroll?: boolean; - className?: boolean; + className?: string; onSelect?: () => void; } diff --git a/src/components/metrics/CitiesTable.tsx b/src/components/metrics/CitiesTable.tsx index 69b89962..067e07e9 100644 --- a/src/components/metrics/CitiesTable.tsx +++ b/src/components/metrics/CitiesTable.tsx @@ -11,8 +11,8 @@ export function CitiesTable(props: MetricsTableProps) { const countryNames = useCountryNames(locale); const renderLabel = (city: string, country: string) => { - const name = countryNames[country]; - return name ? `${city}, ${name}` : city; + const countryName = countryNames[country]; + return countryName ? `${city}, ${countryName}` : city; }; const renderLink = ({ x: city, country }) => { diff --git a/src/components/metrics/MetricsTable.module.css b/src/components/metrics/MetricsTable.module.css index c00e4356..f04d9ae4 100644 --- a/src/components/metrics/MetricsTable.module.css +++ b/src/components/metrics/MetricsTable.module.css @@ -6,13 +6,33 @@ flex: 1; } +.actions { + display: flex; + gap: 20px; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} + .footer { display: flex; justify-content: center; } +.search { + max-width: 300px; +} + @media only screen and (max-width: 992px) { .container { min-height: auto; } + + .actions { + flex-direction: column; + } + + .search { + max-width: 100%; + } } diff --git a/src/components/metrics/MetricsTable.tsx b/src/components/metrics/MetricsTable.tsx index d4ad793d..48beac68 100644 --- a/src/components/metrics/MetricsTable.tsx +++ b/src/components/metrics/MetricsTable.tsx @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { Loading, Icon, Text } from 'react-basics'; +import { ReactNode, useMemo, useState } from 'react'; +import { Loading, Icon, Text, SearchField } from 'react-basics'; import classNames from 'classnames'; import useApi from 'components/hooks/useApi'; import { percentFilter } from 'lib/filters'; @@ -12,6 +12,7 @@ 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 useFormat from 'components//hooks/useFormat'; import styles from './MetricsTable.module.css'; export interface MetricsTableProps extends ListTableProps { @@ -22,6 +23,9 @@ export interface MetricsTableProps extends ListTableProps { limit?: number; delay?: number; onDataLoad?: (data: any) => void; + onSearch?: (search: string) => void; + allowSearch?: boolean; + children?: ReactNode; } export function MetricsTable({ @@ -32,8 +36,12 @@ export function MetricsTable({ limit, onDataLoad, delay = null, + allowSearch = false, + children, ...props }: MetricsTableProps) { + const [search, setSearch] = useState(''); + const { formatValue } = useFormat(); const [{ startDate, endDate, modified }] = useDateRange(websiteId); const { makeUrl, @@ -42,7 +50,6 @@ export function MetricsTable({ const { formatMessage, labels } = useMessages(); const { get, useQuery } = useApi(); const { dir } = useLocale(); - const { data, isLoading, isFetched, error } = useQuery({ queryKey: [ 'websites:metrics', @@ -94,24 +101,43 @@ export function MetricsTable({ } } + if (search) { + items = items.filter(({ x, ...data }) => { + const value = formatValue(x, type, data); + + return value.toLowerCase().includes(search.toLowerCase()); + }); + } + items = percentFilter(items); if (limit) { - items = items.filter((e, i) => i < limit); + items = items.slice(0, limit - 1); } return items; } return []; - }, [data, error, dataFilter, limit]); + }, [data, dataFilter, search, limit, formatValue, type]); return (
- {!data && isLoading && !isFetched && } {error && } +
+ {allowSearch && ( + + )} + {children} +
{data && !error && ( )} + {!data && isLoading && !isFetched && }
{data && !error && limit && ( diff --git a/src/components/metrics/OSTable.tsx b/src/components/metrics/OSTable.tsx index c39cba22..102bafd3 100644 --- a/src/components/metrics/OSTable.tsx +++ b/src/components/metrics/OSTable.tsx @@ -1,4 +1,4 @@ -import MetricsTable from './MetricsTable'; +import MetricsTable, { MetricsTableProps } from './MetricsTable'; import FilterLink from 'components/common/FilterLink'; import useMessages from 'components/hooks/useMessages'; @@ -8,7 +8,7 @@ const names = { 'Sun OS': 'SunOS', }; -export function OSTable({ websiteId, limit }: { websiteId: string; limit?: number }) { +export function OSTable(props: MetricsTableProps) { const { formatMessage, labels } = useMessages(); function renderLink({ x: os }) { @@ -28,12 +28,11 @@ export function OSTable({ websiteId, limit }: { websiteId: string; limit?: numbe return ( ); } diff --git a/src/components/metrics/PagesTable.tsx b/src/components/metrics/PagesTable.tsx index 23676467..11379a2e 100644 --- a/src/components/metrics/PagesTable.tsx +++ b/src/components/metrics/PagesTable.tsx @@ -6,10 +6,10 @@ import useNavigation from 'components/hooks/useNavigation'; import { emptyFilter } from 'lib/filters'; export interface PagesTableProps extends MetricsTableProps { - showFilters?: boolean; + allowFilter?: boolean; } -export function PagesTable({ showFilters, ...props }: PagesTableProps) { +export function PagesTable({ allowFilter, ...props }: PagesTableProps) { const { router, makeUrl, @@ -37,17 +37,16 @@ export function PagesTable({ showFilters, ...props }: PagesTableProps) { }; return ( - <> - {showFilters && } - - + + {allowFilter && } + ); } diff --git a/src/components/metrics/QueryParametersTable.tsx b/src/components/metrics/QueryParametersTable.tsx index 65cac664..90489460 100644 --- a/src/components/metrics/QueryParametersTable.tsx +++ b/src/components/metrics/QueryParametersTable.tsx @@ -13,9 +13,9 @@ const filters = { }; export function QueryParametersTable({ - showFilters, + allowFilter, ...props -}: { showFilters: boolean } & MetricsTableProps) { +}: { allowFilter: boolean } & MetricsTableProps) { const [filter, setFilter] = useState(FILTER_COMBINED); const { formatMessage, labels } = useMessages(); @@ -28,27 +28,26 @@ export function QueryParametersTable({ ]; return ( - <> - {showFilters && } - - filter === FILTER_RAW ? ( - x - ) : ( -
-
{safeDecodeURI(p)}
-
{safeDecodeURI(v)}
-
- ) - } - delay={0} - /> - + + filter === FILTER_RAW ? ( + x + ) : ( +
+
{safeDecodeURI(p)}
+
{safeDecodeURI(v)}
+
+ ) + } + delay={0} + > + {allowFilter && } +
); }