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 <hey@eunjae.dev>
This commit is contained in:
co-authored by
Eunjae Lee
parent
28ff809739
commit
efca90507b
@@ -19,6 +19,8 @@ import {
|
||||
LowestRatedMembersTable,
|
||||
MostBookedTeamMembersTable,
|
||||
MostCancelledBookingsTables,
|
||||
MostCompletedTeamMembersTable,
|
||||
LeastCompletedTeamMembersTable,
|
||||
PopularEventsTable,
|
||||
RecentFeedbackTable,
|
||||
TimezoneBadge,
|
||||
@@ -82,20 +84,23 @@ function InsightsPageContent() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
|
||||
<div className="sm:col-span-2">
|
||||
<PopularEventsTable />
|
||||
</div>
|
||||
<MostBookedTeamMembersTable />
|
||||
<LeastBookedTeamMembersTable />
|
||||
<MostCompletedTeamMembersTable />
|
||||
<LeastCompletedTeamMembersTable />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
|
||||
<MostCancelledBookingsTables />
|
||||
<HighestNoShowHostTable />
|
||||
<div className="sm:col-span-2">
|
||||
<PopularEventsTable />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
|
||||
<HighestRatedMembersTable />
|
||||
<LowestRatedMembersTable />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-4">
|
||||
<div className="sm:col-span-2">
|
||||
<RecentFeedbackTable />
|
||||
</div>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -28,7 +28,7 @@ export const LeastBookedTeamMembersTable = () => {
|
||||
if (!isSuccess || !data) return null;
|
||||
|
||||
return (
|
||||
<ChartCard title={t("least_booked_members")}>
|
||||
<ChartCard title={t("least_bookings_scheduled")}>
|
||||
<UserStatsTable data={data} />
|
||||
</ChartCard>
|
||||
);
|
||||
|
||||
@@ -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 <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !data) return null;
|
||||
|
||||
return (
|
||||
<ChartCard title={t("least_bookings_completed")}>
|
||||
<UserStatsTable data={data} />
|
||||
</ChartCard>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ export const MostBookedTeamMembersTable = () => {
|
||||
if (!isSuccess || !data) return null;
|
||||
|
||||
return (
|
||||
<ChartCard title={t("most_booked_members")}>
|
||||
<ChartCard title={t("most_bookings_scheduled")}>
|
||||
<UserStatsTable data={data} />
|
||||
</ChartCard>
|
||||
);
|
||||
|
||||
@@ -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 <LoadingInsight />;
|
||||
|
||||
if (!isSuccess || !data) return null;
|
||||
|
||||
return (
|
||||
<ChartCard title={t("most_bookings_completed")}>
|
||||
<UserStatsTable data={data} />
|
||||
</ChartCard>
|
||||
);
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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<UserStatsData> {
|
||||
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<
|
||||
|
||||
Reference in New Issue
Block a user