diff --git a/docs/developing/guides/insights/add-new-booking-charts.mdx b/docs/developing/guides/insights/add-new-booking-charts.mdx index 0a828d4eb97984..556a22b3b865b8 100644 --- a/docs/developing/guides/insights/add-new-booking-charts.mdx +++ b/docs/developing/guides/insights/add-new-booking-charts.mdx @@ -20,26 +20,26 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon ```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 ; - + return ( {isSuccess && data?.length > 0 ? ( @@ -66,7 +66,7 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon Update the booking components index file: - + ```typescript // packages/features/insights/components/booking/index.ts export { AverageEventDurationChart } from "./AverageEventDurationChart"; @@ -76,9 +76,9 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon ``` - + Add your component to the main insights page: - + ```typescript // apps/web/modules/insights/insights-view.tsx import { @@ -86,45 +86,45 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon BookingKPICards, // ... existing imports MyNewChart, // Add this import } from "@calcom/features/insights/components/booking"; - + export default function InsightsPage() { // ... existing code - + return (
{/* Existing components */} - + {/* Add your new chart */} - + {/* Other existing components */}
); } ``` -
+ Add the tRPC endpoint in the insights router using the `getInsightsBookingService()` DI container function: - + ```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 }) => { // `createInsightsBookingService` is defined at the root level in this file const insightsBookingService = createInsightsBookingService(ctx, input); - + try { return await insightsBookingService.getMyNewChartData(); } catch (e) { @@ -134,24 +134,21 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon }); ``` - + Add your new method to the `InsightsBookingBaseService` class: - + ```typescript // packages/lib/server/service/InsightsBookingBaseService.ts export class InsightsBookingBaseService { // ... 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; - }> - >` + // Note: Use Prisma.sql for the entire query (Prisma v6 requirement) + // Prisma v6 no longer allows mixing template literals with Prisma.sql fragments + const query = Prisma.sql` SELECT DATE("createdAt") as date, COUNT(*)::int as "bookingsCount" @@ -160,7 +157,14 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon GROUP BY DATE("createdAt") ORDER BY date ASC `; - + + const data = await this.prisma.$queryRaw< + Array<{ + date: Date; + bookingsCount: number; + }> + >(query); + // Transform the data for the chart return data.map((item) => ({ date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD @@ -170,6 +174,7 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon } ``` + ## Best Practices @@ -179,5 +184,6 @@ UI Component → tRPC Handler → Insights Service → Database Query → Respon 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. +6. **Consistent Styling**: Use `recharts` for new charts 7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts +8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments diff --git a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md index 70f045797b3b8a..8760c12b678607 100644 --- a/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md +++ b/packages/features/insights/HOW_TO_ADD_BOOKING_CHARTS.md @@ -143,12 +143,9 @@ export class InsightsBookingBaseService { 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; - }> - >` + // Note: Use Prisma.sql for the entire query (Prisma v6 requirement) + // Prisma v6 no longer allows mixing template literals with Prisma.sql fragments + const query = Prisma.sql` SELECT DATE("createdAt") as date, COUNT(*)::int as "bookingsCount" @@ -158,6 +155,13 @@ export class InsightsBookingBaseService { ORDER BY date ASC `; + const data = await this.prisma.$queryRaw< + Array<{ + date: Date; + bookingsCount: number; + }> + >(query); + // Transform the data for the chart return data.map((item) => ({ date: item.date.toISOString().split("T")[0], // Format as YYYY-MM-DD @@ -174,5 +178,6 @@ export class InsightsBookingBaseService { 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. +6. **Consistent Styling**: Use `recharts` for new charts 7. **Date Handling**: Use `getDateRanges()` and `getTimeView()` for time-based charts +8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments