Skip to content

Commit efca905

Browse files
refactor: improve most booked & least booked (calcom#23192)
* refactor: improve most booked & least booked * add 2 new charts and trpc handlers * update arg * update comment * completed booking filter * update * add change in component level and remove unused translations * current time and accepted status logic handler * resize some charts * restore some i18n * restore i18n * restore i18n --------- Co-authored-by: Eunjae Lee <hey@eunjae.dev>
1 parent 28ff809 commit efca905

9 files changed

Lines changed: 145 additions & 9 deletions

File tree

apps/web/modules/insights/insights-view.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
LowestRatedMembersTable,
2020
MostBookedTeamMembersTable,
2121
MostCancelledBookingsTables,
22+
MostCompletedTeamMembersTable,
23+
LeastCompletedTeamMembersTable,
2224
PopularEventsTable,
2325
RecentFeedbackTable,
2426
TimezoneBadge,
@@ -82,20 +84,23 @@ function InsightsPageContent() {
8284
</div>
8385

8486
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
85-
<div className="sm:col-span-2">
86-
<PopularEventsTable />
87-
</div>
8887
<MostBookedTeamMembersTable />
8988
<LeastBookedTeamMembersTable />
89+
<MostCompletedTeamMembersTable />
90+
<LeastCompletedTeamMembersTable />
9091
</div>
9192

9293
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
9394
<MostCancelledBookingsTables />
9495
<HighestNoShowHostTable />
95-
<HighestRatedMembersTable />
96-
<LowestRatedMembersTable />
96+
<div className="sm:col-span-2">
97+
<PopularEventsTable />
98+
</div>
9799
</div>
100+
98101
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
102+
<HighestRatedMembersTable />
103+
<LowestRatedMembersTable />
99104
<div className="sm:col-span-2">
100105
<RecentFeedbackTable />
101106
</div>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2187,6 +2187,10 @@
21872187
"bookings_by_hour": "Bookings by Hour",
21882188
"most_booked_members": "Most Booked",
21892189
"least_booked_members": "Least Booked",
2190+
"most_bookings_scheduled": "Most Bookings Scheduled",
2191+
"least_bookings_scheduled": "Least Bookings Scheduled",
2192+
"most_bookings_completed": "Most Bookings Completed",
2193+
"least_bookings_completed": "Least Bookings Completed",
21902194
"events_created": "Events Created",
21912195
"events_completed": "Events Completed",
21922196
"events_cancelled": "Events Cancelled",

packages/features/insights/components/booking/LeastBookedTeamMembersTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const LeastBookedTeamMembersTable = () => {
2828
if (!isSuccess || !data) return null;
2929

3030
return (
31-
<ChartCard title={t("least_booked_members")}>
31+
<ChartCard title={t("least_bookings_scheduled")}>
3232
<UserStatsTable data={data} />
3333
</ChartCard>
3434
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { useMemo } from "react";
4+
5+
import dayjs from "@calcom/dayjs";
6+
import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime";
7+
import { useLocale } from "@calcom/lib/hooks/useLocale";
8+
import { trpc } from "@calcom/trpc";
9+
10+
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
11+
import { ChartCard } from "../ChartCard";
12+
import { LoadingInsight } from "../LoadingInsights";
13+
import { UserStatsTable } from "../UserStatsTable";
14+
15+
export const LeastCompletedTeamMembersTable = () => {
16+
const { t } = useLocale();
17+
let insightsBookingParams = useInsightsBookingParameters();
18+
19+
const currentTime = useChangeTimeZoneWithPreservedLocalTime(
20+
useMemo(() => {
21+
return dayjs().toISOString();
22+
}, [])
23+
);
24+
25+
// booking with endDate < now is "accepted" booking
26+
insightsBookingParams = {
27+
...insightsBookingParams,
28+
endDate: currentTime,
29+
};
30+
31+
const { data, isSuccess, isPending } = trpc.viewer.insights.membersWithLeastCompletedBookings.useQuery(
32+
insightsBookingParams,
33+
{
34+
staleTime: 180000,
35+
refetchOnWindowFocus: false,
36+
trpc: {
37+
context: { skipBatch: true },
38+
},
39+
}
40+
);
41+
42+
if (isPending) return <LoadingInsight />;
43+
44+
if (!isSuccess || !data) return null;
45+
46+
return (
47+
<ChartCard title={t("least_bookings_completed")}>
48+
<UserStatsTable data={data} />
49+
</ChartCard>
50+
);
51+
};

packages/features/insights/components/booking/MostBookedTeamMembersTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const MostBookedTeamMembersTable = () => {
2828
if (!isSuccess || !data) return null;
2929

3030
return (
31-
<ChartCard title={t("most_booked_members")}>
31+
<ChartCard title={t("most_bookings_scheduled")}>
3232
<UserStatsTable data={data} />
3333
</ChartCard>
3434
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { useMemo } from "react";
4+
5+
import dayjs from "@calcom/dayjs";
6+
import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime";
7+
import { useLocale } from "@calcom/lib/hooks/useLocale";
8+
import { trpc } from "@calcom/trpc";
9+
10+
import { useInsightsBookingParameters } from "../../hooks/useInsightsBookingParameters";
11+
import { ChartCard } from "../ChartCard";
12+
import { LoadingInsight } from "../LoadingInsights";
13+
import { UserStatsTable } from "../UserStatsTable";
14+
15+
export const MostCompletedTeamMembersTable = () => {
16+
const { t } = useLocale();
17+
let insightsBookingParams = useInsightsBookingParameters();
18+
19+
const currentTime = useChangeTimeZoneWithPreservedLocalTime(
20+
useMemo(() => {
21+
return dayjs().toISOString();
22+
}, [])
23+
);
24+
25+
// booking with endDate < now is "accepted" booking
26+
insightsBookingParams = {
27+
...insightsBookingParams,
28+
endDate: currentTime,
29+
};
30+
31+
const { data, isSuccess, isPending } = trpc.viewer.insights.membersWithMostCompletedBookings.useQuery(
32+
insightsBookingParams,
33+
{
34+
staleTime: 180000,
35+
refetchOnWindowFocus: false,
36+
trpc: {
37+
context: { skipBatch: true },
38+
},
39+
}
40+
);
41+
42+
if (isPending) return <LoadingInsight />;
43+
44+
if (!isSuccess || !data) return null;
45+
46+
return (
47+
<ChartCard title={t("most_bookings_completed")}>
48+
<UserStatsTable data={data} />
49+
</ChartCard>
50+
);
51+
};

packages/features/insights/components/booking/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ export { MostCancelledBookingsTables } from "./MostCancelledBookingsTables";
1111
export { PopularEventsTable } from "./PopularEventsTable";
1212
export { RecentFeedbackTable } from "./RecentFeedbackTable";
1313
export { TimezoneBadge } from "./TimezoneBadge";
14+
export { MostCompletedTeamMembersTable } from "./MostCompletedBookings";
15+
export { LeastCompletedTeamMembersTable } from "./LeastCompletedBookings";

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,6 @@ function createInsightsBookingService(
321321
dateTarget: "createdAt" | "startTime" = "createdAt"
322322
) {
323323
const { scope, selectedTeamId, startDate, endDate, columnFilters } = input;
324-
325324
return getInsightsBookingService({
326325
options: {
327326
scope,
@@ -563,6 +562,28 @@ export const insightsRouter = router({
563562
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
564563
}
565564
}),
565+
membersWithMostCompletedBookings: userBelongsToTeamProcedure
566+
.input(bookingRepositoryBaseInputSchema)
567+
.query(async ({ input, ctx }) => {
568+
const insightsBookingService = createInsightsBookingService(ctx, input, "startTime");
569+
570+
try {
571+
return await insightsBookingService.getMembersStatsWithCount("accepted", "DESC");
572+
} catch (e) {
573+
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
574+
}
575+
}),
576+
membersWithLeastCompletedBookings: userBelongsToTeamProcedure
577+
.input(bookingRepositoryBaseInputSchema)
578+
.query(async ({ input, ctx }) => {
579+
const insightsBookingService = createInsightsBookingService(ctx, input, "startTime");
580+
581+
try {
582+
return await insightsBookingService.getMembersStatsWithCount("accepted", "ASC");
583+
} catch (e) {
584+
throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
585+
}
586+
}),
566587
membersWithMostBookings: userBelongsToTeamProcedure
567588
.input(bookingRepositoryBaseInputSchema)
568589
.query(async ({ input, ctx }) => {

packages/lib/server/service/InsightsBookingBaseService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ export class InsightsBookingBaseService {
809809
}
810810

811811
async getMembersStatsWithCount(
812-
type: "all" | "cancelled" | "noShow" = "all",
812+
type: "all" | "accepted" | "cancelled" | "noShow" = "all",
813813
sortOrder: "ASC" | "DESC" = "DESC"
814814
): Promise<UserStatsData> {
815815
const baseConditions = await this.getBaseConditions();
@@ -819,6 +819,8 @@ export class InsightsBookingBaseService {
819819
additionalCondition = Prisma.sql`AND status = 'cancelled'`;
820820
} else if (type === "noShow") {
821821
additionalCondition = Prisma.sql`AND "noShowHost" = true`;
822+
} else if (type === "accepted") {
823+
additionalCondition = Prisma.sql`AND status = 'accepted'`;
822824
}
823825

824826
const bookingsFromTeam = await this.prisma.$queryRaw<

0 commit comments

Comments
 (0)