@@ -23,7 +23,7 @@ const PIE_COLORS = [
2323] ;
2424
2525type OverviewMeta = { page : number ; pageSize : number ; totalModels : number ; totalPages : number } ;
26- type OverviewAPIResponse = { overview : UsageOverview | null ; empty : boolean ; days : number ; meta ?: OverviewMeta ; filters ?: { models : string [ ] ; routes : string [ ] } } ;
26+ type OverviewAPIResponse = { overview : UsageOverview | null ; empty : boolean ; days : number ; timezone ?: string ; meta ?: OverviewMeta ; filters ?: { models : string [ ] ; routes : string [ ] } } ;
2727
2828type PriceForm = {
2929 model : string ;
@@ -65,15 +65,21 @@ const numericTooltipFormatter: TooltipProps<number, string>["formatter"] = (valu
6565 return [ formatNumberWithCommas ( numericValue ) , name ] ;
6666} ;
6767
68- function formatHourKeyFromTs ( ts : number ) {
69- const parts = hourFormatter . formatToParts ( new Date ( ts ) ) ;
68+ function formatHourKeyFromTs ( ts : number , formatter : Intl . DateTimeFormat ) {
69+ const parts = formatter . formatToParts ( new Date ( ts ) ) ;
7070 const month = parts . find ( ( p ) => p . type === "month" ) ?. value ?? "00" ;
7171 const day = parts . find ( ( p ) => p . type === "day" ) ?. value ?? "00" ;
7272 const hour = parts . find ( ( p ) => p . type === "hour" ) ?. value ?? "00" ;
7373 return `${ month } -${ day } ${ hour } ` ;
7474}
7575
76- function buildHourlySeries ( series : UsageSeriesPoint [ ] , rangeHours ?: number ) {
76+ function buildHourlySeries ( series : UsageSeriesPoint [ ] , rangeHours ?: number , timezone ?: string ) {
77+ // Use the server's bucketing timezone for gap-fill labels so they match the
78+ // labels returned for real data points. Falls back to the module-level formatter
79+ // (browser timezone) when no timezone is provided.
80+ const gapFormatter = timezone
81+ ? new Intl . DateTimeFormat ( "en-CA" , { timeZone : timezone , month : "2-digit" , day : "2-digit" , hour : "2-digit" , hour12 : false } )
82+ : hourFormatter ;
7783 if ( ! series . length ) return [ ] as UsageSeriesPoint [ ] ;
7884
7985 const withTs = series
@@ -97,7 +103,7 @@ function buildHourlySeries(series: UsageSeriesPoint[], rangeHours?: number) {
97103 filled . push ( rest ) ;
98104 } else {
99105 filled . push ( {
100- label : formatHourKeyFromTs ( ts ) ,
106+ label : formatHourKeyFromTs ( ts , gapFormatter ) ,
101107 timestamp : new Date ( ts ) . toISOString ( ) ,
102108 requests : 0 ,
103109 tokens : 0 ,
@@ -166,6 +172,7 @@ export default function DashboardPage() {
166172 }
167173 } , [ ] ) ;
168174 const [ overview , setOverview ] = useState < UsageOverview | null > ( null ) ;
175+ const [ bucketTimezone , setBucketTimezone ] = useState < string | undefined > ( undefined ) ;
169176 const [ overviewError , setOverviewError ] = useState < string | null > ( null ) ;
170177 const [ overviewEmpty , setOverviewEmpty ] = useState ( false ) ;
171178 const [ loadingOverview , setLoadingOverview ] = useState ( true ) ;
@@ -729,6 +736,7 @@ export default function DashboardPage() {
729736 const data : OverviewAPIResponse = await res . json ( ) ;
730737 if ( ! active ) return ;
731738 setOverview ( data . overview ?? null ) ;
739+ setBucketTimezone ( data . timezone ) ;
732740 setOverviewEmpty ( Boolean ( data . empty ) ) ;
733741 setOverviewError ( null ) ;
734742 setPage ( data . meta ?. page ?? 1 ) ;
@@ -759,8 +767,8 @@ export default function DashboardPage() {
759767 if ( ! overviewData ?. byHour ) return [ ] as UsageSeriesPoint [ ] ;
760768 if ( hourRange === "all" ) return overviewData . byHour ;
761769 const hours = hourRange === "24h" ? 24 : 72 ;
762- return buildHourlySeries ( overviewData . byHour , hours ) ;
763- } , [ hourRange , overviewData ?. byHour ] ) ;
770+ return buildHourlySeries ( overviewData . byHour , hours , bucketTimezone ) ;
771+ } , [ hourRange , overviewData ?. byHour , bucketTimezone ] ) ;
764772
765773 const hourlyLineStyle = useMemo (
766774 ( ) => buildHourlyLineStyle ( hourlySeries . length , 3 ) ,
0 commit comments