Skip to content

Commit a2205d4

Browse files
eunjae-leeemrysal
andauthored
feat: add booking status filter to /insights (calcom#22842)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
1 parent cf4e7a5 commit a2205d4

11 files changed

Lines changed: 240 additions & 59 deletions

File tree

apps/web/playwright/insights.e2e.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -203,32 +203,24 @@ test.describe("Insights", async () => {
203203
await page.keyboard.press("Escape");
204204

205205
// Choose User filter item from dropdown
206-
await addFilter(page, "bookingUserId");
206+
await addFilter(page, "userId");
207207

208-
// Wait for the URL to include bookingUserId
209-
await page.waitForURL((url) => url.toString().includes("bookingUserId"));
208+
// Wait for the URL to include userId
209+
await page.waitForURL((url) => url.toString().includes("userId"));
210210

211211
// Click User filter to see a user list
212-
await openFilter(page, "bookingUserId");
212+
await openFilter(page, "userId");
213213

214-
await page
215-
.locator('[data-testid="select-filter-options-bookingUserId"]')
216-
.getByRole("option")
217-
.nth(0)
218-
.click();
214+
await page.locator('[data-testid="select-filter-options-userId"]').getByRole("option").nth(0).click();
219215

220-
await page
221-
.locator('[data-testid="select-filter-options-bookingUserId"]')
222-
.getByRole("option")
223-
.nth(1)
224-
.click();
216+
await page.locator('[data-testid="select-filter-options-userId"]').getByRole("option").nth(1).click();
225217

226218
// press escape button to close the filter
227219
await page.keyboard.press("Escape");
228220

229221
await clearFilters(page);
230222

231-
await expect(page.url()).not.toContain("bookingUserId");
223+
await expect(page.url()).not.toContain("userId");
232224
});
233225

234226
test("should test download button", async ({ page, users }) => {

apps/web/public/static/locales/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,5 +3429,6 @@
34293429
"usage_based_expiration_description": "This link can be used for {{count}} booking",
34303430
"usage_based_generic_expiration_description": "This link can be configured to expire after a set number of bookings",
34313431
"usage_based_expiration_description_plural": "This link can be used for {{count}} bookings",
3432+
"booking_status": "Booking status",
34323433
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
34333434
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import { trpc } from "@calcom/trpc";
2121
import { RoutingFormResponsesDownload } from "../../filters/Download";
2222
import { OrgTeamsFilter } from "../../filters/OrgTeamsFilter";
2323
import { useInsightsColumns } from "../../hooks/useInsightsColumns";
24-
import { useInsightsFacetedUniqueValues } from "../../hooks/useInsightsFacetedUniqueValues";
2524
import { useInsightsParameters } from "../../hooks/useInsightsParameters";
25+
import { useInsightsRoutingFacetedUniqueValues } from "../../hooks/useInsightsRoutingFacetedUniqueValues";
2626
import type { RoutingFormTableRow } from "../../lib/types";
2727
import { RoutingKPICards } from "./RoutingKPICards";
2828

@@ -49,7 +49,12 @@ export function RoutingFormResponsesTable() {
4949
routingFormId,
5050
});
5151

52-
const getInsightsFacetedUniqueValues = useInsightsFacetedUniqueValues({ headers, userId, teamId, isAll });
52+
const getInsightsFacetedUniqueValues = useInsightsRoutingFacetedUniqueValues({
53+
headers,
54+
userId,
55+
teamId,
56+
isAll,
57+
});
5358

5459
const { sorting, limit, offset, ctaContainerRef, updateFilter } = useDataTable();
5560

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { Table } from "@tanstack/react-table";
2+
import { useCallback } from "react";
3+
4+
import { convertFacetedValuesToMap, type FacetedValue } from "@calcom/features/data-table";
5+
import { BookingStatus } from "@calcom/prisma/enums";
6+
import { trpc } from "@calcom/trpc";
7+
8+
import { bookingStatusToText } from "../lib/bookingStatusToText";
9+
10+
const statusOrder: Record<BookingStatus, number> = {
11+
[BookingStatus.ACCEPTED]: 1,
12+
[BookingStatus.PENDING]: 2,
13+
[BookingStatus.AWAITING_HOST]: 3,
14+
[BookingStatus.CANCELLED]: 4,
15+
[BookingStatus.REJECTED]: 5,
16+
};
17+
18+
export const useInsightsBookingFacetedUniqueValues = ({
19+
userId,
20+
teamId,
21+
isAll,
22+
}: {
23+
userId: number | undefined;
24+
teamId: number | undefined;
25+
isAll: boolean;
26+
}) => {
27+
const { data: users } = trpc.viewer.insights.userList.useQuery(
28+
{
29+
teamId,
30+
isAll,
31+
},
32+
{
33+
refetchOnWindowFocus: false,
34+
}
35+
);
36+
37+
const { data: eventTypes } = trpc.viewer.insights.eventTypeList.useQuery(
38+
{
39+
teamId,
40+
userId,
41+
isAll,
42+
},
43+
{
44+
refetchOnWindowFocus: false,
45+
}
46+
);
47+
48+
return useCallback(
49+
(_: Table<any>, columnId: string) => (): Map<FacetedValue, number> => {
50+
if (columnId === "status") {
51+
return convertFacetedValuesToMap(
52+
Object.keys(statusOrder).map((status) => ({
53+
value: status.toLowerCase(),
54+
label: bookingStatusToText(status as BookingStatus),
55+
}))
56+
);
57+
} else if (columnId === "userId") {
58+
return convertFacetedValuesToMap(
59+
users?.map((user) => ({
60+
label: user.name ?? user.email,
61+
value: user.id,
62+
})) ?? []
63+
);
64+
} else if (columnId === "eventTypeId") {
65+
return convertFacetedValuesToMap(
66+
eventTypes?.map((eventType) => ({
67+
value: eventType.id,
68+
label: eventType.teamId ? `${eventType.title} (${eventType.team?.name})` : eventType.title,
69+
})) ?? []
70+
);
71+
}
72+
return new Map<FacetedValue, number>();
73+
},
74+
[users, eventTypes]
75+
);
76+
};

packages/features/insights/hooks/useInsightsBookingParameters.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useMemo } from "react";
22

33
import dayjs from "@calcom/dayjs";
4-
import { ZSingleSelectFilterValue, ZDateRangeFilterValue } from "@calcom/features/data-table";
4+
import { ZDateRangeFilterValue } from "@calcom/features/data-table";
55
import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime";
6+
import { useColumnFilters } from "@calcom/features/data-table/hooks/useColumnFilters";
67
import { useDataTable } from "@calcom/features/data-table/hooks/useDataTable";
78
import { useFilterValue } from "@calcom/features/data-table/hooks/useFilterValue";
89
import { getDefaultStartDate, getDefaultEndDate } from "@calcom/features/data-table/lib/dateRange";
@@ -14,8 +15,6 @@ export function useInsightsBookingParameters() {
1415
const { scope, selectedTeamId } = useInsightsOrgTeams();
1516
const { timeZone } = useDataTable();
1617

17-
const memberUserId = useFilterValue("bookingUserId", ZSingleSelectFilterValue)?.data as number | undefined;
18-
const eventTypeId = useFilterValue("eventTypeId", ZSingleSelectFilterValue)?.data as number | undefined;
1918
const createdAtRange = useFilterValue("createdAt", ZDateRangeFilterValue)?.data;
2019
// TODO for future: this preserving local time & startOf & endOf should be handled
2120
// from DateRangeFilter out of the box.
@@ -34,14 +33,14 @@ export function useInsightsBookingParameters() {
3433
.toISOString();
3534
}, [createdAtRange?.endDate])
3635
);
36+
const columnFilters = useColumnFilters({ exclude: ["createdAt"] });
3737

