@@ -24,6 +24,7 @@ import {
2424 PowerUserDailyBreakdown ,
2525 ExceededRequestDetail ,
2626 ProjectedUserData ,
27+ MonthOption ,
2728 aggregateDataByDay ,
2829 parseCSV ,
2930 getModelUsageSummary ,
@@ -40,12 +41,18 @@ import {
4041 getTotalRequestsForUsersExceedingQuota ,
4142 getProjectedUsersExceedingQuota ,
4243 getProjectedUsersExceedingQuotaDetails ,
44+ getAvailableMonths ,
45+ filterDataByMonth ,
4346 EXCESS_REQUEST_COST
4447} from "@/lib/utils" ;
48+ import { MonthSelector } from "@/components/MonthSelector" ;
4549
4650function App ( ) {
4751 const [ showPrivacyBanner , setShowPrivacyBanner ] = useState ( true ) ;
4852 const [ data , setData ] = useState < CopilotUsageData [ ] | null > ( null ) ;
53+ const [ rawData , setRawData ] = useState < CopilotUsageData [ ] | null > ( null ) ; // Store original unfiltered data
54+ const [ availableMonths , setAvailableMonths ] = useState < MonthOption [ ] > ( [ ] ) ;
55+ const [ selectedMonth , setSelectedMonth ] = useState < string > ( '' ) ;
4956 const [ aggregatedData , setAggregatedData ] = useState < AggregatedData [ ] > ( [ ] ) ;
5057 const [ uniqueModels , setUniqueModels ] = useState < string [ ] > ( [ ] ) ;
5158 const [ modelSummary , setModelSummary ] = useState < ModelUsageSummary [ ] > ( [ ] ) ;
@@ -81,6 +88,67 @@ function App() {
8188 setProjectedUsersData ( projectedDetails ) ;
8289 }
8390 } , [ selectedPlan , data ] ) ;
91+
92+ // Reprocess data when month selection changes
93+ useEffect ( ( ) => {
94+ if ( rawData && selectedMonth ) {
95+ processDataForMonth ( rawData , selectedMonth ) ;
96+ }
97+ } , [ selectedMonth , rawData , selectedPlan ] ) ;
98+
99+ /**
100+ * Process data for a specific month and update all derived state
101+ * This function aggregates and processes data for the selected month only
102+ */
103+ const processDataForMonth = useCallback ( ( rawData : CopilotUsageData [ ] , month : string ) => {
104+ // Filter data by selected month
105+ const filteredData = filterDataByMonth ( rawData , month ) ;
106+ setData ( filteredData ) ;
107+
108+ // Get unique models from filtered data
109+ const models = Array . from ( new Set ( filteredData . map ( item => item . model ) ) ) ;
110+ setUniqueModels ( models ) ;
111+
112+ // Aggregate data by day and model for the selected month
113+ const aggregated = aggregateDataByDay ( filteredData ) ;
114+ setAggregatedData ( aggregated ) ;
115+
116+ // Get model usage summary for the selected month
117+ const summary = getModelUsageSummary ( filteredData ) ;
118+ setModelSummary ( summary ) ;
119+
120+ // Get daily model data for bar chart for the selected month
121+ const dailyData = getDailyModelData ( filteredData ) ;
122+ setDailyModelData ( dailyData ) ;
123+
124+ // Get power users data for the selected month
125+ const powerUsers = getPowerUsers ( filteredData ) ;
126+ setPowerUserSummary ( powerUsers ) ;
127+
128+ // Get power user daily breakdown for the stacked bar chart for the selected month
129+ const powerUserNames = powerUsers . powerUsers . map ( user => user . user ) ;
130+ const powerUserBreakdown = getPowerUserDailyBreakdown ( filteredData , powerUserNames ) ;
131+ setPowerUserDailyBreakdown ( powerUserBreakdown ) ;
132+
133+ // Get count of users exceeding quota for top bar display for the selected month
134+ const exceedingUsersCount = getUniqueUsersExceedingQuota ( filteredData , selectedPlan ) ;
135+ setUsersExceedingQuota ( exceedingUsersCount ) ;
136+
137+ // Get projected count of users who will exceed quota by month-end for the selected month
138+ const projectedExceedingUsersCount = getProjectedUsersExceedingQuota ( filteredData , selectedPlan ) ;
139+ setProjectedUsersExceedingQuota ( projectedExceedingUsersCount ) ;
140+
141+ // Get projected users details for the selected month
142+ const projectedDetails = getProjectedUsersExceedingQuotaDetails ( filteredData , selectedPlan ) ;
143+ setProjectedUsersData ( projectedDetails ) ;
144+
145+ // Get the last date available in the filtered CSV for the selected month
146+ const lastDate = getLastDateFromData ( filteredData ) ;
147+ setLastDateAvailable ( lastDate ) ;
148+
149+ // Reset selected power user when month changes
150+ setSelectedPowerUser ( null ) ;
151+ } , [ selectedPlan ] ) ;
84152
85153 const handlePowerUserSelect = useCallback ( ( userName : string | null ) => {
86154 setSelectedPowerUser ( userName ) ;
@@ -136,54 +204,25 @@ function App() {
136204 }
137205
138206 const parsedData = parseCSV ( csvContent ) ;
139- setData ( parsedData ) ;
140-
141- // Get unique models
142- const models = Array . from ( new Set ( parsedData . map ( item => item . model ) ) ) ;
143- setUniqueModels ( models ) ;
144-
145- // Aggregate data by day and model
146- const aggregated = aggregateDataByDay ( parsedData ) ;
147- setAggregatedData ( aggregated ) ;
148-
149- // Get model usage summary
150- const summary = getModelUsageSummary ( parsedData ) ;
151- setModelSummary ( summary ) ;
152-
153- // Get daily model data for bar chart
154- const dailyData = getDailyModelData ( parsedData ) ;
155- setDailyModelData ( dailyData ) ;
156-
157- // Get power users data
158- const powerUsers = getPowerUsers ( parsedData ) ;
159- setPowerUserSummary ( powerUsers ) ;
160-
161- // Get power user daily breakdown for the stacked bar chart
162- const powerUserNames = powerUsers . powerUsers . map ( user => user . user ) ;
163- const powerUserBreakdown = getPowerUserDailyBreakdown ( parsedData , powerUserNames ) ;
164- setPowerUserDailyBreakdown ( powerUserBreakdown ) ;
165207
166- // Get count of users exceeding quota for top bar display
167- const exceedingUsersCount = getUniqueUsersExceedingQuota ( parsedData , selectedPlan ) ;
168- setUsersExceedingQuota ( exceedingUsersCount ) ;
208+ // Store raw unfiltered data
209+ setRawData ( parsedData ) ;
169210
170- // Get projected count of users who will exceed quota by month-end
171- const projectedExceedingUsersCount = getProjectedUsersExceedingQuota ( parsedData , selectedPlan ) ;
172- setProjectedUsersExceedingQuota ( projectedExceedingUsersCount ) ;
211+ // Extract available months (current and previous month)
212+ const months = getAvailableMonths ( parsedData ) ;
213+ setAvailableMonths ( months ) ;
173214
174- // Get projected users details
175- const projectedDetails = getProjectedUsersExceedingQuotaDetails ( parsedData , selectedPlan ) ;
176- setProjectedUsersData ( projectedDetails ) ;
215+ // Auto-select current month if available, otherwise select the first (most recent) month
216+ const defaultMonth = months . find ( m => m . isCurrentMonth ) ?. value || months [ 0 ] ?. value || '' ;
217+ setSelectedMonth ( defaultMonth ) ;
177218
178- // Get the last date available in the CSV
179- const lastDate = getLastDateFromData ( parsedData ) ;
180- setLastDateAvailable ( lastDate ) ;
181-
182- // Reset selected power user when new data is loaded
183- setSelectedPowerUser ( null ) ;
219+ // Process data for the default selected month
220+ if ( defaultMonth ) {
221+ processDataForMonth ( parsedData , defaultMonth ) ;
222+ }
184223
185224 setIsProcessing ( false ) ;
186- toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
225+ toast . success ( `Loaded ${ parsedData . length . toLocaleString ( ) } records successfully` ) ;
187226 } catch ( error ) {
188227 // Provide user-friendly error messages
189228 let errorMessage = "Failed to parse CSV file. Please check the format." ;
@@ -217,6 +256,9 @@ function App() {
217256 setIsProcessing ( false ) ;
218257 toast . error ( errorMessage ) ;
219258 setData ( null ) ;
259+ setRawData ( null ) ;
260+ setAvailableMonths ( [ ] ) ;
261+ setSelectedMonth ( '' ) ;
220262 setAggregatedData ( [ ] ) ;
221263 setModelSummary ( [ ] ) ;
222264 setDailyModelData ( [ ] ) ;
@@ -229,7 +271,7 @@ function App() {
229271 } ;
230272
231273 reader . readAsText ( file ) ;
232- } , [ ] ) ;
274+ } , [ processDataForMonth ] ) ;
233275
234276 const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
235277 if ( isProcessing ) return ;
@@ -557,7 +599,20 @@ function App() {
557599 { data && data . length > 0 && (
558600 < div className = "space-y-8" >
559601 < div >
560- < h2 className = "text-2xl font-semibold mb-2" > Usage Statistics</ h2 >
602+ < div className = "flex items-center justify-between mb-4" >
603+ < div >
604+ < h2 className = "text-2xl font-semibold mb-2" > Usage Statistics</ h2 >
605+ </ div >
606+ < div className = "flex items-center gap-4" >
607+ < MonthSelector
608+ availableMonths = { availableMonths }
609+ selectedMonth = { selectedMonth }
610+ onMonthChange = { setSelectedMonth }
611+ disabled = { isProcessing }
612+ data = { rawData }
613+ />
614+ </ div >
615+ </ div >
561616 < Separator className = "mb-4" />
562617 < div className = "mb-4" >
563618 < Card >
@@ -588,37 +643,37 @@ function App() {
588643 </ span >
589644 </ div >
590645 < div
591- className = "inline-flex items-center gap-1.5 cursor-pointer hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-all duration-200 rounded-md px-2 py-1 group border border-orange-200 hover:border-orange-300 hover:shadow-sm bg-orange-25 dark:bg-orange-950/10 "
646+ className = "xebia-action-button "
592647 onClick = { ( ) => setShowProjectedUsersDialog ( true ) }
593648 title = "Click to see the users projected to exceed quota"
594649 >
595650 < span className = "text-sm text-muted-foreground" > Projected to Exceed by Month-End:</ span >
596- < span className = "text-lg font-bold text-orange-600 group-hover:text-orange-700 transition-colors " >
651+ < span className = "value " >
597652 { projectedUsersExceedingQuota . toLocaleString ( ) }
598653 </ span >
599- < ChevronRight className = "h-3 w-3 text-orange-600 opacity-60 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all duration-200 " />
654+ < ChevronRight className = "icon " />
600655 </ div >
601656 < div
602- className = "inline-flex items-center gap-1.5 cursor-pointer hover:bg-orange-50 dark:hover:bg-orange-900/20 transition-all duration-200 rounded-md px-2 py-1 group border border-orange-200 hover:border-orange-300 hover:shadow-sm bg-orange-25 dark:bg-orange-950/10 "
657+ className = "xebia-action-button "
603658 onClick = { ( ) => setShowPotentialCostDetails ( true ) }
604659 title = "Click to see cost breakdown"
605660 >
606661 < span className = "text-sm text-muted-foreground" > Potential Cost:</ span >
607- < span className = "text-lg font-bold text-orange-600 group-hover:text-orange-700 transition-colors " >
662+ < span className = "value " >
608663 ${ ( data . reduce ( ( sum , item ) => sum + item . requestsUsed , 0 ) * EXCESS_REQUEST_COST ) . toLocaleString ( undefined , { minimumFractionDigits : 2 , maximumFractionDigits : 2 } ) }
609664 </ span >
610- < ChevronRight className = "h-3 w-3 text-orange-600 opacity-60 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all duration-200 " />
665+ < ChevronRight className = "icon " />
611666 </ div >
612667 { powerUserSummary && (
613668 < Sheet >
614669 < SheetTrigger asChild >
615670 < div
616- className = "inline-flex items-center gap-1.5 cursor-pointer hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-all duration-200 rounded-md px-2 py-1 group border border-blue-200 hover:border-blue-300 hover:shadow-sm bg-blue-25 dark:bg-blue-950/10 "
671+ className = "xebia-action-button "
617672 title = "Click to view power users analysis"
618673 >
619674 < span className = "text-sm text-muted-foreground" > Power Users:</ span >
620- < span className = "text-lg font-bold text-blue-600 group-hover:text-blue-700 transition-colors " > { powerUserSummary . totalPowerUsers } </ span >
621- < ChevronRight className = "h-3 w-3 text-blue-600 opacity-60 group-hover:opacity-100 group-hover:translate-x-0.5 transition-all duration-200 " />
675+ < span className = "value " > { powerUserSummary . totalPowerUsers } </ span >
676+ < ChevronRight className = "icon " />
622677 </ div >
623678 </ SheetTrigger >
624679 < SheetContent side = "bottom" className = "h-[90vh] max-w-[90%] mx-auto overflow-y-auto" >
0 commit comments