Skip to content

Commit 7145a2d

Browse files
authored
refactor: Routed to Per Period to use Insights Routing Service (calcom#23031)
* fix: refactor RoutedToPerPeriod to use Insights Routing Service * clean up * debounce search and increase stale time * fix wrongn conflict merge * clean up DownloadButton * fix query error * address feedback * apply feedback * remove unnecessary CTE * fix limit * make startDate and endDate required * fix type * remove irrelevant test case * address feedback
1 parent baaa041 commit 7145a2d

6 files changed

Lines changed: 369 additions & 722 deletions

File tree

packages/features/insights/components/routing/RoutedToPerPeriod.tsx

Lines changed: 44 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type ReactNode, useMemo, useRef, useState } from "react";
66

77
import { DataTableSkeleton } from "@calcom/features/data-table";
88
import { downloadAsCsv } from "@calcom/lib/csvUtils";
9-
import { useInViewObserver } from "@calcom/lib/hooks/useInViewObserver";
9+
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
1010
import { useLocale } from "@calcom/lib/hooks/useLocale";
1111
import { trpc } from "@calcom/trpc";
1212
import type { RouterOutputs } from "@calcom/trpc/react";
@@ -26,46 +26,26 @@ import {
2626
} from "@calcom/ui/components/table";
2727
import { Tooltip } from "@calcom/ui/components/tooltip";
2828

29-
import { useInsightsParameters } from "../../hooks/useInsightsParameters";
29+
import { useInsightsRoutingParameters } from "../../hooks/useInsightsRoutingParameters";
3030
import { ChartCard } from "../ChartCard";
3131

3232
interface DownloadButtonProps {
33-
teamId?: number;
34-
userId?: number;
35-
isAll?: boolean;
36-
routingFormId?: string;
37-
startDate: string;
38-
endDate: string;
39-
selectedPeriod: string;
33+
selectedPeriod: "perDay" | "perWeek" | "perMonth";
4034
searchQuery?: string;
4135
}
4236