3838
return {
3939
scope,
4040
selectedTeamId,
4141
startDate,
4242
endDate,
4343
timeZone: timeZone || CURRENT_TIMEZONE,
44-
eventTypeId,
45-
memberUserId,
44+
columnFilters,
4645
};
4746
}

packages/features/insights/hooks/useInsightsBookings.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,24 @@ import { useMemo } from "react";
33

44
import { ColumnFilterType } from "@calcom/features/data-table";
55
import { useLocale } from "@calcom/lib/hooks/useLocale";
6+
import type { BookingStatus } from "@calcom/prisma/enums";
67

7-
import type { HeaderRow, RoutingFormTableRow } from "../lib/types";
8-
import { useInsightsFacetedUniqueValues } from "./useInsightsFacetedUniqueValues";
8+
import { useInsightsBookingFacetedUniqueValues } from "./useInsightsBookingFacetedUniqueValues";
99
import { useInsightsOrgTeams } from "./useInsightsOrgTeams";
1010

1111
type DummyTableRow = {
12-
bookingUserId: RoutingFormTableRow["bookingUserId"];
12+
userId: number | null;
1313
eventTypeId: number | null;
14+
status: BookingStatus;
1415
};
1516

1617
const emptyData: DummyTableRow[] = [];
17-
const dummyHeaders: HeaderRow[] = [];
1818

