Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b5c7953
refactor: update PopuplarEvents chart to use InsightsBookingService
eunjae-lee Jul 25, 2025
390fd18
use buildHashMapForUsers
eunjae-lee Jul 25, 2025
b5c4927
refactor least booked
eunjae-lee Jul 25, 2025
93fa26f
refactor charts
eunjae-lee Jul 25, 2025
2bfd460
combine methods
eunjae-lee Jul 25, 2025
151a88b
refactor booking kpi cards
eunjae-lee Jul 25, 2025
39e4743
remove unused code
eunjae-lee Jul 25, 2025
b1e8e9a
fix booking kpi cards
eunjae-lee Jul 25, 2025
0c0b444
restore code that was removed accidentally
eunjae-lee Jul 26, 2025
b47474e
Merge branch 'main' into refactor/charts-on-insights
eunjae-lee Jul 28, 2025
9a5312a
Revert "restore code that was removed accidentally"
eunjae-lee Jul 28, 2025
1e4e47e
Revert "fix booking kpi cards"
eunjae-lee Jul 28, 2025
7c048a2
refactor booking kpi cards
eunjae-lee Jul 28, 2025
13cc6c2
revert method and remove unused code
eunjae-lee Jul 28, 2025
0b39758
fix previous period
eunjae-lee Jul 28, 2025
8022b4e
fix metrics
eunjae-lee Jul 28, 2025
ffe5ea6
clean up trpc call params
eunjae-lee Jul 28, 2025
d5634ac
clean up Download.tsx
eunjae-lee Jul 28, 2025
8cbd7e0
split utils
eunjae-lee Jul 28, 2025
1ddd812
restructure folders for components
eunjae-lee Jul 29, 2025
ab2f1de
clean up imports
eunjae-lee Jul 29, 2025
a13c282
rename TotalUserFeedbackTable to UserStatsTable
eunjae-lee Jul 29, 2025
5ff2a44
add guide
eunjae-lee Jul 29, 2025
bd3420a
Merge branch 'main' into refactor/charts-on-insights
eunjae-lee Jul 29, 2025
574bbf4
use client
eunjae-lee Jul 29, 2025
60f347a
add curly braces to case
eunjae-lee Jul 29, 2025
37894c8
fix tests
eunjae-lee Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { BookingsByHourChartContent } from "@calcom/features/insights/components/BookingsByHourChart";
import { ChartCard } from "@calcom/features/insights/components/ChartCard";
import { BookingsByHourChartContent } from "@calcom/features/insights/components/booking/BookingsByHourChart";
import { useLocale } from "@calcom/lib/hooks/useLocale";

// Sample data for playground testing
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"use client";

import { ChartCard } from "@calcom/features/insights/components/ChartCard";
import { RoutingFunnelContent, legend } from "@calcom/features/insights/components/RoutingFunnelContent";
import {
RoutingFunnelContent,
legend,
} from "@calcom/features/insights/components/routing/RoutingFunnelContent";
import { useLocale } from "@calcom/lib/hooks/useLocale";

// Random sample data for playground testing
Expand Down
2 changes: 1 addition & 1 deletion apps/web/modules/insights/insights-routing-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
FailedBookingsByField,
RoutedToPerPeriod,
RoutingFunnel,
} from "@calcom/features/insights/components";
} from "@calcom/features/insights/components/routing";
import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider";
import { useLocale } from "@calcom/lib/hooks/useLocale";

Expand Down
4 changes: 2 additions & 2 deletions apps/web/modules/insights/insights-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import {
import {
AverageEventDurationChart,
BookingKPICards,
BookingsByHourChart,
EventTrendsChart,
HighestNoShowHostTable,
HighestRatedMembersTable,
BookingsByHourChart,
LeastBookedTeamMembersTable,
LowestRatedMembersTable,
MostBookedTeamMembersTable,
MostCancelledBookingsTables,
PopularEventsTable,
RecentFeedbackTable,
TimezoneBadge,
} from "@calcom/features/insights/components";
} from "@calcom/features/insights/components/booking";
import "@calcom/features/insights/components/tremor.css";
import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider";
import { Download } from "@calcom/features/insights/filters/Download";
Expand Down
177 changes: 177 additions & 0 deletions packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# How to Add a New Booking Chart to Cal.com Insights Page

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.

## Overview

The insights booking system follows this architecture:

```
UI Component → tRPC Handler → Insights Service → Database Query → Response
```

## Step 1: Create the UI Component

Create your chart component in `packages/features/insights/components/booking/`:

