@@ -35,6 +35,8 @@ const FALLBACK_TASK_OPTIONS: BillingTaskRoutingRuleSummary['taskType'][] = ['gen
3535export default function ProviderAuthBillingMatrix ( ) {
3636 const [ historyDays , setHistoryDays ] = useState ( 30 ) ;
3737 const [ fallbackTaskType , setFallbackTaskType ] = useState < BillingTaskRoutingRuleSummary [ 'taskType' ] > ( 'general' ) ;
38+ const [ fallbackHistoryCauseFilter , setFallbackHistoryCauseFilter ] = useState < 'all' | 'fallback_provider' | 'budget_forced_local' | 'emergency_fallback' > ( 'all' ) ;
39+ const [ fallbackHistoryTaskFilter , setFallbackHistoryTaskFilter ] = useState < 'all' | BillingTaskRoutingRuleSummary [ 'taskType' ] > ( 'all' ) ;
3840
3941 // Key update dialog state
4042 const [ activePortalId , setActivePortalId ] = useState < string | null > ( null ) ;
@@ -125,6 +127,33 @@ export default function ProviderAuthBillingMatrix() {
125127 const activeRoutingMutationTask = setTaskRoutingRuleMutation . variables && 'taskType' in setTaskRoutingRuleMutation . variables
126128 ? setTaskRoutingRuleMutation . variables . taskType
127129 : undefined ;
130+ const fallbackHistoryRows = ( fallbackHistory ?? [ ] ) as Array < {
131+ id : number ;
132+ timestamp : number ;
133+ requestedProvider ?: string ;
134+ selectedProvider : string ;
135+ selectedModelId : string ;
136+ taskType : BillingTaskRoutingRuleSummary [ 'taskType' ] ;
137+ strategy : string ;
138+ reason : string ;
139+ causeCode : 'fallback_provider' | 'budget_forced_local' | 'emergency_fallback' | 'preference_honored' ;
140+ } > ;
141+ const fallbackHistoryTaskOptions = Array . from ( new Set ( fallbackHistoryRows . map ( ( event ) => event . taskType ) ) ) . sort ( ) ;
142+ const fallbackHistoryCauseCounts = fallbackHistoryRows . reduce ( ( accumulator , event ) => {
143+ accumulator [ event . causeCode ] = ( accumulator [ event . causeCode ] ?? 0 ) + 1 ;
144+ return accumulator ;
145+ } , { } as Record < string , number > ) ;
146+ const filteredFallbackHistoryRows = fallbackHistoryRows . filter ( ( event ) => {
147+ if ( fallbackHistoryCauseFilter !== 'all' && event . causeCode !== fallbackHistoryCauseFilter ) {
148+ return false ;
149+ }
150+
151+ if ( fallbackHistoryTaskFilter !== 'all' && event . taskType !== fallbackHistoryTaskFilter ) {
152+ return false ;
153+ }
154+
155+ return true ;
156+ } ) ;
128157
129158 const handleDefaultStrategyChange = ( event : React . ChangeEvent < HTMLSelectElement > ) => {
130159 setRoutingStrategyMutation . mutate ( { strategy : event . target . value as BillingRoutingStrategy } ) ;
@@ -293,7 +322,74 @@ export default function ProviderAuthBillingMatrix() {
293322 </ Button >
294323 </ CardHeader >
295324 < CardContent className = "pt-2 space-y-1.5 max-h-64 overflow-y-auto" >
296- { fallbackHistory . map ( ( event ) => {
325+ < div className = "mb-2 space-y-2 rounded-lg border border-zinc-800/70 bg-black/30 p-2 text-[10px]" >
326+ < div className = "flex flex-wrap items-center gap-2" >
327+ < span className = "text-zinc-500 uppercase tracking-wider" > Cause</ span >
328+ { ( [
329+ { value : 'all' , label : 'All' } ,
330+ { value : 'fallback_provider' , label : 'Fallback' } ,
331+ { value : 'budget_forced_local' , label : 'Budget' } ,
332+ { value : 'emergency_fallback' , label : 'Emergency' } ,
333+ ] as const ) . map ( ( option ) => {
334+ const active = fallbackHistoryCauseFilter === option . value ;
335+ const count = option . value === 'all'
336+ ? fallbackHistoryRows . length
337+ : ( fallbackHistoryCauseCounts [ option . value ] ?? 0 ) ;
338+ return (
339+ < button
340+ key = { `fallback-cause-filter-${ option . value } ` }
341+ type = "button"
342+ onClick = { ( ) => setFallbackHistoryCauseFilter ( option . value ) }
343+ className = { `rounded border px-2 py-1 transition-colors ${ active
344+ ? 'border-amber-500/50 bg-amber-500/15 text-amber-200'
345+ : 'border-zinc-700 bg-zinc-950/70 text-zinc-300 hover:bg-zinc-800'
346+ } `}
347+ title = { `Filter fallback history by ${ option . label . toLowerCase ( ) } causes` }
348+ aria-label = { `Filter fallback history by ${ option . label } causes` }
349+ >
350+ { option . label } ({ count } )
351+ </ button >
352+ ) ;
353+ } ) }
354+ </ div >
355+
356+ < div className = "flex flex-wrap items-center gap-2" >
357+ < span className = "text-zinc-500 uppercase tracking-wider" > Task</ span >
358+ < button
359+ type = "button"
360+ onClick = { ( ) => setFallbackHistoryTaskFilter ( 'all' ) }
361+ className = { `rounded border px-2 py-1 transition-colors ${ fallbackHistoryTaskFilter === 'all'
362+ ? 'border-cyan-500/50 bg-cyan-500/15 text-cyan-200'
363+ : 'border-zinc-700 bg-zinc-950/70 text-zinc-300 hover:bg-zinc-800'
364+ } `}
365+ title = "Show all task types"
366+ aria-label = "Show all task types in fallback history"
367+ >
368+ All ({ fallbackHistoryRows . length } )
369+ </ button >
370+ { fallbackHistoryTaskOptions . map ( ( taskTypeOption ) => (
371+ < button
372+ key = { `fallback-task-filter-${ taskTypeOption } ` }
373+ type = "button"
374+ onClick = { ( ) => setFallbackHistoryTaskFilter ( taskTypeOption ) }
375+ className = { `rounded border px-2 py-1 capitalize transition-colors ${ fallbackHistoryTaskFilter === taskTypeOption
376+ ? 'border-cyan-500/50 bg-cyan-500/15 text-cyan-200'
377+ : 'border-zinc-700 bg-zinc-950/70 text-zinc-300 hover:bg-zinc-800'
378+ } `}
379+ title = { `Filter fallback history to ${ formatTaskRoutingLabel ( taskTypeOption ) . toLowerCase ( ) } decisions` }
380+ aria-label = { `Filter fallback history to ${ formatTaskRoutingLabel ( taskTypeOption ) } decisions` }
381+ >
382+ { formatTaskRoutingLabel ( taskTypeOption ) }
383+ </ button >
384+ ) ) }
385+ </ div >
386+
387+ < div className = "text-zinc-500" >
388+ showing { filteredFallbackHistoryRows . length } of { fallbackHistoryRows . length } decisions
389+ </ div >
390+ </ div >
391+
392+ { filteredFallbackHistoryRows . map ( ( event ) => {
297393 const causeColor =
298394 event . causeCode === 'emergency_fallback' ? 'text-red-400 border-red-800' :
299395 event . causeCode === 'budget_forced_local' ? 'text-orange-400 border-orange-800' :
@@ -327,6 +423,11 @@ export default function ProviderAuthBillingMatrix() {
327423 </ div >
328424 ) ;
329425 } ) }
426+ { filteredFallbackHistoryRows . length === 0 ? (
427+ < div className = "rounded-lg border border-dashed border-zinc-800 p-3 text-xs text-zinc-500 text-center" >
428+ No fallback decisions match the selected filters.
429+ </ div >
430+ ) : null }
330431 </ CardContent >
331432 </ Card >
332433 ) }
0 commit comments