refactor(telemetry): chart scale labels, calmer vitals and error styling, usage-first tabs

- sparklines get start/end date and max-value footers
- vitals rating shown by value color instead of badges
- error rows use plain counts, subtle resolve button, terse empty state
- telemetry tab order: usage, players, functions, events, errors; usage is default
This commit is contained in:
2026-08-25 22:35:16 -07:00
parent 7ea189aa61
commit fd0489a22f
12 changed files with 222 additions and 238 deletions
@@ -1,9 +1,15 @@
import { Text } from "@mantine/core";
import { Group, Text } from "@mantine/core";
import { niceExtent, pointsToPath } from "./chart-path";
const VIEW_WIDTH = 240;
const PAD = 3;
export interface SparklineScale {
start?: string;
end?: string;
formatValue?: (value: number) => string;
}
interface SparklineProps {
data: number[];
series2?: number[];
@@ -11,6 +17,7 @@ interface SparklineProps {
showArea?: boolean;
label?: string;
color?: string;
scale?: SparklineScale;
}
export const Sparkline = ({
@@ -20,6 +27,7 @@ export const Sparkline = ({
showArea = false,
label,
color = "var(--mantine-color-anchor)",
scale,
}: SparklineProps) => {
if (data.length === 0) {
return (
@@ -38,7 +46,11 @@ export const Sparkline = ({
? `${path} L${VIEW_WIDTH - PAD},${height - PAD} L${PAD},${height - PAD} Z`
: null;
return (
const allValues = series2?.length ? [...data, ...series2] : data;
const max = Math.max(...allValues);
const formatValue = scale?.formatValue ?? ((value: number) => value.toLocaleString());
const svg = (
<svg
viewBox={`0 0 ${VIEW_WIDTH} ${height}`}
preserveAspectRatio="none"
@@ -72,6 +84,25 @@ export const Sparkline = ({
)}
</svg>
);
if (!scale) return svg;
return (
<div>
{svg}
<Group justify="space-between" gap="xs" mt={2}>
<Text size="xs" c="dimmed">
{scale.start}
</Text>
<Text size="xs" c="dimmed" style={{ fontVariantNumeric: "tabular-nums" }}>
↑{formatValue(max)}
</Text>
<Text size="xs" c="dimmed">
{scale.end}
</Text>
</Group>
</div>
);
};
export default Sparkline;
@@ -1,21 +1,32 @@
import { ReactNode } from "react";
import { Box, Group, Stack, Text, UnstyledButton } from "@mantine/core";
import Sparkline from "./sparkline";
import Sparkline, { SparklineScale } from "./sparkline";
interface StatTileProps {
label: ReactNode;
value: ReactNode;
valueColor?: string;
delta?: number;
spark?: number[];
sparkScale?: SparklineScale;
caption?: ReactNode;
onClick?: () => void;
}
export const StatTile = ({ label, value, delta, spark, caption, onClick }: StatTileProps) => {
export const StatTile = ({
label,
value,
valueColor,
delta,
spark,
sparkScale,
caption,
onClick,
}: StatTileProps) => {
const content = (
<Stack gap={2}>
<Group gap="xs" align="baseline" wrap="nowrap">
<Text fz="xl" fw={700} lh={1.2}>
<Text fz="xl" fw={700} lh={1.2} c={valueColor}>
{value}
</Text>
{delta !== undefined && delta !== 0 && (
@@ -29,7 +40,9 @@ export const StatTile = ({ label, value, delta, spark, caption, onClick }: StatT
{label}
</Text>
{caption && <Box>{caption}</Box>}
{spark && spark.length > 0 && <Sparkline data={spark} height={28} />}
{spark && spark.length > 0 && (
<Sparkline data={spark} height={28} scale={sparkScale} />
)}
</Stack>
);