/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ import { IconTile } from '@/components/common/IconTile'; import { useMemo } from 'react'; import { ArrowUpRight, Info } from 'lucide-react'; import { Link } from 'react-router-dom'; import { cn } from '@/lib/utils'; import { hrefFor, useDashLink } from '../links'; import { LineChart, Line } from 'recharts'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import type { Card as CardSchema } from '../types/schema'; import type { Metric } from '../types/metrics'; import { cardValue, formatValue, sparklineData, computeDelta } from '../helpers'; import { useLiveMetricsStore } from '../stores/liveMetricsStore'; import { getChartColor } from '@/components/ui/chart'; interface StatCardProps { card: CardSchema; historySamples: Metric[]; historyWindow: { from: Date; to: Date }; /** INBUXA: a value counted from the server's objects, used when live metrics aren't available. */ fallback?: number; } export function StatCard({ card, historySamples, historyWindow, fallback }: StatCardProps) { const liveSnapshot = useLiveMetricsStore((s) => s.snapshot); const liveStatus = useLiveMetricsStore((s) => s.status); const link = useDashLink(card.metrics); const value = useMemo(() => { if (card.source === 'live' && fallback !== undefined && liveStatus !== 'open') return fallback; if (card.source === 'live') { const liveSamples = card.metrics.map((id) => liveSnapshot.get(id)).filter((m): m is Metric => m !== undefined); return cardValue(card, liveSamples); } return cardValue(card, historySamples); }, [card, liveSnapshot, historySamples, fallback, liveStatus]); // INBUXA: a live number the server can't report yet reads as unknown, not as zero. const unknown = card.source === 'live' && liveStatus !== 'open' && fallback === undefined; const formattedValue = unknown ? '—' : formatValue(value, card.format); const { from, to } = historyWindow; const sparkline = useMemo(() => { if (card.source !== 'history' || !card.sparkline) return null; return sparklineData(card, historySamples, from, to).map((v, i) => ({ v, i, })); }, [card, historySamples, from, to]); const delta = useMemo(() => { if (card.source !== 'history' || !card.delta) return null; return computeDelta(card, historySamples, from, to); }, [card, historySamples, from, to]); const body = (
{card.title} {link && ( )} {card.description && (

{card.description}

)}
{formattedValue}
{(delta || sparkline) && (
{delta && ( {delta.direction === 'up' ? `\u2191 ${Math.abs(delta.pct)}%` : delta.direction === 'down' ? `\u2193 ${Math.abs(delta.pct)}%` : '\u2013'} )} {sparkline && ( )}
)}
); return link ? ( {body} ) : ( body ); }