11import type { OutputColumnMetadata } from "@internal/tsql" ;
22import { useMemo } from "react" ;
3- import type { AggregationType , BigNumberConfiguration } from "~/components/metrics/QueryWidget" ;
3+ import type {
4+ BigNumberAggregationType ,
5+ BigNumberConfiguration ,
6+ } from "~/components/metrics/QueryWidget" ;
47import { Spinner } from "../Spinner" ;
58import { Paragraph } from "../Paragraph" ;
69
@@ -12,11 +15,24 @@ interface BigNumberCardProps {
1215}
1316
1417/**
15- * Extracts numeric values from a specific column across all rows
18+ * Extracts numeric values from a specific column across all rows,
19+ * optionally sorting them first.
1620 */
17- function extractColumnValues ( rows : Record < string , unknown > [ ] , column : string ) : number [ ] {
21+ function extractColumnValues (
22+ rows : Record < string , unknown > [ ] ,
23+ column : string ,
24+ sortDirection ?: "asc" | "desc"
25+ ) : number [ ] {
1826 const values : number [ ] = [ ] ;
19- for ( const row of rows ) {
27+ const sortedRows = sortDirection
28+ ? [ ...rows ] . sort ( ( a , b ) => {
29+ const aVal = toNumber ( a [ column ] ) ;
30+ const bVal = toNumber ( b [ column ] ) ;
31+ return sortDirection === "asc" ? aVal - bVal : bVal - aVal ;
32+ } )
33+ : rows ;
34+
35+ for ( const row of sortedRows ) {
2036 const val = row [ column ] ;
2137 if ( typeof val === "number" ) {
2238 values . push ( val ) ;
@@ -30,10 +46,19 @@ function extractColumnValues(rows: Record<string, unknown>[], column: string): n
3046 return values ;
3147}
3248
49+ function toNumber ( value : unknown ) : number {
50+ if ( typeof value === "number" ) return value ;
51+ if ( typeof value === "string" ) {
52+ const parsed = parseFloat ( value ) ;
53+ return isNaN ( parsed ) ? 0 : parsed ;
54+ }
55+ return 0 ;
56+ }
57+
3358/**
3459 * Aggregate an array of numbers using the specified aggregation function
3560 */
36- function aggregateValues ( values : number [ ] , aggregation : AggregationType ) : number {
61+ function aggregateValues ( values : number [ ] , aggregation : BigNumberAggregationType ) : number {
3762 if ( values . length === 0 ) return 0 ;
3863 switch ( aggregation ) {
3964 case "sum" :
@@ -46,49 +71,59 @@ function aggregateValues(values: number[], aggregation: AggregationType): number
4671 return Math . min ( ...values ) ;
4772 case "max" :
4873 return Math . max ( ...values ) ;
74+ case "first" :
75+ return values [ 0 ] ;
76+ case "last" :
77+ return values [ values . length - 1 ] ;
4978 }
5079}
5180
5281/**
53- * Formats a number for display as a big number.
54- * Uses K/M suffixes for large values, appropriate decimal places for small values.
82+ * Formats a number for display as a big number with abbreviation (K/M/B suffixes).
5583 */
56- function formatBigNumber ( value : number ) : { formatted : string ; suffix ?: string } {
84+ function formatBigNumberAbbreviated ( value : number ) : { formatted : string ; unitSuffix ?: string } {
5785 if ( Math . abs ( value ) >= 1_000_000_000 ) {
5886 const v = value / 1_000_000_000 ;
59- return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , suffix : "B" } ;
87+ return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , unitSuffix : "B" } ;
6088 }
6189 if ( Math . abs ( value ) >= 1_000_000 ) {
6290 const v = value / 1_000_000 ;
63- return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , suffix : "M" } ;
91+ return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , unitSuffix : "M" } ;
6492 }
6593 if ( Math . abs ( value ) >= 1_000 ) {
6694 const v = value / 1_000 ;
67- return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , suffix : "K" } ;
95+ return { formatted : v % 1 === 0 ? v . toFixed ( 0 ) : v . toFixed ( 1 ) , unitSuffix : "K" } ;
6896 }
97+ return { formatted : formatPlainNumber ( value ) } ;
98+ }
99+
100+ /**
101+ * Formats a number for display without abbreviation.
102+ */
103+ function formatPlainNumber ( value : number ) : string {
69104 if ( Number . isInteger ( value ) ) {
70- return { formatted : value . toLocaleString ( ) } ;
105+ return value . toLocaleString ( ) ;
71106 }
72107 if ( Math . abs ( value ) < 0.01 ) {
73- return { formatted : value . toFixed ( 4 ) } ;
108+ return value . toFixed ( 4 ) ;
74109 }
75110 if ( Math . abs ( value ) < 1 ) {
76- return { formatted : value . toFixed ( 3 ) } ;
111+ return value . toFixed ( 3 ) ;
77112 }
78- return { formatted : value . toFixed ( 2 ) } ;
113+ return value . toFixed ( 2 ) ;
79114}
80115
81116export function BigNumberCard ( { rows, columns, config, isLoading = false } : BigNumberCardProps ) {
82- const { column, aggregation } = config ;
117+ const { column, aggregation, sortDirection , abbreviate = false , prefix , suffix } = config ;
83118
84119 const result = useMemo ( ( ) => {
85120 if ( rows . length === 0 ) return null ;
86121
87- const values = extractColumnValues ( rows , column ) ;
122+ const values = extractColumnValues ( rows , column , sortDirection ) ;
88123 if ( values . length === 0 ) return null ;
89124
90125 return aggregateValues ( values , aggregation ) ;
91- } , [ rows , column , aggregation ] ) ;
126+ } , [ rows , column , aggregation , sortDirection ] ) ;
92127
93128 if ( isLoading ) {
94129 return (
@@ -108,14 +143,23 @@ export function BigNumberCard({ rows, columns, config, isLoading = false }: BigN
108143 ) ;
109144 }
110145
111- const { formatted, suffix } = formatBigNumber ( result ) ;
146+ const { formatted, unitSuffix } = abbreviate
147+ ? formatBigNumberAbbreviated ( result )
148+ : { formatted : formatPlainNumber ( result ) , unitSuffix : undefined } ;
112149
113150 return (
114151 < div className = "flex h-full items-center justify-center p-6" >
115152 < div className = "text-[3.75rem] font-normal tabular-nums leading-none text-text-bright" >
116153 < div className = "flex items-baseline gap-1" >
154+ { prefix && < span > { prefix } </ span > }
117155 { formatted }
118- { suffix && < div className = "text-2xl text-text-dimmed" > { suffix } </ div > }
156+ { ( unitSuffix || suffix ) && (
157+ < div className = "text-2xl text-text-dimmed" >
158+ { unitSuffix }
159+ { unitSuffix && suffix ? " " : "" }
160+ { suffix }
161+ </ div >
162+ ) }
119163 </ div >
120164 </ div >
121165 </ div >
0 commit comments