43-
function DownloadButton({
44-
userId,
45-
teamId,
46-
isAll,
47-
routingFormId,
48-
startDate,
49-
endDate,
50-
selectedPeriod,
51-
searchQuery,
52-
}: DownloadButtonProps) {
37+
function DownloadButton({ selectedPeriod, searchQuery }: DownloadButtonProps) {
5338
const [isDownloading, setIsDownloading] = useState(false);
39+
const routingParams = useInsightsRoutingParameters();
5440
const utils = trpc.useContext();
55-
const { t } = useLocale();
5641

5742
const handleDownload = async (e: React.MouseEvent) => {
5843
e.preventDefault(); // Prevent default form submission
5944

6045
try {
6146
const result = await utils.viewer.insights.routedToPerPeriodCsv.fetch({
62-
userId,
63-
teamId,
64-
startDate,
65-
endDate,
66-
period: selectedPeriod as "perDay" | "perWeek" | "perMonth",
67-
isAll,
68-
routingFormId,
47+
...routingParams,
48+
period: selectedPeriod,
6949
searchQuery: searchQuery || undefined,
7050
});
7151

@@ -94,32 +74,14 @@ function DownloadButton({
9474
}
9575

9676
interface FormCardProps {
97-
selectedPeriod: string;
98-
onPeriodChange: (value: string) => void;
77+
selectedPeriod: "perDay" | "perWeek" | "perMonth";
78+
onPeriodChange: (value: "perDay" | "perWeek" | "perMonth") => void;
9979
searchQuery: string;
10080
onSearchChange: (value: string) => void;
10181
children: ReactNode;
102-
teamId?: number;
103-
userId?: number;
104-
isAll?: boolean;
105-
routingFormId?: string;
106-
startDate: string;
107-
endDate: string;
10882
}
10983

110-
function FormCard({
111-
selectedPeriod,
112-
onPeriodChange,
113-
searchQuery,
114-
onSearchChange,
115-
children,
116-
teamId,
117-
userId,
118-
isAll,
119-
routingFormId,
120-
startDate,
121-
endDate,
122-
}: FormCardProps) {
84+
function FormCard({ selectedPeriod, onPeriodChange, searchQuery, onSearchChange, children }: FormCardProps) {
12385
const { t } = useLocale();
12486

12587
return (
@@ -135,7 +97,7 @@ function FormCard({
13597
]}
13698
className="w-fit"
13799
value={selectedPeriod}
138-
onValueChange={(value) => value && onPeriodChange(value)}
100+
onValueChange={(value) => value && onPeriodChange(value as "perDay" | "perWeek" | "perMonth")}
139101
/>
140102
<div className="flex gap-2">
141103
<div className="w-64">
@@ -147,16 +109,7 @@ function FormCard({
147109
className="w-full"
148110
/>
149111
</div>
150-
<DownloadButton
151-
userId={userId}
152-
teamId={teamId}
153-
isAll={isAll}
154-
routingFormId={routingFormId}
155-
startDate={startDate}
156-
endDate={endDate}
157-
selectedPeriod={selectedPeriod}
158-
searchQuery={searchQuery}
159-
/>
112+
<DownloadButton selectedPeriod={selectedPeriod} searchQuery={searchQuery} />
160113
</div>
161114
</div>
162115
{children}
@@ -215,87 +168,56 @@ const getPerformanceBadge = (performance: RoutedToTableRow["performance"], t: TF
215168

216169
export function RoutedToPerPeriod() {
217170
const { t } = useLocale();
218-
const { userId, teamId, startDate, endDate, isAll, routingFormId } = useInsightsParameters();
219-
const [selectedPeriod, setSelectedPeriod] = useQueryState("selectedPeriod", {
220-
defaultValue: "perWeek",
221-
});
171+
const routingParams = useInsightsRoutingParameters();
172+
const [selectedPeriod, setSelectedPeriod] = useState<"perDay" | "perWeek" | "perMonth">("perWeek");
222173
const [searchQuery, setSearchQuery] = useQueryState("search", {
223174
defaultValue: "",
224175
});
225176

226-
const { ref: loadMoreRef } = useInViewObserver(() => {
227-
if (hasNextPage && !isFetchingNextPage) {
228-
fetchNextPage();
229-
}
230-
});
231-
232177
const tableContainerRef = useRef<HTMLDivElement>(null);
233-
234-
const { data, fetchNextPage, isFetchingNextPage, hasNextPage, isLoading } =
235-
trpc.viewer.insights.routedToPerPeriod.useInfiniteQuery(
236-
{
237-
userId,
238-
teamId,
239-
startDate,
240-
endDate,
241-
period: selectedPeriod as "perDay" | "perWeek" | "perMonth",
242-
isAll,
243-
routingFormId,
244-
searchQuery: searchQuery || undefined,
245-
limit: 10,
178+
const debouncedSearchQuery = useDebounce(searchQuery, 500);
179+
180+
const { data, isLoading } = trpc.viewer.insights.routedToPerPeriod.useQuery(
181+
{
182+
...routingParams,
183+
period: selectedPeriod,
184+
searchQuery: debouncedSearchQuery || undefined,
185+
},
186+
{
187+
staleTime: 30000,
188+
refetchOnWindowFocus: false,
189+
trpc: {
190+
context: { skipBatch: true },
246191
},
247-
{
248-
getNextPageParam: (lastPage) => {
249-
if (!lastPage.users.nextCursor && !lastPage.periodStats.nextCursor) {
250-
return undefined;
251-
}
252-
253-
return {
254-
userCursor: lastPage.users.nextCursor,
255-
periodCursor: lastPage.periodStats.nextCursor,
256-
};
257-
},
258-
}
259-
);
192+
}
193+
);
260194

261195
const flattenedUsers = useMemo(() => {
262-
const userMap = new Map();
263-
data?.pages.forEach((page) => {
264-
page.users.data.forEach((user) => {
265-
if (!userMap.has(user.id)) {
266-
userMap.set(user.id, user);
267-
}
268-
});
269-
});
270-
return Array.from(userMap.values());
271-
}, [data?.pages]);
196+
return data?.users.data || [];
197+
}, [data?.users.data]);
272198

273199
const uniquePeriods = useMemo(() => {
274-
if (!data?.pages) return [];
200+
if (!data?.periodStats.data) return [];
275201

276-
// Get all unique periods from all pages
202+
// Get all unique periods
277203
const periods = new Set<string>();
278-
data.pages.forEach((page) => {
279-
page.periodStats.data.forEach((stat) => {
280-
periods.add(stat.period_start.toISOString());
281-
});
204+
data.periodStats.data.forEach((stat) => {
205+
periods.add(stat.period_start.toISOString());
282206
});
283207

284208
return Array.from(periods)
285209
.map((dateStr) => new Date(dateStr))
286210
.sort((a, b) => a.getTime() - b.getTime());
287-
}, [data?.pages]);
211+
}, [data?.periodStats.data]);
288212

289213
const processedData = useMemo(() => {
290-
if (!data?.pages) return [];
214+
if (!data?.periodStats.data) return [];
291215

292216
// Create a map for quick lookup of stats
293217
const statsMap = new Map<string, number>();
294-
data.pages.forEach((page) => {
295-
page.periodStats.data.forEach((stat) => {
296-
const key = `${stat.userId}-${stat.period_start.toISOString()}`;
297-
statsMap.set(key, stat.total);
298-
});
218+
data.periodStats.data.forEach((stat) => {
219+
const key = `${stat.userId}-${stat.period_start.toISOString()}`;
220+
statsMap.set(key, stat.total);
299221
});
300222

301223
return flattenedUsers.map((user) => {
@@ -314,7 +236,7 @@ export function RoutedToPerPeriod() {
314236
totalBookings: user.totalBookings,
315237
};
316238
});
317-
}, [data?.pages, flattenedUsers, uniquePeriods]);
239+
}, [data?.periodStats.data, flattenedUsers, uniquePeriods]);
318240

319241
if (isLoading) {
320242
return (
@@ -327,13 +249,7 @@ export function RoutedToPerPeriod() {
327249
selectedPeriod={selectedPeriod}
328250
onPeriodChange={setSelectedPeriod}
329251
searchQuery={searchQuery}
330-
onSearchChange={setSearchQuery}
331-
userId={userId}
332-
teamId={teamId}
333-
isAll={isAll}
334-
routingFormId={routingFormId}
335-
startDate={startDate}
336-
endDate={endDate}>
252+
onSearchChange={setSearchQuery}>
337253
<div className="mt-6">
338254
<DataTableSkeleton columns={5} columnWidths={[200, 120, 120, 120, 120]} />
339255
</div>
@@ -368,13 +284,7 @@ export function RoutedToPerPeriod() {
368284
selectedPeriod={selectedPeriod}
369285
onPeriodChange={setSelectedPeriod}
370286
searchQuery={searchQuery}
371-
onSearchChange={setSearchQuery}
372-
userId={userId}
373-
teamId={teamId}
374-
isAll={isAll}
375-
routingFormId={routingFormId}
376-
startDate={startDate}
377-
endDate={endDate}>
287+
onSearchChange={setSearchQuery}>
378288
<div className="mt-6">
379289
<div
380290
className="scrollbar-thin border-subtle relative overflow-auto rounded-md border"
@@ -405,10 +315,7 @@ export function RoutedToPerPeriod() {
405315
<TableBody className="relative">
406316
{processedData.map((row, index) => {
407317
return (
408-
<TableRow
409-
key={row.id}
410-
ref={index === processedData.length - 1 ? loadMoreRef : undefined}
411-
className="divide-muted divide-x">
318+
<TableRow key={row.id} className="divide-muted divide-x">
412319
<TableCell className="bg-default w-[200px]">
413320
<HoverCard>
414321
<HoverCardTrigger asChild>

packages/features/insights/server/raw-data.schema.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ export const routingRepositoryBaseInputSchema = z.object({
8989
columnFilters: z.array(ZColumnFilter).optional(),
9090
});
9191

92+
export const routedToPerPeriodInputSchema = routingRepositoryBaseInputSchema.extend({
93+
period: z.enum(["perDay", "perWeek", "perMonth"]),
94+
limit: z.number().int().min(1).max(100).default(10),
95+
searchQuery: z.string().trim().min(1).optional(),
96+
});
97+
98+
export const routedToPerPeriodCsvInputSchema = routingRepositoryBaseInputSchema.extend({
99+
period: z.enum(["perDay", "perWeek", "perMonth"]),
100+
searchQuery: z.string().trim().min(1).optional(),
101+
});
102+
92103
export const bookingRepositoryBaseInputSchema = z.object({
93104
scope: z.union([z.literal("user"), z.literal("team"), z.literal("org")]),
94105
selectedTeamId: z.number().optional(),

0 commit comments

Comments
 (0)