```typescript
// packages/features/insights/components/booking/MyNewChart.tsx
import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Line, ResponsiveContainer } from "recharts";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";

import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
import { ChartCard } from "../ChartCard";
import { LoadingInsight } from "../LoadingInsights";

export const MyNewChart = () => {
const { t } = useLocale();
const insightsBookingParams = useInsightsBookingParameters();

const { data, isSuccess, isPending } = trpc.viewer.insights.myNewChartData.useQuery(insightsBookingParams, {
staleTime: 180000, // 3 minutes
refetchOnWindowFocus: false,
trpc: { context: { skipBatch: true } },
});

if (isPending) return <LoadingInsight />;

return (
<ChartCard title={t("my_new_chart_title")}>
{isSuccess && data?.length > 0 ? (
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="value" stroke="#8884d8" strokeWidth={2} />
</LineChart>
</ResponsiveContainer>
) : (
<div className="flex h-64 items-center justify-center">
<p className="text-gray-500">{t("no_data_yet")}</p>
</div>
)}
</ChartCard>
);
};
```

## Step 2: Add Component to Barrel Export

Update the booking components index file:

```typescript
// packages/features/insights/components/booking/index.ts
export { AverageEventDurationChart } from "./AverageEventDurationChart";
export { BookingKPICards } from "./BookingKPICards";
// ... existing exports
export { MyNewChart } from "./MyNewChart"; // Add this line
```

## Step 3: Add Component to Insights View

Add your component to the main insights page:

```typescript
// apps/web/modules/insights/insights-view.tsx
import {
AverageEventDurationChart,
BookingKPICards, // ... existing imports
MyNewChart, // Add this import
} from "@calcom/features/insights/components/booking";

export default function InsightsPage() {
// ... existing code

return (
<div className="space-y-6">
{/* Existing components */}
<BookingKPICards />
<EventTrendsChart />

{/* Add your new chart */}
<MyNewChart />

{/* Other existing components */}
</div>
);
}
```

## Step 4: Create tRPC Handler

Add the tRPC endpoint in the insights router using the `createInsightsBookingService()` helper:

```typescript
// packages/features/insights/server/trpc-router.ts
import { bookingRepositoryBaseInputSchema } from "@calcom/features/insights/server/raw-data.schema";
import { userBelongsToTeamProcedure } from "@calcom/trpc/server/procedures/authedProcedure";

import { TRPCError } from "@trpc/server";

export const insightsRouter = router({
// ... existing procedures

myNewChartData: userBelongsToTeamProcedure
.input(bookingRepositoryBaseInputSchema)
.query(async ({ ctx, input }) => {
const insightsBookingService = createInsightsBookingService(ctx, input);

try {
return await insightsBookingService.getMyNewChartData();
} catch (e) {
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
}
}),
});
```

## Step 5: Add Service Method to InsightsBookingService

Add your new method to the `InsightsBookingService` class:

```typescript
// packages/lib/server/service/insightsBooking.ts
export class InsightsBookingService {
// ... existing methods

async getMyNewChartData() {
const baseConditions = await this.getBaseConditions();

// Example: Get booking counts by day using raw SQL for performance
const data = await this.prisma.$queryRaw<
Array<{
date: Date;
bookingsCount: number;
}>
>`
SELECT
DATE("createdAt") as date,
COUNT(*)::int as "bookingsCount"
FROM "BookingTimeStatusDenormalized"
WHERE ${baseConditions}
GROUP BY DATE("createdAt")
ORDER BY date ASC
`;

// Transform the data for the chart
return data.map((item) => ({
date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD
value: item.bookingsCount,
}));
}
}
```

## Best Practices

1. **Use `createInsightsBookingService()`**: Always use the helper function for consistent service creation
2. **Raw SQL for Performance**: Use `$queryRaw` for complex aggregations and better performance
3. **Base Conditions**: Always use `await this.getBaseConditions()` for proper filtering and permissions
4. **Error Handling**: Wrap service calls in try-catch blocks with `TRPCError`
5. **Loading States**: Always show loading indicators with `LoadingInsight`
6. **Consistent Styling**: Use `recharts` for new charts.
7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts
2 changes: 2 additions & 0 deletions packages/features/insights/components/BookingAtCell.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import Link from "next/link";
import { useId } from "react";

Expand Down
2 changes: 2 additions & 0 deletions packages/features/insights/components/ChartCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { Fragment, type ReactNode } from "react";

import classNames from "@calcom/ui/classNames";
Expand Down
55 changes: 0 additions & 55 deletions packages/features/insights/components/EventTrendsChart.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions packages/features/insights/components/FeedbackTable.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { User } from "@calcom/prisma/client";
Expand Down
40 changes: 0 additions & 40 deletions packages/features/insights/components/HighestNoShowHostTable.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions packages/features/insights/components/KPICard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import { Flex, Text, Metric, BadgeDelta } from "@tremor/react";

import { useLocale } from "@calcom/lib/hooks/useLocale";
Expand Down

This file was deleted.

Loading
Loading