diff --git a/apps/web/app/(ee)/api/partners/applications/analytics/route.ts b/apps/web/app/(ee)/api/partners/applications/analytics/route.ts index 6473c6f05fe..29c91a911af 100644 --- a/apps/web/app/(ee)/api/partners/applications/analytics/route.ts +++ b/apps/web/app/(ee)/api/partners/applications/analytics/route.ts @@ -7,7 +7,10 @@ import { import { withWorkspace } from "@/lib/auth"; import { sqlGranularityMap } from "@/lib/planetscale/granularity"; import { prisma } from "@/lib/prisma"; -import { ApplicationEventAnalyticsQuery } from "@/lib/types"; +import { + ApplicationEventAnalyticsQuery, + ApplicationEventStages, +} from "@/lib/types"; import { TZDate, tz } from "@date-fns/tz"; import { parseFilterValue } from "@dub/utils"; import { Prisma } from "@prisma/client"; @@ -34,6 +37,16 @@ const aggregations = { }, } as const; +const STAGE_SORT_COLUMN: Record< + ApplicationEventStages, + "visitedAt" | "startedAt" | "submittedAt" | "approvedAt" +> = { + visited: "visitedAt", + started: "startedAt", + submitted: "submittedAt", + approved: "approvedAt", +}; + // GET /api/partners/applications/analytics export const GET = withWorkspace(async ({ workspace, searchParams }) => { const programId = getDefaultProgramIdOrThrow(workspace); @@ -42,6 +55,7 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => { applicationEventAnalyticsQuerySchema.parse(searchParams); const { + event, groupBy, partnerId, country, @@ -139,6 +153,7 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => { return byPartnerId({ where, programId, + event, }); } @@ -157,14 +172,29 @@ export const GET = withWorkspace(async ({ workspace, searchParams }) => { async function byPartnerId({ where, programId, + event, }: { where: Prisma.ProgramApplicationEventWhereInput; programId: string; + event?: ApplicationEventStages; }) { + const sortColumn = STAGE_SORT_COLUMN[event ?? "visited"]; + const events = await prisma.programApplicationEvent.groupBy({ by: ["referredByPartnerId"], - where, + where: { + ...where, + referredByPartnerId: where.referredByPartnerId ?? { + not: null, + }, + }, ...aggregations, + orderBy: { + _count: { + [sortColumn]: "desc", + }, + }, + take: 100, }); const partnerIds = events @@ -194,24 +224,23 @@ async function byPartnerId({ }) : []; - const eventCountByPartnerId = new Map( - events.map(({ referredByPartnerId, _count }) => [ - referredByPartnerId, - _count, - ]), + const partnersById = new Map( + partners.map((partner) => [partner.id, partner]), ); - const results = partners - .map((partner) => { - const partnerEvents = eventCountByPartnerId.get(partner.id); + const results = events + .map(({ referredByPartnerId, _count }) => { + const partner = referredByPartnerId + ? partnersById.get(referredByPartnerId) + : undefined; - if (!partnerEvents) { + if (!partner) { return null; } return { partner, - ...formatCounts(partnerEvents), + ...formatCounts(_count), }; }) .filter((r): r is NonNullable => r !== null); diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/applications-partners-table.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/applications-partners-table.tsx index 47679dcf6cc..e579ba1ecf1 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/applications-partners-table.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/applications-partners-table.tsx @@ -15,12 +15,14 @@ import { AnimatePresence, motion } from "motion/react"; import { useCallback, useMemo, useState } from "react"; import { PartnerAnalyticsFilterCell } from "../partner-analytics-filter-cell"; import { useApplicationsAnalytics } from "./use-applications-analytics"; +import { useApplicationsAnalyticsQuery } from "./use-applications-analytics-query"; const PAGE_SIZE = 10; export function ApplicationsPartnersTable() { const { pagination, setPagination } = usePagination(PAGE_SIZE); const { queryParams, searchParams } = useRouterStuff(); + const { stage } = useApplicationsAnalyticsQuery(); const [stagedPartnerIds, setStagedPartnerIds] = useState( null, @@ -74,10 +76,20 @@ export function ApplicationsPartnersTable() { const { data, error, isLoading } = useApplicationsAnalytics({ groupBy: "partnerId", + event: stage, }); + const paginatedData = useMemo( + () => + data?.slice( + (pagination.pageIndex - 1) * pagination.pageSize, + pagination.pageIndex * pagination.pageSize, + ), + [data, pagination], + ); + const { table, ...tableProps } = useTable({ - data: data ?? [], + data: paginatedData ?? [], columns: [ { id: "partner", @@ -121,6 +133,7 @@ export function ApplicationsPartnersTable() { ], pagination, onPaginationChange: setPagination, + sortBy: stage, thClassName: "border-l-0", tdClassName: "border-l-0", rowCount: data?.length ?? 0, diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics-filters.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics-filters.tsx index 9308b17f39f..a2fda294425 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics-filters.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics-filters.tsx @@ -36,6 +36,7 @@ export function useApplicationAnalyticsFilters() { const { data: partners } = useApplicationsAnalytics({ groupBy: "partnerId", + event: stage, exclude: ["partnerId"], enabled: tab === "applications", }); diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics.ts b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics.ts index 9c2c16ca898..ef255bb8da9 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics.ts +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/analytics/applications/use-applications-analytics.ts @@ -39,6 +39,7 @@ export function useApplicationsAnalytics< exclude: [ "pageTab", "applicationEvent", + "event", "view", "sortBy", "sortOrder", @@ -84,6 +85,7 @@ export function useApplicationsAnalyticsCount( exclude: [ "pageTab", "applicationEvent", + "event", "view", "sortBy", "sortOrder", diff --git a/apps/web/lib/application-events/schema.ts b/apps/web/lib/application-events/schema.ts index c7924badb85..07a618227d7 100644 --- a/apps/web/lib/application-events/schema.ts +++ b/apps/web/lib/application-events/schema.ts @@ -71,6 +71,7 @@ export const applicationEventSchema = z.object({ // Application analytics export const applicationEventAnalyticsQuerySchema = sharedFilterSchema.extend({ + event: z.enum(APPLICATION_EVENT_STAGES).optional(), groupBy: z .enum(["count", "timeseries", "partnerId", "referralSource", "country"]) .default("count"),