11"use client" ;
22
3- import { useState } from "react" ;
3+ import { useState , useCallback } from "react" ;
44import { Navigation } from "@/components/ui/Navigation" ;
55import { FileUpload } from "@/components/ui/FileUpload" ;
66import { BillingChart } from "@/components/charts/BillingChart" ;
7- import { GitHubBillingReport , BillingData } from "@/types/billing" ;
7+ import { ServiceChart } from "@/components/charts/ServiceChart" ;
8+ import { Tabs } from "@/components/ui/Tabs" ;
9+ import { DataFilters } from "@/components/ui/DataFilters" ;
10+ import { GitHubBillingReport , BillingData , CategorizedBillingData , ServiceData } from "@/types/billing" ;
811
912const sampleBillingData : BillingData [ ] = [
1013 { month : "Jan" , actions : 120 , packages : 80 , storage : 40 } ,
@@ -18,55 +21,222 @@ const sampleBillingData: BillingData[] = [
1821export default function Home ( ) {
1922 const [ billingData , setBillingData ] =
2023 useState < BillingData [ ] > ( sampleBillingData ) ;
24+ const [ categorizedData , setCategorizedData ] = useState < CategorizedBillingData | null > ( null ) ;
25+ const [ filteredData , setFilteredData ] = useState < CategorizedBillingData | null > ( null ) ;
2126 const [ hasUploadedData , setHasUploadedData ] = useState ( false ) ;
2227
2328 const handleDataLoaded = ( report : GitHubBillingReport ) => {
2429 setBillingData ( report . data ) ;
30+ setCategorizedData ( report . categorizedData || null ) ;
31+ setFilteredData ( report . categorizedData || null ) ;
2532 setHasUploadedData ( true ) ;
2633 } ;
2734
35+ const handleFiltersChange = useCallback ( ( serviceType : keyof CategorizedBillingData , filteredServiceData : ServiceData [ ] ) => {
36+ setFilteredData ( prev => {
37+ if ( ! prev ) return null ;
38+ return {
39+ ...prev ,
40+ [ serviceType ] : filteredServiceData
41+ } ;
42+ } ) ;
43+ } , [ ] ) ;
44+
45+ // Create tabs based on available data
46+ const createTabs = ( ) => {
47+ if ( ! categorizedData || ! filteredData ) {
48+ return [
49+ {
50+ id : "overview" ,
51+ label : "Sample Overview" ,
52+ content : (
53+ < BillingChart
54+ data = { billingData }
55+ title = "Sample GitHub Billing Visualization"
56+ />
57+ ) ,
58+ } ,
59+ ] ;
60+ }
61+
62+ return [
63+ {
64+ id : "actionsMinutes" ,
65+ label : "Actions Minutes" ,
66+ content : (
67+ < div >
68+ < DataFilters
69+ data = { categorizedData . actionsMinutes }
70+ onFiltersChange = { ( filtered ) => handleFiltersChange ( "actionsMinutes" , filtered ) }
71+ />
72+ < ServiceChart
73+ data = { filteredData . actionsMinutes }
74+ title = "GitHub Actions Minutes"
75+ serviceType = "actionsMinutes"
76+ useSkuAnalysis = { ( ( ) => {
77+ // 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 ;
81+ } ) ( ) }
82+ />
83+ </ div >
84+ ) ,
85+ } ,
86+ {
87+ id : "actionsStorage" ,
88+ label : "Actions Storage" ,
89+ content : (
90+ < div >
91+ < DataFilters
92+ data = { categorizedData . actionsStorage }
93+ onFiltersChange = { ( filtered ) => handleFiltersChange ( "actionsStorage" , filtered ) }
94+ />
95+ < ServiceChart
96+ data = { filteredData . actionsStorage }
97+ title = "GitHub Actions Storage"
98+ serviceType = "actionsStorage"
99+ />
100+ </ div >
101+ ) ,
102+ } ,
103+ {
104+ id : "packages" ,
105+ label : "Packages" ,
106+ content : (
107+ < div >
108+ < DataFilters
109+ data = { categorizedData . packages }
110+ onFiltersChange = { ( filtered ) => handleFiltersChange ( "packages" , filtered ) }
111+ />
112+ < ServiceChart
113+ data = { filteredData . packages }
114+ title = "GitHub Packages"
115+ serviceType = "packages"
116+ />
117+ </ div >
118+ ) ,
119+ } ,
120+ {
121+ id : "copilot" ,
122+ label : "Copilot" ,
123+ content : (
124+ < div >
125+ < DataFilters
126+ data = { categorizedData . copilot }
127+ onFiltersChange = { ( filtered ) => handleFiltersChange ( "copilot" , filtered ) }
128+ />
129+ < ServiceChart
130+ data = { filteredData . copilot }
131+ title = "GitHub Copilot"
132+ serviceType = "copilot"
133+ />
134+ </ div >
135+ ) ,
136+ } ,
137+ {
138+ id : "codespaces" ,
139+ label : "Codespaces" ,
140+ content : (
141+ < div >
142+ < DataFilters
143+ data = { categorizedData . codespaces }
144+ onFiltersChange = { ( filtered ) => handleFiltersChange ( "codespaces" , filtered ) }
145+ />
146+ < ServiceChart
147+ data = { filteredData . codespaces }
148+ title = "GitHub Codespaces"
149+ serviceType = "codespaces"
150+ />
151+ </ div >
152+ ) ,
153+ } ,
154+ ] . filter ( tab => {
155+ // Only show tabs with data
156+ switch ( tab . id ) {
157+ case "actionsMinutes" :
158+ return categorizedData . actionsMinutes . length > 0 ;
159+ case "actionsStorage" :
160+ return categorizedData . actionsStorage . length > 0 ;
161+ case "packages" :
162+ return categorizedData . packages . length > 0 ;
163+ case "copilot" :
164+ return categorizedData . copilot . length > 0 ;
165+ case "codespaces" :
166+ return categorizedData . codespaces . length > 0 ;
167+ default :
168+ return true ;
169+ }
170+ } ) ;
171+ } ;
172+
173+ const tabs = createTabs ( ) ;
174+
28175 return (
29176 < div className = "min-h-screen bg-gray-950 text-white" >
30177 < Navigation />
31178
32179 { /* Main Content */ }
33180 < section className = "relative py-32 px-4 sm:px-6 lg:px-8" >
34- < div className = "max-w-4xl mx-auto text-center" >
35- < h1 className = "text-5xl md:text-7xl font-bold mb-8 bg-gradient-to-r from-white to-gray-400 bg-clip-text text-transparent" >
36- Understand Your
37- < br />
38- < span className = "bg-gradient-to-r from-green-400 to-blue-400 bg-clip-text text-transparent" >
39- GitHub Costs
40- </ span >
41- </ h1 >
181+ < div className = "max-w-7xl mx-auto" >
182+ { ! hasUploadedData ? (
183+ /* Landing Content - Only shown when no data is uploaded */
184+ < div className = "text-center" >
185+ < h1 className = "text-5xl md:text-7xl font-bold mb-8 bg-gradient-to-r from-white to-gray-400 bg-clip-text text-transparent" >
186+ Understand Your
187+ < br />
188+ < span className = "bg-gradient-to-r from-green-400 to-blue-400 bg-clip-text text-transparent" >
189+ GitHub Costs
190+ </ span >
191+ </ h1 >
42192
43- < p className = "text-xl text-gray-400 mb-12 max-w-2xl mx-auto leading-relaxed" >
44- Upload your GitHub billing report to visualize spending patterns and
45- optimize costs.
46- < br />
47- < span className = "text-sm text-gray-500 mt-2 block" >
48- Your data is processed locally and not stored on our servers.
49- </ span >
50- </ p >
193+ < 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 .
196+ < br />
197+ < span className = "text-sm text-gray-500 mt-2 block" >
198+ Your data is processed locally and not stored on our servers.
199+ </ span >
200+ </ p >
51201
52- { /* File Upload */ }
53- < div className = "mb-16" >
54- < FileUpload onDataLoaded = { handleDataLoaded } />
55- </ div >
202+ { /* File Upload */ }
203+ < div className = "mb-16" >
204+ < FileUpload onDataLoaded = { handleDataLoaded } />
205+ </ div >
206+ </ div >
207+ ) : (
208+ /* Analysis Content - Shown after upload */
209+ < div >
210+ { /* Back Button */ }
211+ < div className = "mb-8" >
212+ < button
213+ onClick = { ( ) => {
214+ setHasUploadedData ( false ) ;
215+ setCategorizedData ( null ) ;
216+ setFilteredData ( null ) ;
217+ setBillingData ( sampleBillingData ) ;
218+ } }
219+ 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"
220+ >
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" />
223+ </ svg >
224+ Upload New File
225+ </ button >
226+ </ div >
56227
57- { /* Chart Visualization */ }
58- < div className = "max-w-6xl mx-auto" >
59- < div className = "bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-8" >
60- < BillingChart
61- data = { billingData }
62- title = {
63- hasUploadedData
64- ? "Your GitHub Billing Data"
65- : "Sample GitHub Billing Visualization"
66- }
67- />
228+ { /* Full Width Visualization */ }
229+ < div className = "bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-8" >
230+ < div className = "mb-6" >
231+ < h2 className = "text-2xl font-bold mb-2" > Service Breakdown</ h2 >
232+ < p className = "text-gray-400" >
233+ Detailed cost and usage analysis by GitHub service
234+ </ p >
235+ </ div >
236+ < Tabs tabs = { tabs } defaultTab = "actionsMinutes" />
237+ </ div >
68238 </ div >
69- </ div >
239+ ) }
70240 </ div >
71241
72242 { /* Background decoration */ }
0 commit comments