Added new table under Insights page for most cancelled bookings (#19858)
- Added new Table componnent - Added trpc call - Added locale title fixed rebase changes Co-authored-by: amrit <iamamrit27@gmail.com>
This commit is contained in:
1 parent
a05935cdf6
commit
3834e69100
5 files changed
+139
-11
No files matched your search
@@ -11,13 +11,14 @@ import {
|
||||
AverageEventDurationChart,
|
||||
BookingKPICards,
|
||||
BookingStatusLineChart,
|
||||
LeastBookedTeamMembersTable,
|
||||
MostBookedTeamMembersTable,
|
||||
PopularEventsTable,
|
||||
HighestNoShowHostTable,
|
||||
RecentFeedbackTable,
|
||||
HighestRatedMembersTable,
|
||||
LeastBookedTeamMembersTable,
|
||||
LowestRatedMembersTable,
|
||||
MostBookedTeamMembersTable,
|
||||
MostCancelledBookingsTables,
|
||||
PopularEventsTable,
|
||||
RecentFeedbackTable,
|
||||
} from "@calcom/features/insights/components";
|
||||
import "@calcom/features/insights/components/tremor.css";
|
||||
import { InsightsOrgTeamsProvider } from "@calcom/features/insights/context/InsightsOrgTeamsProvider";
|
||||
@@ -76,6 +77,9 @@ function InsightsPageContent() {
|
||||
<MostBookedTeamMembersTable />
|
||||
<LeastBookedTeamMembersTable />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<MostCancelledBookingsTables />
|
||||
</div>
|
||||
<RecentFeedbackTable />
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<HighestNoShowHostTable />
|
||||
|
||||
@@ -3069,6 +3069,7 @@
|
||||
"display": "Display",
|
||||
"could_not_find_slug_to_publish_org": "Could not find slug to publish the organization",
|
||||
"picklist": "Picklist",
|
||||
"most_cancelled_bookings": "Most Cancelled Bookings",
|
||||
"name_or_email": "Name or Email",
|
||||
"salesforce_round_robin_skip_fallback_to_lead_owner": "If no contact is found, fallback to lead owner if it exists",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Title } from "@tremor/react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
|
||||
import { useInsightsParameters } from "../hooks/useInsightsParameters";
|
||||
import { CardInsights } from "./Card";
|
||||
import { LoadingInsight } from "./LoadingInsights";
|
||||
import { TotalBookingUsersTable } from "./TotalBookingUsersTable";
|
||||
|
||||
export const MostCancelledBookingsTables = () => {
|
||||
const { t } = useLocale();
|
||||
const { isAll, teamId, startDate, endDate, eventTypeId } = useInsightsParameters();
|
||||
|
||||
const { data, isSuccess, isPending } = trpc.viewer.insights.membersWithMostCancelledBookings.useQuery(
|
||||
{
|
||||
startDate,
|
||||
endDate,
|
||||
teamId,
|
||||
eventTypeId,
|
||||
isAll,
|
||||
},
|
||||
{
|
||||
staleTime: 30000,
|
||||
trpc: {
|
||||
context: { skipBatch: true },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (isPending) return <LoadingInsight />;
|
||||
|
||||
return (
|
||||
<CardInsights className="shadow-none">
|
||||
<Title className="text-emphasis">{t("most_cancelled_bookings")}</Title>
|
||||
{!isSuccess || !startDate || !endDate || !teamId ? null : <TotalBookingUsersTable data={data} />}
|
||||
</CardInsights>
|
||||
);
|
||||
};
|
||||
@@ -1,14 +1,15 @@
|
||||
export { AverageEventDurationChart } from "./AverageEventDurationChart";
|
||||
export { BookingKPICards } from "./BookingKPICards";
|
||||
export { RoutingKPICards } from "./RoutingKPICards";
|
||||
export { BookingStatusLineChart } from "./BookingStatusLineChart";
|
||||
export { LeastBookedTeamMembersTable } from "./LeastBookedTeamMembersTable";
|
||||
export { MostBookedTeamMembersTable } from "./MostBookedTeamMembersTable";
|
||||
export { PopularEventsTable } from "./PopularEventsTable";
|
||||
export { RecentFeedbackTable } from "./RecentFeedbackTable";
|
||||
export { FailedBookingsByField } from "./FailedBookingsByField";
|
||||
export { HighestNoShowHostTable } from "./HighestNoShowHostTable";
|
||||
export { HighestRatedMembersTable } from "./HighestRatedMembersTable";
|
||||
export { LeastBookedTeamMembersTable } from "./LeastBookedTeamMembersTable";
|
||||
export { LowestRatedMembersTable } from "./LowestRatedMembersTable";
|
||||
export { RoutingFormResponsesTable, type RoutingFormTableType } from "./RoutingFormResponsesTable";
|
||||
export { FailedBookingsByField } from "./FailedBookingsByField";
|
||||
export { MostBookedTeamMembersTable } from "./MostBookedTeamMembersTable";
|
||||
export { MostCancelledBookingsTables } from "./MostCancelledBookingsTables";
|
||||
export { PopularEventsTable } from "./PopularEventsTable";
|
||||
export { RecentFeedbackTable } from "./RecentFeedbackTable";
|
||||
export { RoutedToPerPeriod } from "./RoutedToPerPeriod";
|
||||
export { RoutingFormResponsesTable, type RoutingFormTableType } from "./RoutingFormResponsesTable";
|
||||
export { RoutingKPICards } from "./RoutingKPICards";
|
||||
@@ -755,6 +755,89 @@ export const insightsRouter = router({
|
||||
|
||||
return result;
|
||||
}),
|
||||
membersWithMostCancelledBookings: userBelongsToTeamProcedure
|
||||
.input(rawDataInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const { teamId, startDate, endDate, eventTypeId, isAll, userId, memberUserId } = input;
|
||||
|
||||
if (!teamId) {
|
||||
return [];
|
||||
}
|
||||
const user = ctx.user;
|
||||
|
||||
const r = await buildBaseWhereCondition({
|
||||
teamId,
|
||||
eventTypeId: eventTypeId ?? undefined,
|
||||
memberUserId: memberUserId ?? undefined,
|
||||
userId: userId ?? undefined,
|
||||
isAll: isAll ?? false,
|
||||
ctx: {
|
||||
userIsOwnerAdminOfParentTeam: ctx.user.isOwnerAdminOfParentTeam,
|
||||
userOrganizationId: ctx.user.organizationId,
|
||||
insightsDb: ctx.insightsDb,
|
||||
},
|
||||
});
|
||||
|
||||
let { whereCondition: bookingWhere } = r;
|
||||
|
||||
bookingWhere = {
|
||||
...bookingWhere,
|
||||
createdAt: {
|
||||
gte: dayjs(startDate).startOf("day").toDate(),
|
||||
lte: dayjs(endDate).endOf("day").toDate(),
|
||||
},
|
||||
status: "CANCELLED",
|
||||
};
|
||||
|
||||
const bookingsFromTeam = await ctx.insightsDb.bookingTimeStatus.groupBy({
|
||||
by: ["userId"],
|
||||
where: bookingWhere,
|
||||
_count: {
|
||||
id: true,
|
||||
},
|
||||
orderBy: {
|
||||
_count: {
|
||||
id: "desc",
|
||||
},
|
||||
},
|
||||
take: 10,
|
||||
});
|
||||
|
||||
const userIds = bookingsFromTeam.reduce((userIds: number[], booking) => {
|
||||
if (typeof booking.userId === "number" && !userIds.includes(booking.userId)) {
|
||||
userIds.push(booking.userId);
|
||||
}
|
||||
return userIds;
|
||||
}, []);
|
||||
|
||||
if (userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const usersFromTeam = await ctx.insightsDb.user.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: userIds,
|
||||
},
|
||||
},
|
||||
select: userSelect,
|
||||
});
|
||||
|
||||
const userHashMap = buildHashMapForUsers(usersFromTeam);
|
||||
|
||||
const result = bookingsFromTeam.map((booking) => {
|
||||
return {
|
||||
userId: booking.userId,
|
||||
// We know with 100% certainty that userHashMap.get(...) will retrieve a user
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
user: userHashMap.get(booking.userId)!,
|
||||
emailMd5: md5(user?.email),
|
||||
count: booking._count.id,
|
||||
};
|
||||
});
|
||||
|
||||
return result;
|
||||
}),
|
||||
membersWithMostBookings: userBelongsToTeamProcedure
|
||||
.input(rawDataInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
|
||||
Reference in new issue
Block a user