Files
cal.diy/packages/features/insights/components/booking/CSATOverTimeChart.tsx
T
89cc175d73 feat: add no-show hosts and csat graphs on Insights (#24556)
* feat: Add No-Show Hosts and CSAT insights with beautiful card design

- Add No-Show Hosts Over Time chart with line visualization
- Add CSAT Over Time chart with percentage tracking
- Add Recent No-Show Guests list with email copy functionality
- Redesign all KPI cards as individual separated cards with curved edges
- Add proper TRPC endpoints and database queries for new insights
- Follow Cal.com design patterns with consistent styling
- All charts now have individual cards with rounded-2xl borders and shadow-sm elevation
- Fix all linting issues with unused imports and variables

* coderabbit fixes

* fix(insights): address review feedback on charts

* fix(insights): correct dayjs timezone usage for type safety

* Update packages/features/insights/services/InsightsBookingBaseService.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Fix InfoBadge import path

* Wrap InfoBadge in span for styling

* Add insights charts and i18n keys

* Fix ChartCard usage and double card wrapping issues

* Remove double card wrapping for chart components

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Eunjae Lee <hey@eunjae.dev>
2025-10-28 10:39:39 +00:00

104 lines
2.9 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 = {
CSAT: "#22c55e",
};
type CSATOverTimeData = RouterOutputs["viewer"]["insights"]["csatOverTime"][number];
// Custom Tooltip component
const CustomTooltip = ({
active,
payload,
label: _label,
}: {
active?: boolean;
payload?: Array<{
value: number;
dataKey: string;
name: string;
color: string;
payload: CSATOverTimeData;
}>;
label?: string;
}) => {
const { t } = useLocale();
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">{payload[0].payload.formattedDateFull}</p>
{payload.map((entry, index: number) => (
<p key={index} style={{ color: entry.color }}>
{t("csat")}: {entry.value.toFixed(1)}%
</p>
))}
</div>
);
};
export const CSATOverTimeChart = () => {
const { t } = useLocale();
const insightsBookingParams = useInsightsBookingParameters();
const {
data: csatData,
isSuccess,
isPending,
} = trpc.viewer.insights.csatOverTime.useQuery(insightsBookingParams, {
staleTime: 180000,
refetchOnWindowFocus: false,
trpc: {
context: { skipBatch: true },
},
});
if (isPending) return <LoadingInsight />;
if (!isSuccess) return null;
return (
<ChartCard title={t("csat_over_time")} className="h-full">
<div className="linechart ml-4 mt-4 h-80 sm:ml-0">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={csatData ?? []} margin={{ top: 30, right: 20, left: 0, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis dataKey="Month" className="text-xs" axisLine={false} tickLine={false} />
<YAxis
allowDecimals={true}
className="text-xs opacity-50"
axisLine={false}
tickLine={false}
tickFormatter={(value) => `${value}%`}
domain={[0, 100]}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="linear"
dataKey="CSAT"
name={t("csat")}
stroke={COLOR.CSAT}
strokeWidth={2}
dot={{ r: 4 }}
activeDot={{ r: 6 }}
animationDuration={1000}
/>
</LineChart>
</ResponsiveContainer>
</div>
</ChartCard>
);
};