All files / src/components/crm LeadsStatStrip.tsx

100% Statements 19/19
87.5% Branches 7/8
100% Functions 12/12
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121                                                              2x                                         68x 268x   68x   268x 67x     68x 245x     68x       245x 245x     126x       245x     61x                             59x   59x               59x     295x                        
import type { KanbanData } from "../../lib/api/pro/crm";
import { abbreviatePaise } from "../../lib/currency-utils";
import { overdueLeads } from "./lead-overdue";
import { isTerminalStage } from "./lead-status";
 
/**
 * The figures, printed before the board.
 *
 * **Figures and rows lead** — so the Leads page opens on five numbers, not on
 * a wall of cards. `.statstrip` in `.context/mockups/_base.css`: one ruled
 * strip, each figure a `.label` and a tabular `.num` on one baseline, ₹
 * glyphed, no cards and no boxes.
 *
 * EVERY FIGURE IS DERIVED FROM THE KANBAN PAYLOAD THE PAGE ALREADY HAS.
 * No second request, and — more importantly — no second definition. "Overdue"
 * is `overdueLeads()` from `lead-overdue.ts`, the same function the band below
 * and `/` (Today) call, so the figure and the rows under it can never
 * disagree. "Open" is "not in a terminal stage", which is the same
 * `isTerminalStage()` the columns use.
 *
 * ONE SUBSTITUTION FROM THE MOCKUP, STATED
 * The mockup's fifth figure is "Won this month". The kanban payload carries no
 * stage-entry date, only `dateUpdated`, and a won lead whose phone number was
 * corrected last Tuesday would be counted as won last Tuesday. Rather than
 * print a number that is quietly wrong, the figure is "Won" — every won lead's
 * value, which IS exact. `/insights/pipeline` already owns period-scoped
 * figures (`useCrmAnalytics`, `wonValueInPeriod`) and is where a month-scoped
 * version belongs.
 */
 
/** Seven days, the window "added this week" means. */
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
 
export interface LeadsFigures {
	open: number;
	overdue: number;
	addedThisWeek: number;
	/** Paise. Quote value across every open lead. */
	pipelinePaise: number;
	/** Paise. Order value (falling back to quote) across every won lead. */
	wonPaise: number;
}
 
/**
 * The five figures. Exported for its own test — the arithmetic is the whole
 * substance of this component and asserting it through the DOM would only
 * prove that `abbreviatePaise` was called.
 */
export function deriveLeadsFigures(
	data: KanbanData,
	now: number = Date.now(),
): LeadsFigures {
	const terminalStageIds = new Set(
		data.stages.filter((s) => isTerminalStage(s.stageType)).map((s) => s.id),
	);
	const wonStageIds = new Set(
		data.stages
			.filter((s) => s.stageType === "system_terminal_won")
			.map((s) => s.id),
	);
 
	const openLeads = data.leads.filter(
		(lead) => !terminalStageIds.has(lead.currentStageId),
	);
 
	return {
		open: openLeads.length,
		overdue: overdueLeads(data.leads, now).length,
		addedThisWeek: data.leads.filter((lead) => {
			const created = new Date(lead.dateCreated).getTime();
			return !Number.isNaN(created) && now - created <= WEEK_MS;
		}).length,
		pipelinePaise: openLeads.reduce(
			(sum, lead) => sum + (lead.quoteValuePaise ?? 0),
			0,
		),
		wonPaise: data.leads
			.filter((lead) => wonStageIds.has(lead.currentStageId))
			.reduce(
				(sum, lead) =>
					sum + (lead.orderValuePaise ?? lead.quoteValuePaise ?? 0),
				0,
			),
	};
}
 
// ─── Component ───────────────────────────────────────────────────────────────
 
type LeadsStatStripProps = {
	data: KanbanData;
	/** One reading of the clock for the whole page. */
	now: number;
};
 
export function LeadsStatStrip({ data, now }: LeadsStatStripProps) {
	const figures = deriveLeadsFigures(data, now);
 
	const stats: { label: string; value: string }[] = [
		{ label: "Open", value: String(figures.open) },
		{ label: "Overdue", value: String(figures.overdue) },
		{ label: "Added this week", value: String(figures.addedThisWeek) },
		{ label: "Pipeline", value: `₹ ${abbreviatePaise(figures.pipelinePaise)}` },
		{ label: "Won", value: `₹ ${abbreviatePaise(figures.wonPaise)}` },
	];
 
	return (
		<dl className="flex flex-wrap items-baseline gap-x-6 gap-y-1.5 border-y border-border-default py-2">
			{stats.map((stat) => (
				<div key={stat.label} className="inline-flex items-baseline gap-1.5">
					<dt className="text-xs font-semibold uppercase tracking-wider text-foreground-muted">
						{stat.label}
					</dt>
					<dd className="text-base font-semibold tabular-nums text-foreground-default">
						{stat.value}
					</dd>
				</div>
			))}
		</dl>
	);
}