Skip to content

Commit 6b04951

Browse files
committed
feat: format SKU labels in hero card breakdowns
Use formatDisplayValue for pretty names (actions_linux → Actions Linux) and show product-specific Octicons. Extracted getSkuIcon to shared formatters module.
1 parent 7f007af commit 6b04951

File tree

4 files changed

+36
-19
lines changed

4 files changed

+36
-19
lines changed

src/App.module.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@
141141
display: flex;
142142
justify-content: space-between;
143143
gap: var(--base-size-8, 8px);
144+
align-items: center;
145+
}
146+
147+
.heroCardBreakdown span svg {
148+
flex-shrink: 0;
149+
margin-right: 3px;
150+
vertical-align: text-bottom;
144151
}
145152

146153
.reportTabs,

src/components/HeroCardsGrid.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HeroCard } from './HeroCard';
33
import type { ReportSchema } from '../lib/report-schema';
44
import type { ReportSummary, AnyReportRow, TokenUsageRow } from '../lib/types';
55
import { REPORT_TYPES } from '../lib/types';
6-
import { formatCurrency, formatCompact } from '../lib/formatters';
6+
import { formatCurrency, formatCompact, formatDisplayValue, getSkuIcon } from '../lib/formatters';
77
import { topN } from '../lib/aggregation';
88
import styles from '../App.module.css';
99

@@ -67,12 +67,17 @@ export function HeroCardsGrid({ schema, summary, visibleRows, reportType }: Hero
6767
card.breakdownMetricField as keyof AnyReportRow & string,
6868
3,
6969
);
70-
return top3.map((m) => (
71-
<span key={m.key}>
72-
<span>{m.key}</span>
73-
<span>{card.format === 'currency' ? formatCurrency(m.value) : formatCompact(m.value)}</span>
74-
</span>
75-
));
70+
return top3.map((m) => {
71+
const label = formatDisplayValue(m.key, card.breakdownGroupField!);
72+
const isSkuBreakdown = card.breakdownGroupField === 'sku';
73+
const SkuIcon = isSkuBreakdown ? getSkuIcon(m.key) : null;
74+
return (
75+
<span key={m.key}>
76+
<span>{SkuIcon && <SkuIcon size={14} />}{label}</span>
77+
<span>{card.format === 'currency' ? formatCurrency(m.value) : formatCompact(m.value)}</span>
78+
</span>
79+
);
80+
});
7681
})()}
7782

7883
{/* Static breakdown entries */}

src/components/ReportTable.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import {
1414
} from '@tanstack/react-table';
1515
import { Avatar, Button, CounterLabel, SelectPanel } from '@primer/react';
1616
import { Table as PrimerTable } from '@primer/react/experimental';
17-
import { AppsIcon, ColumnsIcon, CopilotIcon, CreditCardIcon, DatabaseIcon, PackageIcon, RepoIcon, TagIcon, WorkflowIcon } from '@primer/octicons-react';
17+
import { AppsIcon, ColumnsIcon, CreditCardIcon, RepoIcon, TagIcon, WorkflowIcon } from '@primer/octicons-react';
1818
import { OnboardingBubble, ONBOARDING_STEPS } from './onboarding';
1919
import { type ActionListItemInput } from '@primer/react/deprecated';
2020
import { useReport } from '../context/useReport';
2121
import { groupBy, sumBy } from '../lib/aggregation';
22-
import { formatCurrency, formatCompact, humanizeColumn, formatDisplayValue, getAvatarUrl, formatDatetime } from '../lib/formatters';
22+
import { formatCurrency, formatCompact, humanizeColumn, formatDisplayValue, getAvatarUrl, formatDatetime, getSkuIcon } from '../lib/formatters';
2323
import type { AnyReportRow, TokenUsageRow, UsageReportRow } from '../lib/types';
2424
import { REPORT_TYPES } from '../lib/types';
2525
import { getModelIconUrl } from '../lib/chart-theme';
@@ -35,17 +35,7 @@ const COLUMN_ICONS: Record<string, React.ComponentType<{ size?: number; classNam
3535
workflowPath: WorkflowIcon,
3636
};
3737

38-
type OcticonComponent = React.ComponentType<{ size?: number; className?: string }>;
3938

40-
/** Map a raw SKU key to the product-relevant Octicon */
41-
function getSkuIcon(rawValue: string): OcticonComponent {
42-
const v = rawValue.toLowerCase();
43-
if (v.startsWith('actions')) return WorkflowIcon;
44-
if (v.startsWith('copilot') || v.startsWith('coding_agent') || v.startsWith('spark')) return CopilotIcon;
45-
if (v.startsWith('packages')) return PackageIcon;
46-
if (v.startsWith('git_lfs')) return DatabaseIcon;
47-
return TagIcon;
48-
}
4939

5040
// Extend TanStack's ColumnMeta to support our align property
5141
declare module '@tanstack/react-table' {

src/lib/formatters.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
import type { ComponentType } from 'react';
2+
import { CopilotIcon, DatabaseIcon, PackageIcon, TagIcon, WorkflowIcon } from '@primer/octicons-react';
3+
4+
type OcticonComponent = ComponentType<{ size?: number; className?: string }>;
5+
6+
/** Map a raw SKU key to the product-relevant Octicon */
7+
export function getSkuIcon(rawValue: string): OcticonComponent {
8+
const v = rawValue.toLowerCase();
9+
if (v.startsWith('actions')) return WorkflowIcon;
10+
if (v.startsWith('copilot') || v.startsWith('coding_agent') || v.startsWith('spark')) return CopilotIcon;
11+
if (v.startsWith('packages')) return PackageIcon;
12+
if (v.startsWith('git_lfs')) return DatabaseIcon;
13+
return TagIcon;
14+
}
15+
116
/** Format a number as USD currency */
217
export function formatCurrency(value: number): string {
318
return new Intl.NumberFormat('en-US', {

0 commit comments

Comments
 (0)