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