Skip to content

Commit 35a373e

Browse files
docs: Create add-new-booking-charts.mdx (calcom#22865)
* Create add-charts.mdx * Update mint.json * Rename add-charts.mdx to add-new-booking-charts.mdx * Update mint.json
1 parent 02c0b72 commit 35a373e

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
---
2+
title: "How to Add a New Booking Chart to Cal.com Insights Page"
3+
---
4+
5+
This guide walks you through creating a new booking chart component for the insights page, covering the entire stack from UI component to backend service.
6+
7+
## Overview
8+
9+
The insights booking system follows this architecture:
10+
11+
```
12+
UI Component → tRPC Handler → Insights Service → Database Query → Response
13+
```
14+
15+
<Steps>
16+
<Step title="Create the UI Component">
17+
18+
Create your chart component in `packages/features/insights/components/booking/`:
19+
20+
```typescript
21+
// packages/features/insights/components/booking/MyNewChart.tsx
22+
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from "recharts";
23+
24+
import { useLocale } from "@calcom/lib/hooks/useLocale";
25+
import { trpc } from "@calcom/trpc";
26+
27+
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
28+
import { ChartCard } from "../ChartCard";
29+
import { LoadingInsight } from "../LoadingInsights";
30+
31+
export const MyNewChart = () => {
32+
const { t } = useLocale();
33+
const insightsBookingParams = useInsightsBookingParameters();
34+
35+
const { data, isSuccess, isPending } = trpc.viewer.insights.myNewChartData.useQuery(insightsBookingParams, {
36+
staleTime: 180000, // 3 minutes
37+
refetchOnWindowFocus: false,
38+
trpc: { context: { skipBatch: true } },
39+
});
40+
41+
if (isPending) return <LoadingInsight />;
42+
43+
return (
44+
<ChartCard title={t("my_new_chart_title")}>
45+
{isSuccess && data?.length > 0 ? (
46+
<ResponsiveContainer width="100%" height={300}>
47+
<LineChart data={data}>
48+
<CartesianGrid strokeDasharray="3 3" />
49+
<XAxis dataKey="date" />
50+
<YAxis />
51+
<Tooltip />
52+
<Line type="monotone" dataKey="value" stroke="#8884d8" strokeWidth={2} />
53+
</LineChart>
54+
</ResponsiveContainer>
55+
) : (
56+
<div className="flex h-64 items-center justify-center">
57+
<p className="text-gray-500">{t("no_data_yet")}</p>
58+
</div>
59+
)}
60+
</ChartCard>
61+
);
62+
};
63+
```
64+
</Step>
65+
<Step title="Add Component to Barrel Export">
66+
Update the booking components index file:
67+
68+
```typescript
69+
// packages/features/insights/components/booking/index.ts
70+
export { AverageEventDurationChart } from "./AverageEventDurationChart";
71+
export { BookingKPICards } from "./BookingKPICards";
72+
// ... existing exports
73+
export { MyNewChart } from "./MyNewChart"; // Add this line
74+
```
75+
</Step>
76+
<Step title="Add Component to Insights View">
77+
78+
Add your component to the main insights page:
79+
80+
```typescript
81+
// apps/web/modules/insights/insights-view.tsx
82+
import {
83+
AverageEventDurationChart,
84+
BookingKPICards, // ... existing imports
85+
MyNewChart, // Add this import
86+
} from "@calcom/features/insights/components/booking";
87+
88+
export default function InsightsPage() {
89+
// ... existing code
90+
91+
return (
92+
<div className="space-y-6">
93+
{/* Existing components */}
94+
<BookingKPICards />
95+
<EventTrendsChart />
96+
97+
{/* Add your new chart */}
98+
<MyNewChart />
99+
100+
{/* Other existing components */}
101+
</div>
102+
);
103+
}
104+
```
105+
</Step>
106+
<Step title="Create tRPC Handler">
107+
108+
Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function:
109+
110+
```typescript
111+
// packages/features/insights/server/trpc-router.ts
112+
import { bookingRepositoryBaseInputSchema } from "@calcom/features/insights/server/raw-data.schema";
113+
import { userBelongsToTeamProcedure } from "@calcom/trpc/server/procedures/authedProcedure";
114+
115+
import { TRPCError } from "@trpc/server";
116+
117+
export const insightsRouter = router({
118+
// ... existing procedures
119+
120+
myNewChartData: userBelongsToTeamProcedure
121+
.input(bookingRepositoryBaseInputSchema)
122+
.query(async ({ ctx, input }) => {
123+
// `createInsightsBookingService` is defined at the root level in this file
124+
const insightsBookingService = createInsightsBookingService(ctx, input);
125+
126+
try {
127+
return await insightsBookingService.getMyNewChartData();
128+
} catch (e) {
129+
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
130+
}
131+
}),
132+
});
133+
```
134+
</Step>
135+
<Step title="Add Service Method to InsightsBookingBaseService">
136+
Add your new method to the `InsightsBookingBaseService` class:
137+
138+
```typescript
139+
// packages/lib/server/service/InsightsBookingBaseService.ts
140+
export class InsightsBookingBaseService {
141+
// ... existing methods
142+
143+
async getMyNewChartData() {
144+
const baseConditions = await this.getBaseConditions();
145+
146+
// Example: Get booking counts by day using raw SQL for performance
147+
const data = await this.prisma.$queryRaw<
148+
Array<{
149+
date: Date;
150+
bookingsCount: number;
151+
}>
152+
>`
153+
SELECT
154+
DATE("createdAt") as date,
155+
COUNT(*)::int as "bookingsCount"
156+
FROM "BookingTimeStatusDenormalized"
157+
WHERE ${baseConditions}
158+
GROUP BY DATE("createdAt")
159+
ORDER BY date ASC
160+
`;
161+
162+
// Transform the data for the chart
163+
return data.map((item) => ({
164+
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
165+
value: item.bookingsCount,
166+
}));
167+
}
168+
}
169+
```
170+
</Step>
171+
172+
## Best Practices
173+
174+
1. **Use `getInsightsBookingService()`**: Always use the DI container function for consistent service creation
175+
2. **Raw SQL for Performance**: Use `$queryRaw` for complex aggregations and better performance
176+
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
177+
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
178+
5. **Loading States**: Always show loading indicators with `LoadingInsight`
179+
6. **Consistent Styling**: Use `recharts` for new charts.
180+
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts

docs/mint.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,11 @@
110110
"icon": "code-simple",
111111
"pages": ["developing/guides/embeds/embed-events"]
112112
}
113+
{
114+
"group": "Insights",
115+
"icon": "chart-bar",
116+
"pages": ["developing/guides/insights/add-new-booking-charts"]
117+
}
113118
]
114119
},
115120
{

0 commit comments

Comments
 (0)