Files
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

179 lines
4.9 KiB
TypeScript

"use client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import classNames from "@calcom/ui/classNames";
import { SkeletonText } from "@calcom/ui/components/skeleton";
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
import { ChartCard } from "../ChartCard";
import { KPICard } from "../KPICard";
export const BookingKPICards = () => {
const { t } = useLocale();
const insightsBookingParams = useInsightsBookingParameters();
const { data, isSuccess, isPending } = trpc.viewer.insights.bookingKPIStats.useQuery(
insightsBookingParams,
{
staleTime: 180000,
refetchOnWindowFocus: false,
trpc: {
context: { skipBatch: true },
},
}
);
const eventCategories: {
title: string;
index: "created" | "completed" | "rescheduled" | "cancelled";
}[] = [
{
title: t("events_created"),
index: "created",
},
{
title: t("events_completed"),
index: "completed",
},
{
title: t("events_rescheduled"),
index: "rescheduled",
},
{
title: t("events_cancelled"),
index: "cancelled",
},
];
const performanceCategories: {
title: string;
index: "rating" | "no_show" | "no_show_guest" | "csat";
}[] = [
{
title: t("event_ratings"),
index: "rating",
},
{
title: t("event_no_show"),
index: "no_show",
},
{
title: t("event_no_show_guest"),
index: "no_show_guest",
},
{
title: t("csat_score"),
index: "csat",
},
];
if (isPending) {
return (
<LoadingKPICards eventCategories={eventCategories} performanceCategories={performanceCategories} />
);
}
if (!isSuccess || !data) return null;
return (
<div className="space-y-4">
<ChartCard title={t("events")}>
<StatContainer>
{eventCategories.map((item, index) => (
<StatItem key={item.title} index={index}>
<KPICard
title={item.title}
previousMetricData={data[item.index]}
previousDateRange={data.previousRange}
/>
</StatItem>
))}
</StatContainer>
</ChartCard>
<ChartCard title={t("performance")}>
<StatContainer>
{performanceCategories.map((item, index) => (
<StatItem key={item.title} index={index}>
<KPICard
title={item.title}
previousMetricData={data[item.index]}
previousDateRange={data.previousRange}
/>
</StatItem>
))}
</StatContainer>
</ChartCard>
</div>
);
};
const LoadingKPICards = (props: {
eventCategories: { title: string; index: string }[];
performanceCategories: { title: string; index: string }[];
}) => {
const { eventCategories, performanceCategories } = props;
const { t } = useLocale();
return (
<div className="space-y-4">
<ChartCard title={t("events")}>
<StatContainer>
{eventCategories.map((item, index) => (
<StatItem key={item.title} index={index}>
<div>
<SkeletonText className="mb-2 h-4 w-24" />
<div className="items-baseline justify-start space-x-3 truncate">
<SkeletonText className="h-8 w-16" />
</div>
<div className="mt-4 justify-start space-x-2">
<SkeletonText className="h-4 w-20" />
</div>
</div>
</StatItem>
))}
</StatContainer>
</ChartCard>
<ChartCard title={t("performance")}>
<StatContainer>
{performanceCategories.map((item, index) => (
<StatItem key={item.title} index={index}>
<div>
<SkeletonText className="mb-2 h-4 w-24" />
<div className="items-baseline justify-start space-x-3 truncate">
<SkeletonText className="h-8 w-16" />
</div>
<div className="mt-4 justify-start space-x-2">
<SkeletonText className="h-4 w-20" />
</div>
</div>
</StatItem>
))}
</StatContainer>
</ChartCard>
</div>
);
};
// StatContainer: wraps the grid
function StatContainer({ children }: { children: React.ReactNode }) {
return <div className="group grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4">{children}</div>;
}
// StatItem: handles border logic
function StatItem({ index, children }: { index: number; children: React.ReactNode }) {
return (
<div
className={classNames(
"border-muted flex-1 p-4",
index === 0 && "border-b sm:border-r md:border-b-0 md:border-r",
index === 1 && "border-b sm:border-r-0 md:border-b-0 md:border-r",
index === 2 && "border-b sm:border-b-0 sm:border-r md:border-b-0"
)}>
{children}
</div>
);
}