Files
calendar/packages/features/insights/components/booking/AverageEventDurationChart.tsx
T
Eunjae LeeandGitHub 593c48c8b4 refactor: replace tremor with recharts (#22791)
* refactor: replace tremor with recharts

* update tooltip

* clean up types

* replace tremor with recharts

* replace BarList with recharts

* replace ProgressBar

* remove tremor from the repository

* clean up

* fix UserStatsTable

* fix type error

* add explicit return type
2025-08-05 01:21:23 +01:00

125 lines
3.6 KiB
TypeScript

"use client";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import type { RouterOutputs } from "@calcom/trpc/react";
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
import { ChartCard } from "../ChartCard";
import { LoadingInsight } from "../LoadingInsights";
const COLOR = {
AVERAGE: "#3b82f6",
};
type AverageEventDurationData = RouterOutputs["viewer"]["insights"]["averageEventDuration"][number];
// Custom duration formatter that shows hours and minutes
const formatDuration = (minutes: number) => {
if (!minutes) return "0m";
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
const remainingMinutesStr = remainingMinutes.toFixed(1);
if (hours > 0 && remainingMinutes > 0) {
return `${hours}h ${remainingMinutesStr}m`;
} else if (hours > 0) {
return `${hours}h`;
} else {
return `${remainingMinutesStr}m`;
}
};
// Custom Tooltip component
const CustomTooltip = ({
active,
payload,
label,
}: {
active?: boolean;
payload?: Array<{
value: number;
dataKey: string;
name: string;
color: string;
payload: AverageEventDurationData;
}>;
label?: string;
}) => {
if (!active || !payload?.length) {
return null;
}
return (
<div className="bg-default text-inverted border-subtle rounded-lg border p-3 shadow-lg">
<p className="text-default font-medium">{label}</p>
{payload.map((entry, index: number) => (
<p key={index} style={{ color: entry.color }}>
{entry.name}: {formatDuration(entry.value)}
</p>
))}
</div>
);
};
export const AverageEventDurationChart = () => {
const { t } = useLocale();
const insightsBookingParams = useInsightsBookingParameters();
const { data, isSuccess, isPending } = trpc.viewer.insights.averageEventDuration.useQuery(
insightsBookingParams,
{
staleTime: 180000,
refetchOnWindowFocus: false,
trpc: {
context: { skipBatch: true },
},
}
);
if (isPending) return <LoadingInsight />;
if (!isSuccess || !data) return null;
const isNoData = data.every((item) => item["Average"] === 0);
return (
<ChartCard title={t("average_event_duration")}>
{isNoData && (
<div className="text-default flex h-60 text-center">
<p className="m-auto text-sm font-light">{t("insights_no_data_found_for_filter")}</p>
</div>
)}
{data && data.length > 0 && !isNoData && (
<div className="mt-4 h-80">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={data} margin={{ top: 30, right: 20, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis dataKey="Date" className="text-xs" axisLine={false} tickLine={false} />
<YAxis
allowDecimals={false}
className="text-xs opacity-50"
axisLine={false}
tickLine={false}
tickFormatter={formatDuration}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="linear"
dataKey="Average"
name={t("average_event_duration")}
stroke={COLOR.AVERAGE}
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
animationDuration={1000}
/>
</LineChart>
</ResponsiveContainer>
</div>
)}
</ChartCard>
);
};