From efca90507bc77811df63a987908ee799a04562ad Mon Sep 17 00:00:00 2001 From: Bandhan Majumder <133476557+bandhan-majumder@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:47:07 +0530 Subject: [PATCH] refactor: improve most booked & least booked (#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 --- apps/web/modules/insights/insights-view.tsx | 15 ++++-- apps/web/public/static/locales/en/common.json | 4 ++ .../booking/LeastBookedTeamMembersTable.tsx | 2 +- .../booking/LeastCompletedBookings.tsx | 51 +++++++++++++++++++ .../booking/MostBookedTeamMembersTable.tsx | 2 +- .../booking/MostCompletedBookings.tsx | 51 +++++++++++++++++++ .../insights/components/booking/index.ts | 2 + .../features/insights/server/trpc-router.ts | 23 ++++++++- .../service/InsightsBookingBaseService.ts | 4 +- 9 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 packages/features/insights/components/booking/LeastCompletedBookings.tsx create mode 100644 packages/features/insights/components/booking/MostCompletedBookings.tsx diff --git a/apps/web/modules/insights/insights-view.tsx b/apps/web/modules/insights/insights-view.tsx index 2b5e1c3af2..1d466baa83 100644 --- a/apps/web/modules/insights/insights-view.tsx +++ b/apps/web/modules/insights/insights-view.tsx @@ -19,6 +19,8 @@ import { LowestRatedMembersTable, MostBookedTeamMembersTable, MostCancelledBookingsTables, + MostCompletedTeamMembersTable, + LeastCompletedTeamMembersTable, PopularEventsTable, RecentFeedbackTable, TimezoneBadge, @@ -82,20 +84,23 @@ function InsightsPageContent() {
-
- -
+ +
+
+ +
+
+ +
-
-
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index fafa47b34e..e91d1e3f5c 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2187,6 +2187,10 @@ "bookings_by_hour": "Bookings by Hour", "most_booked_members": "Most Booked", "least_booked_members": "Least Booked", + "most_bookings_scheduled": "Most Bookings Scheduled", + "least_bookings_scheduled": "Least Bookings Scheduled", + "most_bookings_completed": "Most Bookings Completed", + "least_bookings_completed": "Least Bookings Completed", "events_created": "Events Created", "events_completed": "Events Completed", "events_cancelled": "Events Cancelled", diff --git a/packages/features/insights/components/booking/LeastBookedTeamMembersTable.tsx b/packages/features/insights/components/booking/LeastBookedTeamMembersTable.tsx index a44e462515..8fd16a782c 100644 --- a/packages/features/insights/components/booking/LeastBookedTeamMembersTable.tsx +++ b/packages/features/insights/components/booking/LeastBookedTeamMembersTable.tsx @@ -28,7 +28,7 @@ export const LeastBookedTeamMembersTable = () => { if (!isSuccess || !data) return null; return ( - + ); diff --git a/packages/features/insights/components/booking/LeastCompletedBookings.tsx b/packages/features/insights/components/booking/LeastCompletedBookings.tsx new file mode 100644 index 0000000000..4b147cd152 --- /dev/null +++ b/packages/features/insights/components/booking/LeastCompletedBookings.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useMemo } from "react"; + +import dayjs from "@calcom/dayjs"; +import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime"; +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"; +import { UserStatsTable } from "../UserStatsTable"; + +export const LeastCompletedTeamMembersTable = () => { + const { t } = useLocale(); + let insightsBookingParams = useInsightsBookingParameters(); + + const currentTime = useChangeTimeZoneWithPreservedLocalTime( + useMemo(() => { + return dayjs().toISOString(); + }, []) + ); + + // booking with endDate < now is "accepted" booking + insightsBookingParams = { + ...insightsBookingParams, + endDate: currentTime, + }; + + const { data, isSuccess, isPending } = trpc.viewer.insights.membersWithLeastCompletedBookings.useQuery( + insightsBookingParams, + { + staleTime: 180000, + refetchOnWindowFocus: false, + trpc: { + context: { skipBatch: true }, + }, + } + ); + + if (isPending) return ; + + if (!isSuccess || !data) return null; + + return ( + + + + ); +}; diff --git a/packages/features/insights/components/booking/MostBookedTeamMembersTable.tsx b/packages/features/insights/components/booking/MostBookedTeamMembersTable.tsx index f2009810f5..9e71e0452f 100644 --- a/packages/features/insights/components/booking/MostBookedTeamMembersTable.tsx +++ b/packages/features/insights/components/booking/MostBookedTeamMembersTable.tsx @@ -28,7 +28,7 @@ export const MostBookedTeamMembersTable = () => { if (!isSuccess || !data) return null; return ( - + ); diff --git a/packages/features/insights/components/booking/MostCompletedBookings.tsx b/packages/features/insights/components/booking/MostCompletedBookings.tsx new file mode 100644 index 0000000000..57ea6aced8 --- /dev/null +++ b/packages/features/insights/components/booking/MostCompletedBookings.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useMemo } from "react"; + +import dayjs from "@calcom/dayjs"; +import { useChangeTimeZoneWithPreservedLocalTime } from "@calcom/features/data-table/hooks/useChangeTimeZoneWithPreservedLocalTime"; +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"; +import { UserStatsTable } from "../UserStatsTable"; + +export const MostCompletedTeamMembersTable = () => { + const { t } = useLocale(); + let insightsBookingParams = useInsightsBookingParameters(); + + const currentTime = useChangeTimeZoneWithPreservedLocalTime( + useMemo(() => { + return dayjs().toISOString(); + }, []) + ); + + // booking with endDate < now is "accepted" booking + insightsBookingParams = { + ...insightsBookingParams, + endDate: currentTime, + }; + + const { data, isSuccess, isPending } = trpc.viewer.insights.membersWithMostCompletedBookings.useQuery( + insightsBookingParams, + { + staleTime: 180000, + refetchOnWindowFocus: false, + trpc: { + context: { skipBatch: true }, + }, + } + ); + + if (isPending) return ; + + if (!isSuccess || !data) return null; + + return ( + + + + ); +}; diff --git a/packages/features/insights/components/booking/index.ts b/packages/features/insights/components/booking/index.ts index 33009e88a7..b0b287a166 100644 --- a/packages/features/insights/components/booking/index.ts +++ b/packages/features/insights/components/booking/index.ts @@ -11,3 +11,5 @@ export { MostCancelledBookingsTables } from "./MostCancelledBookingsTables"; export { PopularEventsTable } from "./PopularEventsTable"; export { RecentFeedbackTable } from "./RecentFeedbackTable"; export { TimezoneBadge } from "./TimezoneBadge"; +export { MostCompletedTeamMembersTable } from "./MostCompletedBookings"; +export { LeastCompletedTeamMembersTable } from "./LeastCompletedBookings"; diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index 7384ae7dc0..05d700d0ae 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -321,7 +321,6 @@ function createInsightsBookingService( dateTarget: "createdAt" | "startTime" = "createdAt" ) { const { scope, selectedTeamId, startDate, endDate, columnFilters } = input; - return getInsightsBookingService({ options: { scope, @@ -563,6 +562,28 @@ export const insightsRouter = router({ throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); } }), + membersWithMostCompletedBookings: userBelongsToTeamProcedure + .input(bookingRepositoryBaseInputSchema) + .query(async ({ input, ctx }) => { + const insightsBookingService = createInsightsBookingService(ctx, input, "startTime"); + + try { + return await insightsBookingService.getMembersStatsWithCount("accepted", "DESC"); + } catch (e) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); + } + }), + membersWithLeastCompletedBookings: userBelongsToTeamProcedure + .input(bookingRepositoryBaseInputSchema) + .query(async ({ input, ctx }) => { + const insightsBookingService = createInsightsBookingService(ctx, input, "startTime"); + + try { + return await insightsBookingService.getMembersStatsWithCount("accepted", "ASC"); + } catch (e) { + throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" }); + } + }), membersWithMostBookings: userBelongsToTeamProcedure .input(bookingRepositoryBaseInputSchema) .query(async ({ input, ctx }) => { diff --git a/packages/lib/server/service/InsightsBookingBaseService.ts b/packages/lib/server/service/InsightsBookingBaseService.ts index 7e669b5ad8..7468a44886 100644 --- a/packages/lib/server/service/InsightsBookingBaseService.ts +++ b/packages/lib/server/service/InsightsBookingBaseService.ts @@ -809,7 +809,7 @@ export class InsightsBookingBaseService { } async getMembersStatsWithCount( - type: "all" | "cancelled" | "noShow" = "all", + type: "all" | "accepted" | "cancelled" | "noShow" = "all", sortOrder: "ASC" | "DESC" = "DESC" ): Promise { const baseConditions = await this.getBaseConditions(); @@ -819,6 +819,8 @@ export class InsightsBookingBaseService { additionalCondition = Prisma.sql`AND status = 'cancelled'`; } else if (type === "noShow") { additionalCondition = Prisma.sql`AND "noShowHost" = true`; + } else if (type === "accepted") { + additionalCondition = Prisma.sql`AND status = 'accepted'`; } const bookingsFromTeam = await this.prisma.$queryRaw<