import { cn } from "@/src/utils/tailwind"; import { type IntervalConfig, type TimeRange, } from "@/src/utils/date-range-utils"; import { formatChartTooltipTimestamp } from "./chart-formatters"; import { useChart } from "@/src/components/ui/chart"; /** * Props for the ScoreChartTooltip component. * Compatible with Recharts tooltip API. */ export interface ScoreChartTooltipProps { active?: boolean; payload?: Array<{ name?: string; value?: number | string; color?: string; dataKey?: string; payload?: any; }>; label?: string | number; interval?: IntervalConfig; timeRange?: TimeRange; valueFormatter?: (value: number) => string; labelFormatter?: (label: string | number) => string; } /** * Custom tooltip component for Score Analytics charts. * Provides consistent styling and formatting across all chart types. * * Features: * - Sorted values (descending by magnitude) * - Custom value formatting (e.g., compact numbers, percentages) * - Dynamic timestamp formatting based on interval * - Color indicators for each data series * - Consistent design matching dashboard tooltips * * Usage: * ```tsx * * } * /> * ``` */ export function ScoreChartTooltip({ active, payload, label, interval, timeRange, valueFormatter = (value: number) => value.toString(), labelFormatter, }: ScoreChartTooltipProps) { const { config } = useChart(); if (!active || !payload || payload.length === 0) { return null; } // Filter out duplicates const uniquePayload = Array.from( new Map(payload.map((item) => [item.name ?? item.dataKey, item])).values(), ); // Sort payload by config key order for stable tooltip across columns // This maintains consistent ordering (e.g., alphabetical + unmatched last) // REVERSED to mirror the visual stack order (bottom-to-top becomes top-to-bottom in tooltip) // Falls back to value-based sorting if config keys not available const configKeys = Object.keys(config); const sortedPayload = uniquePayload.sort((a, b) => { const keyA = a.name ?? a.dataKey ?? ""; const keyB = b.name ?? b.dataKey ?? ""; // If both keys exist in config, sort by config order (reversed) const indexA = configKeys.indexOf(keyA); const indexB = configKeys.indexOf(keyB); if (indexA !== -1 && indexB !== -1) { return indexB - indexA; // Reversed: mirror visual stack order } // Fallback to value-based sorting (descending) for non-config keys return (Number(b.value) ?? 0) - (Number(a.value) ?? 0); }); // Format the label (timestamp) let formattedLabel: string; if (labelFormatter) { // Use custom label formatter if provided formattedLabel = labelFormatter(label ?? ""); } else if (typeof label === "string") { // Label is already formatted (from chart data transformation) formattedLabel = label; } else if (interval && timeRange && label) { // Format timestamp using interval-aware formatting (for numeric timestamps) const timestamp = typeof label === "number" ? new Date(label) : new Date(label); formattedLabel = formatChartTooltipTimestamp( timestamp, interval, timeRange, ); } else { // Fallback to string representation formattedLabel = String(label ?? ""); } return (
{/* Header with timestamp/label */}

{formattedLabel}

{/* Data series with values */}
{sortedPayload.map((entry, index) => { // Get series label from config using the Bar's dataKey const seriesKey = entry.name || entry.dataKey || ""; const seriesLabel = config[seriesKey]?.label || seriesKey; return (
{/* Color indicator */}
{/* Series label from config */} {seriesLabel?.toString() ?? ""} {/* Formatted value */} {valueFormatter(Number(entry.value ?? 0))}
); })}
); }