feat(sales-stats): дельты период-к-периоду на карточках сводки

StatCard получил опциональную дельту (стрелка + %), а страница статистики тянет
сводку ещё и за предыдущий равный период и считает рост/падение для метрик
периода: выручка, новые триалы, продления, доход с допов, ручные пополнения.
Для «всего времени» сравнение не показывается (не с чем сравнивать).
This commit is contained in:
c0mrade
2026-06-02 15:15:34 +03:00
parent a75f42d056
commit a685e542aa
2 changed files with 80 additions and 2 deletions

View File

@@ -1,12 +1,22 @@
import { type ReactNode } from 'react';
import { TREND_STYLES } from './constants';
const DEFAULT_VALUE_CLASS = 'text-dark-100';
export interface StatCardDelta {
/** Signed percent change vs the comparison period. */
percent: number;
trend: 'up' | 'down' | 'stable';
}
interface StatCardProps {
label: string;
value: string | number;
icon?: ReactNode;
valueClassName?: string;
/** Optional period-over-period change shown next to the value. */
delta?: StatCardDelta | null;
}
export function StatCard({
@@ -14,15 +24,25 @@ export function StatCard({
value,
icon,
valueClassName = DEFAULT_VALUE_CLASS,
delta,
}: StatCardProps) {
const trendStyle = delta ? (TREND_STYLES[delta.trend] ?? TREND_STYLES.stable) : null;
return (
<div className="rounded-xl bg-dark-800/30 p-3">
<div className="flex items-center gap-1.5 text-sm text-dark-500">
{icon}
<span>{label}</span>
</div>
<div className={`mt-1 truncate text-base font-semibold sm:text-lg ${valueClassName}`}>
{value}
<div className="mt-1 flex items-baseline gap-1.5">
<span className={`truncate text-base font-semibold sm:text-lg ${valueClassName}`}>
{value}
</span>
{trendStyle && (
<span className={`shrink-0 text-xs font-medium ${trendStyle.className}`}>
{trendStyle.arrow} {Math.abs(delta!.percent)}%
</span>
)}
</div>
</div>
);