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 (
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
)}
)}
);
};