use-home-overview.ts2.9 KBView on GitHub 'use client';
import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
/**
* The ONE query behind every metric tile in the home rail.
*
* `statistics.getOverview` already returns response time, follow-up time, the pipeline
* breakdown, the activity timeline and the event totals in a single 60s-cached call — so five
* metric tiles cost one request, not five. Each tile is a pure projection of this result and
* fetches nothing of its own.
*
* The window is a FIXED rolling 30 days and is not a parameter. Making it one would give each
* tile its own range, and each distinct range is another query — which is the whole reason the
* shared call works. The rail is a glance surface; `/statistics` is where rescoping lives.
*/
const WINDOW_DAYS = 30;
export type HomeOverview = {
responseTime?: { medianSeconds: number | null } | null;
followUpTime?: { medianSeconds: number | null } | null;
pipelineStats?: { status: string; count: number; totalDealValue: number }[];
activityTimeline?: {
date: string;
emailInbound: number;
emailOutbound: number;
meetings: number;
slackMessages: number;
calls: number;
notes: number;
total: number;
}[];
totalEvents?: number;
totalConversations?: number;
};
export interface HomeOverviewScope {
aopId?: string | null;
targetUserId?: string;
}
export function useHomeOverview(enabled: boolean, scope: HomeOverviewScope = {}) {
const trpc = useTRPC();
/**
* Day-granular bounds so the key is stable across a session. Deriving them from `new Date()`
* on every render would mint a new query key every millisecond and refetch forever.
*/
const dateRange = useMemo(() => {
const to = new Date();
to.setHours(23, 59, 59, 999);
const from = new Date(to);
from.setDate(from.getDate() - WINDOW_DAYS);
from.setHours(0, 0, 0, 0);
return { dateFrom: from.toISOString(), dateTo: to.toISOString() };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [new Date().toDateString()]);
const { data, isLoading } = useQuery({
// The scope comes from the Pipeline widget's filters and applies to the WHOLE overview —
// it is one call, so the statistics rows follow the same pipeline the table is showing.
// `aopId` matters most: `computePipelineStats` groups over EVERY crm_conversation the user
// owns, so unscoped it counts cold inbound, vendors, recruiting and spam as "deals".
...trpc.statistics.getOverview.queryOptions({
dateRange,
aopId: scope.aopId ?? undefined,
targetUserId: scope.targetUserId,
}),
// Gated on the rail actually holding a metric tile: a default rail (meetings + agents)
// must issue no statistics call at all.
enabled,
staleTime: 5 * 60 * 1000,
});
return { overview: data as HomeOverview | undefined, isLoading: enabled && isLoading };
}