1919
export const useInsightsBookings = () => {
2020
const { t } = useLocale();
2121
const { isAll, teamId, userId } = useInsightsOrgTeams();
2222

23-
const getInsightsFacetedUniqueValues = useInsightsFacetedUniqueValues({
24-
headers: dummyHeaders,
23+
const getInsightsFacetedUniqueValues = useInsightsBookingFacetedUniqueValues({
2524
userId,
2625
teamId,
2726
isAll,
@@ -30,31 +29,44 @@ export const useInsightsBookings = () => {
3029
const columns = useMemo(() => {
3130
const columnHelper = createColumnHelper<DummyTableRow>();
3231
return [
33-
columnHelper.accessor("bookingUserId", {
34-
id: "bookingUserId",
35-
header: t("user"),
36-
enableColumnFilter: true,
37-
enableSorting: false,
32+
columnHelper.accessor("eventTypeId", {
33+
id: "eventTypeId",
34+
header: t("event_type"),
35+
size: 200,
3836
meta: {
3937
filter: {
4038
type: ColumnFilterType.SINGLE_SELECT,
4139
},
4240
},
41+
enableColumnFilter: true,
42+
enableSorting: false,
4343
cell: () => null,
4444
}),
45-
columnHelper.accessor("eventTypeId", {
46-
id: "eventTypeId",
47-
header: t("event_type"),
45+
columnHelper.accessor("status", {
46+
id: "status",
47+
header: t("booking_status"),
4848
size: 200,
4949
meta: {
5050
filter: {
51-
type: ColumnFilterType.SINGLE_SELECT,
51+
type: ColumnFilterType.MULTI_SELECT,
5252
},
5353
},
5454
enableColumnFilter: true,
5555
enableSorting: false,
5656
cell: () => null,
5757
}),
58+
columnHelper.accessor("userId", {
59+
id: "userId",
60+
header: t("member"),
61+
enableColumnFilter: true,
62+
enableSorting: false,
63+
meta: {
64+
filter: {
65+
type: ColumnFilterType.SINGLE_SELECT,
66+
},
67+
},
68+
cell: () => null,
69+
}),
5870
];
5971
}, [t]);
6072

packages/features/insights/hooks/useInsightsFacetedUniqueValues.ts renamed to packages/features/insights/hooks/useInsightsRoutingFacetedUniqueValues.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const statusOrder: Record<BookingStatus, number> = {
1616
[BookingStatus.REJECTED]: 5,
1717
};
1818

19-
export const useInsightsFacetedUniqueValues = ({
19+
export const useInsightsRoutingFacetedUniqueValues = ({
2020
headers,
2121
userId,
2222
teamId,

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,5 @@ export const bookingRepositoryBaseInputSchema = z.object({
7676
startDate: z.string(),
7777
endDate: z.string(),
7878
timeZone: z.string(),
79-
eventTypeId: z.coerce.number().optional().nullable(),
80-
memberUserId: z.coerce.number().optional().nullable(),
79+
columnFilters: z.array(ZColumnFilter).optional(),
8180
});

packages/features/insights/server/trpc-router.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ function createInsightsBookingService(
320320
input: z.infer<typeof bookingRepositoryBaseInputSchema>,
321321
dateTarget: "createdAt" | "startTime" = "createdAt"
322322
) {
323-
const { scope, selectedTeamId, eventTypeId, memberUserId, startDate, endDate } = input;
323+
const { scope, selectedTeamId, startDate, endDate, columnFilters } = input;
324324

325325
return new InsightsBookingService({
326326
prisma: ctx.insightsDb,
@@ -331,8 +331,7 @@ function createInsightsBookingService(
331331
...(selectedTeamId && { teamId: selectedTeamId }),
332332
},
333333
filters: {
334-
...(eventTypeId && { eventTypeId }),
335-
...(memberUserId && { memberUserId }),
334+
...(columnFilters && { columnFilters }),
336335
dateRange: {
337336
target: dateTarget,
338337
startDate,

0 commit comments

Comments
 (0)