import { useTRPC } from '@/integrations/trpc/react'; import { useQuery } from '@tanstack/react-query'; import { useNumber } from '@/hooks/use-numer-formatter'; import { getChartColor } from '@/utils/theme'; import * as Portal from '@radix-ui/react-portal'; import { bind } from 'bind-event-listener'; import throttle from 'lodash.throttle'; import React, { useEffect, useState } from 'react'; import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; import { AnimatedNumber } from '../animated-number'; import { BarShapeBlue } from '../charts/common-bar'; import { SerieIcon } from '../report-chart/common/serie-icon'; interface RealtimeLiveHistogramProps { projectId: string; } export function RealtimeLiveHistogram({ projectId, }: RealtimeLiveHistogramProps) { const trpc = useTRPC(); // Use the same liveData endpoint as overview const { data: liveData, isLoading } = useQuery( trpc.overview.liveData.queryOptions({ projectId }), ); const chartData = liveData?.minuteCounts ?? []; // Calculate total unique visitors (sum of unique visitors per minute) // Note: This is an approximation - ideally we'd want unique visitors across all minutes const totalVisitors = liveData?.totalSessions ?? 0; if (isLoading) { return (
); } if (!liveData) { return null; } const maxDomain = Math.max(...chartData.map((item) => item.visitorCount), 0) * 1.2 || 1; return ( 0 ? (
{liveData.referrers.slice(0, 3).map((ref, index) => (
{ref.count}
))}
) : null } >
); } interface WrapperProps { children: React.ReactNode; count: number; icons?: React.ReactNode; } function Wrapper({ children, count, icons }: WrapperProps) { return (
Unique visitors {icons ?
: null} last 30 min
{icons}
{children}
); } // Custom tooltip component that uses portals to escape overflow hidden const CustomTooltip = ({ active, payload, coordinate }: any) => { const number = useNumber(); const [position, setPosition] = useState<{ x: number; y: number } | null>( null, ); const inactive = !active || !payload?.length; useEffect(() => { const setPositionThrottled = throttle(setPosition, 50); const unsubMouseMove = bind(window, { type: 'mousemove', listener(event) { if (!inactive) { setPositionThrottled({ x: event.clientX, y: event.clientY + 20 }); } }, }); const unsubDragEnter = bind(window, { type: 'pointerdown', listener() { setPosition(null); }, }); return () => { unsubMouseMove(); unsubDragEnter(); }; }, [inactive]); if (inactive) { return null; } if (!active || !payload || !payload.length) { return null; } const data = payload[0].payload; const tooltipWidth = 200; const correctXPosition = (x: number | undefined) => { if (!x) { return undefined; } const screenWidth = window.innerWidth; const newX = x; if (newX + tooltipWidth > screenWidth) { return screenWidth - tooltipWidth; } return newX; }; return (
{data.time}
Active users
{number.formatWithUnit(data.visitorCount)}
{data.referrers && data.referrers.length > 0 && (
Referrers:
{data.referrers.slice(0, 3).map((ref: any, index: number) => (
{ref.referrer}
{ref.count}
))} {data.referrers.length > 3 && (
+{data.referrers.length - 3} more
)}
)} ); };