Skip to content

Commit fa3ecce

Browse files
committed
feat: Enhance DataFilters component with repository and breakdown options
- Added repository filter to DataFilters for improved data filtering. - Introduced breakdown selection for cost and quantity in DataFilters. - Updated state management to handle new filters and notify parent component of changes. - Adjusted layout to accommodate new filters dynamically. refactor: Clean up Tabs component for consistency - Standardized formatting in Tabs component for better readability. fix: Improve CSV parsing logic in fileParser - Enhanced error handling for empty or invalid CSV files. - Streamlined header parsing and index finding for better maintainability. - Improved data categorization and summary generation for clarity and performance.
1 parent de51c73 commit fa3ecce

File tree

5 files changed

+1030
-320
lines changed

5 files changed

+1030
-320
lines changed

src/app/page.tsx

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { BillingChart } from "@/components/charts/BillingChart";
77
import { ServiceChart } from "@/components/charts/ServiceChart";
88
import { Tabs } from "@/components/ui/Tabs";
99
import { 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

1217
const sampleBillingData: BillingData[] = [
1318
{ month: "Jan", actions: 120, packages: 80, storage: 40 },
@@ -21,9 +26,20 @@ const sampleBillingData: BillingData[] = [
2126
export 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

Comments
 (0)