/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ import { useMemo } from 'react'; import * as LucideIcons from 'lucide-react'; import { Info } from 'lucide-react'; 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'; const warnedIcons = new Set(); function LucideIcon({ name, className }: { name: string; className?: string }) { const formatted = name .split('-') .map((s) => s[0].toUpperCase() + s.slice(1)) .join(''); const IconComp = (LucideIcons as Record)[formatted] as LucideIcons.LucideIcon | undefined; if (!IconComp) { if (import.meta.env.DEV && !warnedIcons.has(name)) { warnedIcons.add(name); console.warn(`Unknown icon name: "${name}"`); } return ; } return ; } interface StatCardProps { card: CardSchema; historySamples: Metric[]; historyWindow: { from: Date; to: Date }; } export function StatCard({ card, historySamples, historyWindow }: StatCardProps) { const liveSnapshot = useLiveMetricsStore((s) => s.snapshot); const value = useMemo(() => { 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]); const formattedValue = 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]); return (
{card.title} {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 && ( )}
)}
); }