33import { Tab , TabGroup , TabList } from '@tremor/react' ;
44import { useGenericCounter } from '@/hooks/useTinybirdData' ;
55import { useSearchParams } from 'next/navigation' ;
6- import BarList from './BarList ' ;
6+ import CustomBarList from './CustomBarList ' ;
77import { useState , useEffect } from 'react' ;
88import { tabs } from '../constants' ;
99import { useTinybirdToken } from '@/providers/TinybirdProvider' ;
10+ import {
11+ Server ,
12+ Cloud ,
13+ User ,
14+ Building2 ,
15+ Cpu
16+ } from 'lucide-react' ;
17+
18+ // Custom OpenAI Icon
19+ const OpenAIIcon = ( ) => (
20+ < svg viewBox = "0 0 24 24" width = "16" height = "16" fill = "none" xmlns = "http://www.w3.org/2000/svg" className = "text-green-500" >
21+ < path d = "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" fill = "currentColor" />
22+ </ svg >
23+ ) ;
24+
25+ // Custom Anthropic Icon
26+ const AnthropicIcon = ( ) => (
27+ < svg viewBox = "0 0 24 24" width = "16" height = "16" fill = "none" xmlns = "http://www.w3.org/2000/svg" className = "text-purple-500" >
28+ < path d = "M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z" fill = "currentColor" />
29+ </ svg >
30+ ) ;
31+
32+ // Custom Google AI Icon
33+ const GoogleAIIcon = ( ) => (
34+ < svg viewBox = "0 0 24 24" width = "16" height = "16" fill = "none" xmlns = "http://www.w3.org/2000/svg" className = "text-blue-500" >
35+ < path d = "M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" fill = "currentColor" />
36+ </ svg >
37+ ) ;
38+
39+ // Custom GPT-4 Icon
40+ const GPT4Icon = ( ) => (
41+ < svg viewBox = "0 0 24 24" width = "16" height = "16" fill = "none" xmlns = "http://www.w3.org/2000/svg" className = "text-green-600" >
42+ < path d = "M18.5 3h-13A2.5 2.5 0 0 0 3 5.5v13A2.5 2.5 0 0 0 5.5 21h13a2.5 2.5 0 0 0 2.5-2.5v-13A2.5 2.5 0 0 0 18.5 3zM8 17.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v11zm5 0a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v11zm5 0a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v11z" fill = "currentColor" />
43+ </ svg >
44+ ) ;
45+
46+ // Custom Claude Icon
47+ const ClaudeIcon = ( ) => (
48+ < svg viewBox = "0 0 24 24" width = "16" height = "16" fill = "none" xmlns = "http://www.w3.org/2000/svg" className = "text-purple-600" >
49+ < path d = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-4-8c0-2.21 1.79-4 4-4s4 1.79 4 4-1.79 4-4 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z" fill = "currentColor" />
50+ < path d = "M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z" fill = "currentColor" />
51+ </ svg >
52+ ) ;
53+
54+ // Helper function to get icon for provider
55+ const getProviderIcon = ( provider : string ) => {
56+ const lowerProvider = provider . toLowerCase ( ) ;
57+ if ( lowerProvider . includes ( 'openai' ) ) {
58+ return < OpenAIIcon /> ;
59+ } else if ( lowerProvider . includes ( 'anthropic' ) ) {
60+ return < AnthropicIcon /> ;
61+ } else if ( lowerProvider . includes ( 'google' ) ) {
62+ return < GoogleAIIcon /> ;
63+ } else {
64+ return < Cloud className = "w-4 h-4 text-gray-500" /> ;
65+ }
66+ } ;
67+
68+ // Helper function to get icon for model
69+ const getModelIcon = ( model : string ) => {
70+ const lowerModel = model . toLowerCase ( ) ;
71+ if ( lowerModel . includes ( 'gpt' ) ) {
72+ return < OpenAIIcon /> ;
73+ } else if ( lowerModel . includes ( 'claude' ) ) {
74+ return < AnthropicIcon /> ;
75+ } else if ( lowerModel . includes ( 'palm' ) || lowerModel . includes ( 'gemini' ) ) {
76+ return < GoogleAIIcon /> ;
77+ } else {
78+ return < Cpu className = "w-4 h-4 text-gray-500" /> ;
79+ }
80+ } ;
81+
82+ // Helper function to get icon for environment
83+ const getEnvironmentIcon = ( env : string ) => {
84+ const lowerEnv = env . toLowerCase ( ) ;
85+ if ( lowerEnv . includes ( 'prod' ) ) {
86+ return < Server className = "w-4 h-4 text-green-500" /> ;
87+ } else if ( lowerEnv . includes ( 'staging' ) ) {
88+ return < Server className = "w-4 h-4 text-yellow-500" /> ;
89+ } else if ( lowerEnv . includes ( 'dev' ) ) {
90+ return < Server className = "w-4 h-4 text-blue-500" /> ;
91+ } else {
92+ return < Server className = "w-4 h-4 text-gray-500" /> ;
93+ }
94+ } ;
1095
1196interface TabbedPaneProps {
1297 filters : Record < string , string > ;
@@ -19,7 +104,7 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
19104 const filteredTabs = tabs . filter ( tab => ! orgName || tab . key !== 'organization' ) ;
20105 const initialDimension = searchParams . get ( 'dimension' ) || filteredTabs [ 0 ] . key ;
21106 const [ selectedTab , setSelectedTab ] = useState < string > ( initialDimension ) ;
22- const [ barListData , setBarListData ] = useState < Array < { name : string ; value : number } > > ( [ ] ) ;
107+ const [ barListData , setBarListData ] = useState < Array < { name : string ; value : number ; icon ?: React . ReactNode } > > ( [ ] ) ;
23108 // eslint-disable-next-line @typescript-eslint/no-unused-vars
24109 const [ selectedValues , setSelectedValues ] = useState < string [ ] > ( [ ] ) ;
25110
@@ -77,13 +162,40 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
77162 useEffect ( ( ) => {
78163 if ( data ?. data ) {
79164 // eslint-disable-next-line @typescript-eslint/no-explicit-any
80- const newData = data . data . map ( ( item : any ) => ( {
81- name : item . category || 'Unknown' ,
82- value : item . total_cost || 0 // Use total_cost instead of count
83- } ) ) ;
165+ const newData = data . data . map ( ( item : any ) => {
166+ const name = item . category || 'Unknown' ;
167+ let icon ;
168+
169+ // Assign icon based on the selected tab
170+ switch ( selectedTab ) {
171+ case 'provider' :
172+ icon = getProviderIcon ( name ) ;
173+ break ;
174+ case 'model' :
175+ icon = getModelIcon ( name ) ;
176+ break ;
177+ case 'environment' :
178+ icon = getEnvironmentIcon ( name ) ;
179+ break ;
180+ case 'organization' :
181+ icon = < Building2 className = "w-4 h-4 text-gray-500" /> ;
182+ break ;
183+ case 'user' :
184+ icon = < User className = "w-4 h-4 text-gray-500" /> ;
185+ break ;
186+ default :
187+ icon = < Cloud className = "w-4 h-4 text-gray-500" /> ;
188+ }
189+
190+ return {
191+ name,
192+ value : item . total_cost || 0 ,
193+ icon
194+ } ;
195+ } ) ;
84196 setBarListData ( newData ) ;
85197 }
86- } , [ data ] ) ;
198+ } , [ data , selectedTab ] ) ;
87199
88200 return (
89201 < div className = "h-full" >
@@ -99,8 +211,8 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
99211 className = { ( { selected } ) =>
100212 `px-4 py-2 text-sm font-medium rounded-lg transition-colors
101213 ${ selected
102- ? 'bg-tremor-background text-tremor-content-strong shadow '
103- : 'text-tremor-content hover:bg-tremor-background-subtle ' } `
214+ ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900 dark:text-indigo-300 '
215+ : 'text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800 ' } `
104216 }
105217 >
106218 { tab . name }
@@ -113,9 +225,9 @@ export default function TabbedPane({ filters, onFilterUpdate }: TabbedPaneProps)
113225 ) : error ? (
114226 < div > Error loading data</ div >
115227 ) : (
116- < BarList
228+ < CustomBarList
117229 data = { barListData }
118- valueFormatter = { ( value : number ) => `$${ value . toLocaleString ( ) } ` } // Add $ sign for cost values
230+ valueFormatter = { ( value : number ) => `$${ value . toLocaleString ( ) } ` }
119231 onSelectionChange = { handleSelectionChange }
120232 />
121233 ) }
0 commit comments