diff --git a/packages/features/insights/components/BookingKPICards.tsx b/packages/features/insights/components/BookingKPICards.tsx index 81cf50a16b..1fb966e33d 100644 --- a/packages/features/insights/components/BookingKPICards.tsx +++ b/packages/features/insights/components/BookingKPICards.tsx @@ -40,7 +40,15 @@ export const BookingKPICards = () => { const categories: { title: string; - index: "created" | "completed" | "rescheduled" | "cancelled" | "no_show" | "rating" | "csat"; + index: + | "created" + | "completed" + | "rescheduled" + | "cancelled" + | "no_show" + | "rating" + | "csat" + | "no_show_guest"; }[] = [ { title: t("events_created"), @@ -66,6 +74,10 @@ export const BookingKPICards = () => { title: t("event_no_show"), index: "no_show", }, + { + title: t("event_no_show_guest"), + index: "no_show_guest", + }, { title: t("csat_score"), index: "csat", diff --git a/packages/features/insights/components/BookingStatusLineChart.tsx b/packages/features/insights/components/BookingStatusLineChart.tsx index 688a306c1b..f2e6e386a6 100644 --- a/packages/features/insights/components/BookingStatusLineChart.tsx +++ b/packages/features/insights/components/BookingStatusLineChart.tsx @@ -60,9 +60,9 @@ export const BookingStatusLineChart = () => { diff --git a/packages/features/insights/server/events.ts b/packages/features/insights/server/events.ts index df22ed7d77..4968ac373b 100644 --- a/packages/features/insights/server/events.ts +++ b/packages/features/insights/server/events.ts @@ -12,6 +12,7 @@ type StatusAggregate = { rescheduled: number; cancelled: number; noShowHost: number; + noShowGuests: number; _all: number; uncompleted: number; }; @@ -73,20 +74,30 @@ class EventsInsights { const whereClause = buildSqlCondition(whereConditional); const data = await prisma.$queryRaw< - { periodStart: Date; bookingsCount: number; timeStatus: string; noShowHost: boolean }[] + { + periodStart: Date; + bookingsCount: number; + timeStatus: string; + noShowHost: boolean; + noShowGuests: number; + }[] >` SELECT "periodStart", CAST(COUNT(*) AS INTEGER) AS "bookingsCount", + CAST(COUNT(CASE WHEN "isNoShowGuest" = true THEN 1 END) AS INTEGER) AS "noShowGuests", "timeStatus", "noShowHost" FROM ( SELECT DATE_TRUNC(${timeView}, "createdAt") AS "periodStart", + "a"."noShow" AS "isNoShowGuest", "timeStatus", "noShowHost" FROM "BookingTimeStatus" + JOIN + "Attendee" "a" ON "a"."bookingId" = "BookingTimeStatus"."id" WHERE "createdAt" BETWEEN ${formattedStartDate}::timestamp AND ${formattedEndDate}::timestamp AND ${Prisma.raw(whereClause)} @@ -100,7 +111,7 @@ class EventsInsights { `; const aggregate: AggregateResult = {}; - data.forEach(({ periodStart, bookingsCount, timeStatus, noShowHost }) => { + data.forEach(({ periodStart, bookingsCount, timeStatus, noShowHost, noShowGuests }) => { const formattedDate = dayjs(periodStart).format("MMM D, YYYY"); if (dayjs(periodStart).isAfter(endDate)) { @@ -116,6 +127,7 @@ class EventsInsights { noShowHost: 0, _all: 0, uncompleted: 0, + noShowGuests: 0, }; } @@ -130,6 +142,9 @@ class EventsInsights { if (noShowHost) { aggregate[formattedDate]["noShowHost"] += Number(bookingsCount); } + + // Track no-show guests explicitly + aggregate[formattedDate]["noShowGuests"] += noShowGuests; }); // Generate a complete list of expected date labels based on the timeline @@ -160,6 +175,7 @@ class EventsInsights { rescheduled: 0, cancelled: 0, noShowHost: 0, + noShowGuests: 0, _all: 0, uncompleted: 0, }; @@ -169,6 +185,27 @@ class EventsInsights { return aggregate; }; + static getTotalNoShowGuests = async (where: Prisma.BookingTimeStatusWhereInput) => { + const bookings = await prisma.bookingTimeStatus.findMany({ + where, + select: { + id: true, + }, + }); + + const { _count: totalNoShowGuests } = await prisma.attendee.aggregate({ + where: { + bookingId: { + in: bookings.map((booking) => booking.id), + }, + noShow: true, + }, + _count: true, + }); + + return totalNoShowGuests; + }; + static countGroupedByStatus = async (where: Prisma.BookingTimeStatusWhereInput) => { const data = await prisma.bookingTimeStatus.groupBy({ where, diff --git a/packages/features/insights/server/trpc-router.ts b/packages/features/insights/server/trpc-router.ts index 206d97d5f2..d9f0a36602 100644 --- a/packages/features/insights/server/trpc-router.ts +++ b/packages/features/insights/server/trpc-router.ts @@ -236,6 +236,10 @@ const emptyResponseEventsByStatus = { count: 0, deltaPrevious: 0, }, + no_show_guest: { + count: 0, + deltaPrevious: 0, + }, csat: { count: 0, deltaPrevious: 0, @@ -304,16 +308,20 @@ export const insightsRouter = router({ countGroupedByStatus, totalRatingsAggregate, totalCSAT, + totalNoShowGuests, lastPeriodCountGroupedByStatus, lastPeriodTotalRatingsAggregate, lastPeriodTotalCSAT, + lastPeriodTotalNoShowGuests, ] = await Promise.all([ EventsInsights.countGroupedByStatus(baseWhereCondition), EventsInsights.getAverageRating(baseWhereCondition), EventsInsights.getTotalCSAT(baseWhereCondition), + EventsInsights.getTotalNoShowGuests(baseWhereCondition), EventsInsights.countGroupedByStatus(lastPeriodBaseCondition), EventsInsights.getAverageRating(lastPeriodBaseCondition), EventsInsights.getTotalCSAT(lastPeriodBaseCondition), + EventsInsights.getTotalNoShowGuests(lastPeriodBaseCondition), ]); const baseBookingsCount = countGroupedByStatus["_all"]; @@ -360,6 +368,10 @@ export const insightsRouter = router({ count: totalNoShow, deltaPrevious: EventsInsights.getPercentage(totalNoShow, lastPeriodTotalNoShow), }, + no_show_guest: { + count: totalNoShowGuests, + deltaPrevious: EventsInsights.getPercentage(totalNoShowGuests, lastPeriodTotalNoShowGuests), + }, rating: { count: averageRating, deltaPrevious: EventsInsights.getPercentage(averageRating, lastPeriodAverageRating), @@ -379,6 +391,7 @@ export const insightsRouter = router({ result.rescheduled.count === 0 && result.cancelled.count === 0 && result.no_show.count === 0 && + result.no_show_guest.count === 0 && result.rating.count === 0 ) { return emptyResponseEventsByStatus; @@ -482,6 +495,7 @@ export const insightsRouter = router({ Rescheduled: 0, Cancelled: 0, "No-Show (Host)": 0, + "No-Show (Guest)": 0, }; const countsForDateRange = countsByStatus[formattedDate]; @@ -492,6 +506,7 @@ export const insightsRouter = router({ EventData["Rescheduled"] = countsForDateRange["rescheduled"] || 0; EventData["Cancelled"] = countsForDateRange["cancelled"] || 0; EventData["No-Show (Host)"] = countsForDateRange["noShowHost"] || 0; + EventData["No-Show (Guest)"] = countsForDateRange["noShowGuests"] || 0; } return EventData; });