Skip to content

Commit 3e299f9

Browse files
authored
refactor: update charts on /insights to use InsightsBookingService (#22744)
* refactor: update PopuplarEvents chart to use InsightsBookingService * use buildHashMapForUsers * refactor least booked * refactor charts * combine methods * refactor booking kpi cards * remove unused code * fix booking kpi cards * restore code that was removed accidentally * Revert "restore code that was removed accidentally" This reverts commit 0c0b444. * Revert "fix booking kpi cards" This reverts commit b1e8e9a. * refactor booking kpi cards * revert method and remove unused code * fix previous period * fix metrics * clean up trpc call params * clean up Download.tsx * split utils * restructure folders for components * clean up imports * rename TotalUserFeedbackTable to UserStatsTable * add guide * use client * add curly braces to case * fix tests
1 parent 1b9691d commit 3e299f9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1363
-1679
lines changed

apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/playground/bookings-by-hour/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import { BookingsByHourChartContent } from "@calcom/features/insights/components/BookingsByHourChart";
43
import { ChartCard } from "@calcom/features/insights/components/ChartCard";
4+
import { BookingsByHourChartContent } from "@calcom/features/insights/components/booking/BookingsByHourChart";
55
import { useLocale } from "@calcom/lib/hooks/useLocale";
66

77
// Sample data for playground testing

apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/playground/routing-funnel/page.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"use client";
22

33
import { ChartCard } from "@calcom/features/insights/components/ChartCard";
4-
import { RoutingFunnelContent, legend } from "@calcom/features/insights/components/RoutingFunnelContent";
4+
import {
5+
RoutingFunnelContent,
6+
legend,
7+
} from "@calcom/features/insights/components/routing/RoutingFunnelContent";
58
import { useLocale } from "@calcom/lib/hooks/useLocale";
69

710
// Random sample data for playground testing

apps/web/modules/insights/insights-routing-view.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
FailedBookingsByField,
88
RoutedToPerPeriod,
99
RoutingFunnel,
10-
} from "@calcom/features/insights/components";
10+
} from "@calcom/features/insights/components/routing";
1111
import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider";
1212
import { useLocale } from "@calcom/lib/hooks/useLocale";
1313

apps/web/modules/insights/insights-view.tsx

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

packages/features/insights/components/BookingAtCell.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import Link from "next/link";
24
import { useId } from "react";
35

packages/features/insights/components/ChartCard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { Fragment, type ReactNode } from "react";
24

35
import classNames from "@calcom/ui/classNames";

packages/features/insights/components/EventTrendsChart.tsx

Lines changed: 0 additions & 55 deletions
This file was deleted.

packages/features/insights/components/FeedbackTable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
24
import { useLocale } from "@calcom/lib/hooks/useLocale";
35
import type { User } from "@calcom/prisma/client";

packages/features/insights/components/HighestNoShowHostTable.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)