From 4bc27f1edc968ac9f975a3e74143b075447c21a0 Mon Sep 17 00:00:00 2001 From: Sajid Mehmood Date: Mon, 27 Jul 2026 14:14:42 +0000 Subject: [PATCH 1/2] Make world map countries clickable to filter by country Clicking a country on the world map now applies a `country=eq.` filter, the same param the country metrics table uses, so the whole dashboard narrows to that country. Clicking the selected country again clears the filter, and the selected country is highlighted on the map. Panning the map is not treated as a click, and filtering is disabled on the realtime map since realtime data ignores filters. Run-on: Niteshift Co-Authored-By: Claude Opus 5 --- .../[websiteId]/realtime/RealtimePage.tsx | 2 +- src/components/metrics/WorldMap.tsx | 45 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx index 6220c6957..d9933d0e6 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimePage.tsx @@ -50,7 +50,7 @@ export function RealtimePage({ websiteId }: { websiteId: string }) { - + diff --git a/src/components/metrics/WorldMap.tsx b/src/components/metrics/WorldMap.tsx index 3c8fadb80..73a684d65 100644 --- a/src/components/metrics/WorldMap.tsx +++ b/src/components/metrics/WorldMap.tsx @@ -1,11 +1,12 @@ import { Column, type ColumnProps, FloatingTooltip, useTheme } from '@umami/react-zen'; import { colord } from 'colord'; -import { useMemo, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps'; import { useCountryNames, useLocale, useMessages, + useNavigation, useWebsiteMetricsQuery, } from '@/components/hooks'; import { getThemeColors } from '@/lib/colors'; @@ -16,15 +17,19 @@ import { formatLongNumber } from '@/lib/format'; export interface WorldMapProps extends ColumnProps { websiteId?: string; data?: any[]; + allowFilter?: boolean; } -export function WorldMap({ websiteId, data, ...props }: WorldMapProps) { +export function WorldMap({ websiteId, data, allowFilter = true, ...props }: WorldMapProps) { const [tooltip, setTooltipPopup] = useState(); const { theme } = useTheme(); const { colors } = getThemeColors(theme); const { locale } = useLocale(); const { formatMessage, labels } = useMessages(); const { countryNames } = useCountryNames(locale); + const { router, updateParams, query } = useNavigation(); + const pointerRef = useRef<{ x: number; y: number }>(null); + const selectedCountry = allowFilter ? query.country?.replace(/^eq\./, '') : null; const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale); const unknownLabel = formatMessage(labels.unknown); @@ -41,6 +46,10 @@ export function WorldMap({ websiteId, data, ...props }: WorldMapProps) { if (code === 'AQ') return; const country = metrics?.find(({ x }) => x === code); + if (code === selectedCountry) { + return colors.map.baseColor; + } + if (!country) { return colors.map.fillColor; } @@ -54,6 +63,10 @@ export function WorldMap({ websiteId, data, ...props }: WorldMapProps) { return code === 'AQ' ? 0 : 1; }; + const isClickable = (code: string) => { + return Boolean(allowFilter && code && code !== 'AQ'); + }; + const handleHover = (code: string) => { if (code === 'AQ') return; const country = metrics?.find(({ x }) => x === code); @@ -64,6 +77,26 @@ export function WorldMap({ websiteId, data, ...props }: WorldMapProps) { ); }; + const handlePointerDown = (e: { clientX: number; clientY: number }) => { + pointerRef.current = { x: e.clientX, y: e.clientY }; + }; + + const handleClick = (code: string, e: { clientX: number; clientY: number }) => { + if (!isClickable(code)) return; + + // ignore clicks that are the end of a pan/drag on the map + const start = pointerRef.current; + pointerRef.current = null; + + if (start && Math.abs(e.clientX - start.x) + Math.abs(e.clientY - start.y) > 4) { + return; + } + + router.replace(updateParams({ country: code === selectedCountry ? undefined : `eq.${code}` }), { + scroll: false, + }); + }; + return ( { return geographies.map(geo => { const code = ISO_COUNTRIES[geo.id]; + const cursor = isClickable(code) ? 'pointer' : 'default'; return ( handleHover(code)} onMouseOut={() => setTooltipPopup(null)} + onMouseDown={handlePointerDown} + onClick={(e: any) => handleClick(code, e)} /> ); }); From 669ab9ad956370cf1bb8648a29b4edbdf1bcc44d Mon Sep 17 00:00:00 2001 From: Sajid Mehmood Date: Mon, 27 Jul 2026 14:21:06 +0000 Subject: [PATCH 2/2] Fix map regions without an ISO code appearing selected Regions missing from ISO_COUNTRIES (Antarctica, Somaliland, South Sudan) have an undefined code, which compared equal to the undefined selectedCountry when no filter was active and painted them in the full highlight color. Default selectedCountry to null and guard the comparison on a non-empty code. Run-on: Niteshift Co-Authored-By: Claude Opus 5 --- src/components/metrics/WorldMap.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/metrics/WorldMap.tsx b/src/components/metrics/WorldMap.tsx index 73a684d65..5d843966d 100644 --- a/src/components/metrics/WorldMap.tsx +++ b/src/components/metrics/WorldMap.tsx @@ -29,7 +29,7 @@ export function WorldMap({ websiteId, data, allowFilter = true, ...props }: Worl const { countryNames } = useCountryNames(locale); const { router, updateParams, query } = useNavigation(); const pointerRef = useRef<{ x: number; y: number }>(null); - const selectedCountry = allowFilter ? query.country?.replace(/^eq\./, '') : null; + const selectedCountry = allowFilter ? query.country?.replace(/^eq\./, '') || null : null; const visitorsLabel = formatMessage(labels.visitors).toLocaleLowerCase(locale); const unknownLabel = formatMessage(labels.unknown); @@ -42,11 +42,15 @@ export function WorldMap({ websiteId, data, allowFilter = true, ...props }: Worl [data, mapData], ); + const isSelected = (code: string) => { + return Boolean(code) && code === selectedCountry; + }; + const getFillColor = (code: string) => { if (code === 'AQ') return; const country = metrics?.find(({ x }) => x === code); - if (code === selectedCountry) { + if (isSelected(code)) { return colors.map.baseColor; } @@ -118,7 +122,7 @@ export function WorldMap({ websiteId, data, allowFilter = true, ...props }: Worl geography={geo} fill={getFillColor(code)} stroke={colors.map.strokeColor} - strokeWidth={code === selectedCountry ? 1 : undefined} + strokeWidth={isSelected(code) ? 1 : undefined} opacity={getOpacity(code)} style={{ default: { outline: 'none', cursor },