import { X_AXIS_STYLE_PROPS, useXAxisProps, useYAxisProps, } from '@/components/report-chart/common/axis'; import { Widget, WidgetBody, WidgetHead } from '@/components/widget'; import { useNumber } from '@/hooks/use-numer-formatter'; import { useTRPC } from '@/integrations/trpc/react'; import { formatDate } from '@/utils/date'; import { getChartColor } from '@/utils/theme'; import { sum } from '@openpanel/common'; import type { IServiceOrganization } from '@openpanel/db'; import { useQuery } from '@tanstack/react-query'; import { Loader2Icon } from 'lucide-react'; import { pick } from 'ramda'; import { Bar, BarChart, CartesianGrid, Tooltip as RechartTooltip, ReferenceLine, ResponsiveContainer, XAxis, YAxis, } from 'recharts'; import { BarShapeBlue } from '../charts/common-bar'; type Props = { organization: IServiceOrganization; }; function Card({ title, value }: { title: string; value: string }) { return (
{title}
{value}
); } export default function Usage({ organization }: Props) { const number = useNumber(); const trpc = useTRPC(); const usageQuery = useQuery( trpc.subscription.usage.queryOptions({ organizationId: organization.id, }), ); // Determine interval based on data range - use weekly if more than 30 days const getDataInterval = () => { if (!usageQuery.data || usageQuery.data.length === 0) return 'day'; const dates = usageQuery.data.map((item) => new Date(item.day)); const minDate = new Date(Math.min(...dates.map((d) => d.getTime()))); const maxDate = new Date(Math.max(...dates.map((d) => d.getTime()))); const daysDiff = Math.ceil( (maxDate.getTime() - minDate.getTime()) / (1000 * 60 * 60 * 24), ); return daysDiff > 30 ? 'week' : 'day'; }; const interval = getDataInterval(); const useWeeklyIntervals = interval === 'week'; const xAxisProps = useXAxisProps({ interval }); const yAxisProps = useYAxisProps({}); const wrapper = (node: React.ReactNode) => ( Usage {node} ); if (usageQuery.isLoading) { return wrapper(
, ); } if (usageQuery.isError) { return wrapper(
Issues loading usage data
, ); } const subscriptionPeriodEventsLimit = organization.hasSubscription ? organization.subscriptionPeriodEventsLimit : 0; const subscriptionPeriodEventsCount = organization.hasSubscription ? organization.subscriptionPeriodEventsCount : 0; // Group daily data into weekly intervals if data spans more than 30 days const processChartData = () => { if (!usageQuery.data) return []; if (useWeeklyIntervals) { // Group daily data into weekly intervals const weeklyData: { [key: string]: { count: number; startDate: Date; endDate: Date }; } = {}; usageQuery.data.forEach((item) => { const date = new Date(item.day); // Get the start of the week (Monday) const startOfWeek = new Date(date); const dayOfWeek = date.getDay(); const diff = date.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1); // Adjust when day is Sunday startOfWeek.setDate(diff); startOfWeek.setHours(0, 0, 0, 0); const weekKey = startOfWeek.toISOString().split('T')[0]; if (!weeklyData[weekKey]) { weeklyData[weekKey] = { count: 0, startDate: new Date(startOfWeek), endDate: new Date(startOfWeek), }; } weeklyData[weekKey].count += item.count; weeklyData[weekKey].endDate = new Date(date); }); return Object.values(weeklyData).map((week) => ({ date: week.startDate.getTime(), count: week.count, weekRange: `${formatDate(week.startDate)} - ${formatDate(week.endDate)}`, })); } // Use daily data for monthly subscriptions return usageQuery.data.map((item) => ({ date: new Date(item.day).getTime(), count: item.count, })); }; const chartData = processChartData(); const domain = [ 0, Math.max( subscriptionPeriodEventsLimit, subscriptionPeriodEventsCount, ...chartData.map((item) => item.count), ), ] as [number, number]; domain[1] += domain[1] * 0.05; return wrapper( <>
{organization.hasSubscription ? ( <> ) : ( <>
item.count) ?? []), )} />
)}
{/* Events Chart */}

{useWeeklyIntervals ? 'Weekly Events' : 'Daily Events'}

} />
{/* Total Events vs Limit Chart */}

Total Events vs Limit

} cursor={false} /> {organization.hasSubscription && subscriptionPeriodEventsLimit > 0 && ( )}
, ); } function EventsTooltip({ useWeekly, ...props }: { useWeekly: boolean } & any) { const number = useNumber(); const payload = props.payload?.[0]?.payload; if (!payload) { return null; } return (
{useWeekly && payload.weekRange ? payload.weekRange : payload?.date ? formatDate(new Date(payload.date)) : 'Unknown date'}
Events {useWeekly ? 'this week' : 'this day'}
{number.format(payload.count)}
); } function TotalTooltip(props: any) { const number = useNumber(); const payload = props.payload?.[0]?.payload; if (!payload) { return null; } return (
Total Events
Your events count
{number.format(payload.count)}
{payload.limit > 0 && (
Your tier limit
{number.format(payload.limit)}
)}
); }