mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* refactor: import AppRouter from generated types instead of server source This change improves tRPC build performance by having the client-side code import AppRouter from pre-generated type declarations instead of traversing the entire server router tree. Changes: - Create type bridge file at packages/trpc/types/app-router.ts - Update packages/trpc/react/trpc.ts to import from the bridge - Update .gitignore to only ignore types/server (generated files) - Update eslint.config.mjs to only ignore types/server (generated files) The type bridge provides: 1. Faster typechecking - avoids parsing 458 server files 2. Stable import location that's easy to lint against 3. Single place to adjust if generated path changes Build order is already enforced in turbo.json (type-check depends on @calcom/trpc#build). Co-Authored-By: [email protected] <[email protected]> * fix: move bridge file to react/ to avoid TS5055 error Move the AppRouter type bridge file from types/app-router.ts to react/app-router.ts to avoid the TS5055 'Cannot write file because it would overwrite input file' error. The issue was that placing the bridge file in types/ caused TypeScript to treat the generated .d.ts files as input files during the tRPC build, then fail when trying to emit to the same location. By placing the bridge in react/ (which is excluded from the tRPC server build), the bridge file is only used by client code and doesn't interfere with the server type generation. Changes: - Move bridge file from types/app-router.ts to react/app-router.ts - Update import in react/trpc.ts to use ./app-router - Revert .gitignore to ignore all of types/ (generated files) - Revert eslint.config.mjs to ignore all of types/** Co-Authored-By: [email protected] <[email protected]> * refactor: split tRPC build into server and react phases - Create tsconfig.server.json for server-only type generation - Create tsconfig.react.json for react/client type generation - Update build script to run server build first, then react build - Remove || true so build properly fails on errors - This allows react code to import from generated server types Co-Authored-By: [email protected] <[email protected]> * refactor: split @calcom/trpc exports to separate server and react entrypoints - Remove react exports from @calcom/trpc root (index.ts) - Update 89 files to import from @calcom/trpc/react instead of @calcom/trpc - This fixes the boundary leak where server builds were pulling in react code - Server build no longer compiles react/app-router.ts, fixing the chicken-and-egg issue where react code needed generated server types that didn't exist yet This improves TypeScript build performance by preventing the server type generation from traversing the entire react/client type graph. Co-Authored-By: [email protected] <[email protected]> * fix: import WorkflowType from lib/types instead of React component This fixes a boundary leak where the server build was pulling in React components through the WorkflowRepository import chain. By importing WorkflowListType from lib/types instead of WorkflowListPage.tsx, the server build no longer traverses React component files. Co-Authored-By: [email protected] <[email protected]> * fix: extract server-safe types to prevent boundary leaks in tRPC build - Extract ChildrenEventType to lib/childrenEventType.ts (server-safe) - Extract Slots type to calendars/lib/slots.ts (server-safe) - Create types.server.ts files for eventtypes and bookings - Update server code to import from server-safe type files - Update DatePicker.tsx to use extracted Slots type - Update app-store utils to use BookerEventForAppData type This prevents the server build from pulling in React files through transitive imports from @calcom/features barrel exports. Co-Authored-By: [email protected] <[email protected]> * fix: update Segment.test.tsx mock path to @calcom/trpc/react The test was mocking @calcom/trpc but importing from @calcom/trpc/react. After the entrypoint separation, the mock path needs to match the import path. Co-Authored-By: [email protected] <[email protected]> * fix: temporarily restore || true to unblock PR merge The pre-existing Prisma type errors (~345 errors) will be addressed in a follow-up PR. This allows the two-phase build architecture changes to be merged first. Co-Authored-By: [email protected] <[email protected]> * Run trpc build as part of API v2 build * Removed the bridge file * refactor: extract event type schemas to server-safe file - Create packages/features/eventtypes/lib/schemas.ts with createEventTypeInput and EventTypeDuplicateInput - Update types.ts to re-export schemas from the new server-safe location - Update tRPC schema files to import from schemas.ts instead of types.server.ts - Delete types.server.ts (was duplicating ~200 lines unnecessarily) This keeps the server build graph clean while avoiding code duplication. Co-Authored-By: [email protected] <[email protected]> * Removed the optionality of the tRPC builds * Removed the extra command for API v2 * refactor: rename calendars/lib/slots.ts to types.ts Per Keith's feedback, renamed the file to types.ts since it contains type definitions. Co-Authored-By: [email protected] <[email protected]> * Added back tRPC build:server for API v2 --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Dhairyashil Shinde <[email protected]>
190 lines
6.7 KiB
Plaintext
190 lines
6.7 KiB
Plaintext
---
|
|
title: "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
|
|
```
|
|
|
|
<Steps>
|
|
<Step title="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/react";
|
|
|
|
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>
|
|
|
|
<Step title="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>
|
|
<Step title="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>
|
|
<Step title="Create tRPC Handler">
|
|
|
|
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) {
|
|
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
|
|
}
|
|
}),
|
|
});
|
|
```
|
|
</Step>
|
|
<Step title="Add Service Method to InsightsBookingBaseService">
|
|
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
|
|
// 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"
|
|
FROM "BookingTimeStatusDenormalized"
|
|
WHERE ${baseConditions}
|
|
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
|
|
value: item.bookingsCount,
|
|
}));
|
|
}
|
|
}
|
|
```
|
|
</Step>
|
|
|
|
</Steps>
|
|
|
|
## Best Practices
|
|
|
|
1. **Use `getInsightsBookingService()`**: Always use the DI container 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
|
|
8. **Prisma v6 Compatibility**: Use `Prisma.sql` for the entire query instead of mixing template literals with SQL fragments
|