11import { useState , useCallback , useRef , DragEvent } from "react" ;
2- import { Upload , GithubLogo } from "@phosphor-icons/react" ;
2+ import { Upload , GithubLogo , CircleNotch } from "@phosphor-icons/react" ;
33import { toast , Toaster } from "sonner" ;
44import {
55 LineChart , Line , XAxis , YAxis , CartesianGrid , Tooltip , Legend , ResponsiveContainer ,
@@ -34,6 +34,7 @@ function App() {
3434 const [ dailyModelData , setDailyModelData ] = useState < DailyModelData [ ] > ( [ ] ) ;
3535 const [ powerUserSummary , setPowerUserSummary ] = useState < PowerUserSummary | null > ( null ) ;
3636 const [ isDragging , setIsDragging ] = useState ( false ) ;
37+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
3738 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
3839
3940 const processFile = useCallback ( ( file : File ) => {
@@ -45,9 +46,12 @@ function App() {
4546 return ;
4647 }
4748
49+ setIsProcessing ( true ) ;
50+
4851 const reader = new FileReader ( ) ;
4952
5053 reader . onerror = ( ) => {
54+ setIsProcessing ( false ) ;
5155 toast . error ( "Failed to read the file. The file may be corrupted or unreadable." ) ;
5256 } ;
5357
@@ -89,6 +93,7 @@ function App() {
8993 const powerUsers = getPowerUsers ( parsedData ) ;
9094 setPowerUserSummary ( powerUsers ) ;
9195
96+ setIsProcessing ( false ) ;
9297 toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
9398 } catch ( error ) {
9499 // Provide user-friendly error messages
@@ -120,6 +125,7 @@ function App() {
120125 }
121126 }
122127
128+ setIsProcessing ( false ) ;
123129 toast . error ( errorMessage ) ;
124130 setData ( null ) ;
125131 setAggregatedData ( [ ] ) ;
@@ -133,6 +139,8 @@ function App() {
133139 } , [ ] ) ;
134140
135141 const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
142+ if ( isProcessing ) return ;
143+
136144 const file = event . target . files ?. [ 0 ] ;
137145 if ( file ) {
138146 processFile ( file ) ;
@@ -141,25 +149,29 @@ function App() {
141149 if ( event . target ) {
142150 event . target . value = '' ;
143151 }
144- } , [ processFile ] ) ;
152+ } , [ processFile , isProcessing ] ) ;
145153
146154 const handleButtonClick = ( ) => {
155+ if ( isProcessing ) return ;
147156 fileInputRef . current ?. click ( ) ;
148157 } ;
149158
150159 const handleDragOver = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
160+ if ( isProcessing ) return ;
151161 e . preventDefault ( ) ;
152162 e . stopPropagation ( ) ;
153163 setIsDragging ( true ) ;
154- } , [ ] ) ;
164+ } , [ isProcessing ] ) ;
155165
156166 const handleDragLeave = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
167+ if ( isProcessing ) return ;
157168 e . preventDefault ( ) ;
158169 e . stopPropagation ( ) ;
159170 setIsDragging ( false ) ;
160- } , [ ] ) ;
171+ } , [ isProcessing ] ) ;
161172
162173 const handleDrop = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
174+ if ( isProcessing ) return ;
163175 e . preventDefault ( ) ;
164176 e . stopPropagation ( ) ;
165177 setIsDragging ( false ) ;
@@ -173,7 +185,7 @@ function App() {
173185 toast . error ( "Please upload a CSV file. Supported formats: .csv files or text files with CSV content." ) ;
174186 }
175187 }
176- } , [ processFile ] ) ;
188+ } , [ processFile , isProcessing ] ) ;
177189
178190 // Generate chart data grouped by date with total compliant and exceeding requests
179191 const chartData = useCallback ( ( ) => {
@@ -297,24 +309,36 @@ function App() {
297309 { ! ( data && data . length > 0 ) && (
298310 < Card className = "mb-8" >
299311 < div
300- className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } transition-colors duration-200` }
312+ className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } ${ isProcessing ? 'opacity-50 pointer-events-none' : '' } transition-colors duration-200` }
301313 onDragOver = { handleDragOver }
302314 onDragLeave = { handleDragLeave }
303315 onDrop = { handleDrop }
304316 >
305317 < div className = "mb-4" >
306- < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
318+ { isProcessing ? (
319+ < CircleNotch size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground animate-spin" />
320+ ) : (
321+ < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
322+ ) }
307323 </ div >
308324
309- < h2 className = "text-xl font-medium mb-2" > Upload CSV File</ h2 >
325+ < h2 className = "text-xl font-medium mb-2" >
326+ { isProcessing ? "Processing CSV..." : "Upload CSV File" }
327+ </ h2 >
310328 < p className = "text-muted-foreground mb-4 max-w-md mx-auto" >
311- { isDragging
312- ? "Drop your file here..."
313- : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
329+ { isProcessing
330+ ? "Please wait while we process your file..."
331+ : isDragging
332+ ? "Drop your file here..."
333+ : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
314334 </ p >
315335
316- < Button onClick = { handleButtonClick } className = "cursor-pointer" >
317- Select CSV File
336+ < Button
337+ onClick = { handleButtonClick }
338+ className = "cursor-pointer"
339+ disabled = { isProcessing }
340+ >
341+ { isProcessing ? "Processing..." : "Select CSV File" }
318342 </ Button >
319343 < input
320344 ref = { fileInputRef }
@@ -323,6 +347,7 @@ function App() {
323347 accept = ".csv"
324348 onChange = { handleFileUpload }
325349 className = "hidden"
350+ disabled = { isProcessing }
326351 />
327352 </ div >
328353 </ Card >
@@ -501,7 +526,7 @@ function App() {
501526 </ div >
502527
503528 { /* Model Usage Table */ }
504- < div className = "grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6" >
529+ < div className = "mb-6" >
505530 < Card className = "p-5" >
506531 < h3 className = "text-md font-medium mb-3" > Requests per Model</ h3 >
507532 < div className = "overflow-auto max-h-60" >
@@ -527,26 +552,6 @@ function App() {
527552 </ Table >
528553 </ div >
529554 </ Card >
530-
531- < Card className = "p-5" >
532- < h3 className = "text-md font-medium mb-3" > Models List</ h3 >
533- < div className = "overflow-auto max-h-60" >
534- < Table >
535- < TableHeader >
536- < TableRow >
537- < TableHead > Model Name</ TableHead >
538- </ TableRow >
539- </ TableHeader >
540- < TableBody >
541- { uniqueModels . map ( ( model ) => (
542- < TableRow key = { model } >
543- < TableCell > { model } </ TableCell >
544- </ TableRow >
545- ) ) }
546- </ TableBody >
547- </ Table >
548- </ div >
549- </ Card >
550555 </ div >
551556 </ div >
552557
0 commit comments