@@ -7,7 +7,12 @@ import { BillingChart } from "@/components/charts/BillingChart";
77import { ServiceChart } from "@/components/charts/ServiceChart" ;
88import { Tabs } from "@/components/ui/Tabs" ;
99import { DataFilters } from "@/components/ui/DataFilters" ;
10- import { GitHubBillingReport , BillingData , CategorizedBillingData , ServiceData } from "@/types/billing" ;
10+ import {
11+ GitHubBillingReport ,
12+ BillingData ,
13+ CategorizedBillingData ,
14+ ServiceData ,
15+ } from "@/types/billing" ;
1116
1217const sampleBillingData : BillingData [ ] = [
1318 { month : "Jan" , actions : 120 , packages : 80 , storage : 40 } ,
@@ -21,9 +26,20 @@ const sampleBillingData: BillingData[] = [
2126export default function Home ( ) {
2227 const [ billingData , setBillingData ] =
2328 useState < BillingData [ ] > ( sampleBillingData ) ;
24- const [ categorizedData , setCategorizedData ] = useState < CategorizedBillingData | null > ( null ) ;
25- const [ filteredData , setFilteredData ] = useState < CategorizedBillingData | null > ( null ) ;
29+ const [ categorizedData , setCategorizedData ] =
30+ useState < CategorizedBillingData | null > ( null ) ;
31+ const [ filteredData , setFilteredData ] =
32+ useState < CategorizedBillingData | null > ( null ) ;
2633 const [ hasUploadedData , setHasUploadedData ] = useState ( false ) ;
34+ const [ breakdown , setBreakdown ] = useState <
35+ Record < string , "cost" | "quantity" >
36+ > ( {
37+ actionsMinutes : "quantity" ,
38+ actionsStorage : "quantity" ,
39+ packages : "quantity" ,
40+ copilot : "quantity" ,
41+ codespaces : "quantity" ,
42+ } ) ;
2743
2844 const handleDataLoaded = ( report : GitHubBillingReport ) => {
2945 setBillingData ( report . data ) ;
@@ -32,15 +48,53 @@ export default function Home() {
3248 setHasUploadedData ( true ) ;
3349 } ;
3450
35- const handleFiltersChange = useCallback ( ( serviceType : keyof CategorizedBillingData , filteredServiceData : ServiceData [ ] ) => {
36- setFilteredData ( prev => {
37- if ( ! prev ) return null ;
38- return {
51+ const handleFiltersChange = useCallback (
52+ (
53+ serviceType : keyof CategorizedBillingData ,
54+ filteredServiceData : ServiceData [ ]
55+ ) => {
56+ setFilteredData ( ( prev ) => {
57+ if ( ! prev ) return null ;
58+ return {
59+ ...prev ,
60+ [ serviceType ] : filteredServiceData ,
61+ } ;
62+ } ) ;
63+ } ,
64+ [ ]
65+ ) ;
66+
67+ const handleBreakdownChange = useCallback (
68+ ( serviceType : string , newBreakdown : "cost" | "quantity" ) => {
69+ setBreakdown ( ( prev ) => ( {
3970 ...prev ,
40- [ serviceType ] : filteredServiceData
41- } ;
42- } ) ;
43- } , [ ] ) ;
71+ [ serviceType ] : newBreakdown ,
72+ } ) ) ;
73+ } ,
74+ [ ]
75+ ) ;
76+
77+ // Memoized breakdown change handlers to prevent re-renders
78+ const handleActionsMinutesBreakdownChange = useCallback (
79+ ( newBreakdown : "cost" | "quantity" ) => {
80+ handleBreakdownChange ( "actionsMinutes" , newBreakdown ) ;
81+ } ,
82+ [ handleBreakdownChange ]
83+ ) ;
84+
85+ const handleActionsStorageBreakdownChange = useCallback (
86+ ( newBreakdown : "cost" | "quantity" ) => {
87+ handleBreakdownChange ( "actionsStorage" , newBreakdown ) ;
88+ } ,
89+ [ handleBreakdownChange ]
90+ ) ;
91+
92+ const handlePackagesBreakdownChange = useCallback (
93+ ( newBreakdown : "cost" | "quantity" ) => {
94+ handleBreakdownChange ( "packages" , newBreakdown ) ;
95+ } ,
96+ [ handleBreakdownChange ]
97+ ) ;
4498
4599 // Create tabs based on available data
46100 const createTabs = ( ) => {
@@ -67,17 +121,33 @@ export default function Home() {
67121 < div >
68122 < DataFilters
69123 data = { categorizedData . actionsMinutes }
70- onFiltersChange = { ( filtered ) => handleFiltersChange ( "actionsMinutes" , filtered ) }
124+ onFiltersChange = { ( filtered ) =>
125+ handleFiltersChange ( "actionsMinutes" , filtered )
126+ }
127+ onBreakdownChange = { handleActionsMinutesBreakdownChange }
128+ serviceType = "actionsMinutes"
71129 />
72130 < ServiceChart
73131 data = { filteredData . actionsMinutes }
74132 title = "GitHub Actions Minutes"
75133 serviceType = "actionsMinutes"
134+ breakdown = { breakdown . actionsMinutes }
76135 useSkuAnalysis = { ( ( ) => {
77136 // Use SKU analysis when all organizations are shown (no organization filter applied)
78- const originalOrgs = new Set ( categorizedData . actionsMinutes . map ( item => item . organization ) . filter ( Boolean ) ) ;
79- const filteredOrgs = new Set ( filteredData . actionsMinutes . map ( item => item . organization ) . filter ( Boolean ) ) ;
80- return originalOrgs . size === filteredOrgs . size && originalOrgs . size > 1 ;
137+ const originalOrgs = new Set (
138+ categorizedData . actionsMinutes
139+ . map ( ( item ) => item . organization )
140+ . filter ( Boolean )
141+ ) ;
142+ const filteredOrgs = new Set (
143+ filteredData . actionsMinutes
144+ . map ( ( item ) => item . organization )
145+ . filter ( Boolean )
146+ ) ;
147+ return (
148+ originalOrgs . size === filteredOrgs . size &&
149+ originalOrgs . size > 1
150+ ) ;
81151 } ) ( ) }
82152 />
83153 </ div >
@@ -90,12 +160,17 @@ export default function Home() {
90160 < div >
91161 < DataFilters
92162 data = { categorizedData . actionsStorage }
93- onFiltersChange = { ( filtered ) => handleFiltersChange ( "actionsStorage" , filtered ) }
163+ onFiltersChange = { ( filtered ) =>
164+ handleFiltersChange ( "actionsStorage" , filtered )
165+ }
166+ onBreakdownChange = { handleActionsStorageBreakdownChange }
167+ serviceType = "actionsStorage"
94168 />
95169 < ServiceChart
96170 data = { filteredData . actionsStorage }
97171 title = "GitHub Actions Storage"
98172 serviceType = "actionsStorage"
173+ breakdown = { breakdown . actionsStorage }
99174 />
100175 </ div >
101176 ) ,
@@ -107,12 +182,17 @@ export default function Home() {
107182 < div >
108183 < DataFilters
109184 data = { categorizedData . packages }
110- onFiltersChange = { ( filtered ) => handleFiltersChange ( "packages" , filtered ) }
185+ onFiltersChange = { ( filtered ) =>
186+ handleFiltersChange ( "packages" , filtered )
187+ }
188+ onBreakdownChange = { handlePackagesBreakdownChange }
189+ serviceType = "packages"
111190 />
112191 < ServiceChart
113192 data = { filteredData . packages }
114193 title = "GitHub Packages"
115194 serviceType = "packages"
195+ breakdown = { breakdown . packages }
116196 />
117197 </ div >
118198 ) ,
@@ -124,7 +204,10 @@ export default function Home() {
124204 < div >
125205 < DataFilters
126206 data = { categorizedData . copilot }
127- onFiltersChange = { ( filtered ) => handleFiltersChange ( "copilot" , filtered ) }
207+ onFiltersChange = { ( filtered ) =>
208+ handleFiltersChange ( "copilot" , filtered )
209+ }
210+ serviceType = "copilot"
128211 />
129212 < ServiceChart
130213 data = { filteredData . copilot }
@@ -141,7 +224,10 @@ export default function Home() {
141224 < div >
142225 < DataFilters
143226 data = { categorizedData . codespaces }
144- onFiltersChange = { ( filtered ) => handleFiltersChange ( "codespaces" , filtered ) }
227+ onFiltersChange = { ( filtered ) =>
228+ handleFiltersChange ( "codespaces" , filtered )
229+ }
230+ serviceType = "codespaces"
145231 />
146232 < ServiceChart
147233 data = { filteredData . codespaces }
@@ -151,7 +237,7 @@ export default function Home() {
151237 </ div >
152238 ) ,
153239 } ,
154- ] . filter ( tab => {
240+ ] . filter ( ( tab ) => {
155241 // Only show tabs with data
156242 switch ( tab . id ) {
157243 case "actionsMinutes" :
@@ -191,8 +277,8 @@ export default function Home() {
191277 </ h1 >
192278
193279 < p className = "text-xl text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed" >
194- Upload your GitHub billing report to visualize spending patterns and
195- optimize costs across all services.
280+ Upload your GitHub billing report to visualize spending patterns
281+ and optimize costs across all services.
196282 < br />
197283 < span className = "text-sm text-gray-500 mt-2 block" >
198284 Your data is processed locally and not stored on our servers.
@@ -218,8 +304,18 @@ export default function Home() {
218304 } }
219305 className = "inline-flex items-center px-4 py-2 text-sm font-medium text-gray-300 bg-gray-800/50 border border-gray-600 rounded-lg hover:bg-gray-700/50 hover:border-gray-500 transition-colors"
220306 >
221- < svg className = "w-4 h-4 mr-2" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
222- < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M10 19l-7-7m0 0l7-7m-7 7h18" />
307+ < svg
308+ className = "w-4 h-4 mr-2"
309+ fill = "none"
310+ stroke = "currentColor"
311+ viewBox = "0 0 24 24"
312+ >
313+ < path
314+ strokeLinecap = "round"
315+ strokeLinejoin = "round"
316+ strokeWidth = { 2 }
317+ d = "M10 19l-7-7m0 0l7-7m-7 7h18"
318+ />
223319 </ svg >
224320 Upload New File
225321 </ button >
0 commit comments