mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
feat(views): show Total in daily token/cost chart tooltips (MUL-2282) (#2704)
* feat(views): show Total in daily token/cost chart tooltips (MUL-2282) Add a Total row at the bottom of the daily-tokens-chart and daily-cost-chart tooltips so users can see the precise stack sum on hover, in addition to the per-stack breakdown. Implemented by extending shared ChartTooltipContent with an optional `footer` prop (ReactNode | (payload) => ReactNode) that renders below the items with a top divider; backwards-compatible (no behavior change when footer is omitted). Co-authored-by: multica-agent <github@multica.ai> * fix(views): i18n Total label in chart tooltips (MUL-2282) Lint rule i18next/no-literal-string flagged the hardcoded "Total" string in daily-cost-chart and daily-tokens-chart tooltips. Move it to runtimes.charts.tooltip_total and read via useT. Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -127,6 +127,7 @@ function ChartTooltipContent({
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
footer,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
@@ -137,6 +138,16 @@ function ChartTooltipContent({
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
footer?:
|
||||
| React.ReactNode
|
||||
| ((
|
||||
payload: NonNullable<
|
||||
RechartsPrimitive.DefaultTooltipContentProps<
|
||||
TooltipValueType,
|
||||
TooltipNameType
|
||||
>["payload"]
|
||||
>,
|
||||
) => React.ReactNode)
|
||||
} & Omit<
|
||||
RechartsPrimitive.DefaultTooltipContentProps<
|
||||
TooltipValueType,
|
||||
@@ -266,6 +277,11 @@ function ChartTooltipContent({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{footer != null && (
|
||||
<div className="mt-0.5 border-t border-border/50 pt-1.5">
|
||||
{typeof footer === "function" ? footer(payload) : footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,7 +154,8 @@
|
||||
},
|
||||
"charts": {
|
||||
"heatmap_less": "Less",
|
||||
"heatmap_more": "More"
|
||||
"heatmap_more": "More",
|
||||
"tooltip_total": "Total"
|
||||
},
|
||||
"list": {
|
||||
"col_runtime": "Runtime",
|
||||
|
||||
@@ -149,7 +149,8 @@
|
||||
},
|
||||
"charts": {
|
||||
"heatmap_less": "少",
|
||||
"heatmap_more": "多"
|
||||
"heatmap_more": "多",
|
||||
"tooltip_total": "总计"
|
||||
},
|
||||
"list": {
|
||||
"col_runtime": "运行时",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type ChartConfig,
|
||||
} from "@multica/ui/components/ui/chart";
|
||||
import type { DailyCostStackData } from "../../utils";
|
||||
import { useT } from "../../../i18n";
|
||||
|
||||
// Three-segment stack (input / output / cache write) — keeps the user's
|
||||
// attention on what's actually driving spend. Cache reads are excluded
|
||||
@@ -29,6 +30,7 @@ export const costStackConfig = {
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export function DailyCostChart({ data }: { data: DailyCostStackData[] }) {
|
||||
const { t } = useT("runtimes");
|
||||
// No internal empty-state — the parent decides what to show in place of
|
||||
// the chart (often a diagnostic explaining *why* there's no cost). Letting
|
||||
// recharts render an empty axis would be both ugly and uninformative.
|
||||
@@ -58,6 +60,22 @@ export function DailyCostChart({ data }: { data: DailyCostStackData[] }) {
|
||||
? `$${value.toFixed(2)} ${name}`
|
||||
: `${value} ${name}`
|
||||
}
|
||||
footer={(payload) => {
|
||||
const total = payload.reduce(
|
||||
(sum, item) =>
|
||||
sum +
|
||||
(typeof item.value === "number" ? item.value : 0),
|
||||
0,
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-2 font-medium">
|
||||
<span>{t(($) => $.charts.tooltip_total)}</span>
|
||||
<span className="font-mono tabular-nums">
|
||||
${total.toFixed(2)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type ChartConfig,
|
||||
} from "@multica/ui/components/ui/chart";
|
||||
import { formatTokens, type DailyTokenData } from "../../utils";
|
||||
import { useT } from "../../../i18n";
|
||||
|
||||
// Four-segment stack — input / output / cache read / cache write. Unlike the
|
||||
// cost chart, cache reads ARE visible here: a typical day on Claude shows
|
||||
@@ -32,6 +33,7 @@ export const tokenStackConfig = {
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export function DailyTokensChart({ data }: { data: DailyTokenData[] }) {
|
||||
const { t } = useT("runtimes");
|
||||
// No internal empty-state — same convention as DailyCostChart: the parent
|
||||
// decides what to render when there's nothing to show.
|
||||
return (
|
||||
@@ -60,6 +62,22 @@ export function DailyTokensChart({ data }: { data: DailyTokenData[] }) {
|
||||
? `${formatTokens(value)} ${name}`
|
||||
: `${value} ${name}`
|
||||
}
|
||||
footer={(payload) => {
|
||||
const total = payload.reduce(
|
||||
(sum, item) =>
|
||||
sum +
|
||||
(typeof item.value === "number" ? item.value : 0),
|
||||
0,
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-2 font-medium">
|
||||
<span>{t(($) => $.charts.tooltip_total)}</span>
|
||||
<span className="font-mono tabular-nums">
|
||||
{total.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user