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 ,
@@ -36,6 +36,7 @@ function App() {
3636 const [ powerUserSummary , setPowerUserSummary ] = useState < PowerUserSummary | null > ( null ) ;
3737 const [ isDragging , setIsDragging ] = useState ( false ) ;
3838 const [ selectedPlan , setSelectedPlan ] = useState < string > ( COPILOT_PLANS . BUSINESS ) ; // Default to Business
39+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
3940 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
4041
4142 const processFile = useCallback ( ( file : File ) => {
@@ -47,9 +48,12 @@ function App() {
4748 return ;
4849 }
4950
51+ setIsProcessing ( true ) ;
52+
5053 const reader = new FileReader ( ) ;
5154
5255 reader . onerror = ( ) => {
56+ setIsProcessing ( false ) ;
5357 toast . error ( "Failed to read the file. The file may be corrupted or unreadable." ) ;
5458 } ;
5559
@@ -91,6 +95,7 @@ function App() {
9195 const powerUsers = getPowerUsers ( parsedData ) ;
9296 setPowerUserSummary ( powerUsers ) ;
9397
98+ setIsProcessing ( false ) ;
9499 toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
95100 } catch ( error ) {
96101 // Provide user-friendly error messages
@@ -122,6 +127,7 @@ function App() {
122127 }
123128 }
124129
130+ setIsProcessing ( false ) ;
125131 toast . error ( errorMessage ) ;
126132 setData ( null ) ;
127133 setAggregatedData ( [ ] ) ;
@@ -135,6 +141,8 @@ function App() {
135141 } , [ ] ) ;
136142
137143 const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
144+ if ( isProcessing ) return ;
145+
138146 const file = event . target . files ?. [ 0 ] ;
139147 if ( file ) {
140148 processFile ( file ) ;
@@ -143,25 +151,29 @@ function App() {
143151 if ( event . target ) {
144152 event . target . value = '' ;
145153 }
146- } , [ processFile ] ) ;
154+ } , [ processFile , isProcessing ] ) ;
147155
148156 const handleButtonClick = ( ) => {
157+ if ( isProcessing ) return ;
149158 fileInputRef . current ?. click ( ) ;
150159 } ;
151160
152161 const handleDragOver = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
162+ if ( isProcessing ) return ;
153163 e . preventDefault ( ) ;
154164 e . stopPropagation ( ) ;
155165 setIsDragging ( true ) ;
156- } , [ ] ) ;
166+ } , [ isProcessing ] ) ;
157167
158168 const handleDragLeave = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
169+ if ( isProcessing ) return ;
159170 e . preventDefault ( ) ;
160171 e . stopPropagation ( ) ;
161172 setIsDragging ( false ) ;
162- } , [ ] ) ;
173+ } , [ isProcessing ] ) ;
163174
164175 const handleDrop = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
176+ if ( isProcessing ) return ;
165177 e . preventDefault ( ) ;
166178 e . stopPropagation ( ) ;
167179 setIsDragging ( false ) ;
@@ -175,7 +187,7 @@ function App() {
175187 toast . error ( "Please upload a CSV file. Supported formats: .csv files or text files with CSV content." ) ;
176188 }
177189 }
178- } , [ processFile ] ) ;
190+ } , [ processFile , isProcessing ] ) ;
179191
180192 // Generate chart data grouped by date with total compliant and exceeding requests
181193 const chartData = useCallback ( ( ) => {
@@ -320,24 +332,36 @@ function App() {
320332 { ! ( data && data . length > 0 ) && (
321333 < Card className = "mb-8" >
322334 < div
323- className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } transition-colors duration-200` }
335+ className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } ${ isProcessing ? 'opacity-50 pointer-events-none' : '' } transition-colors duration-200` }
324336 onDragOver = { handleDragOver }
325337 onDragLeave = { handleDragLeave }
326338 onDrop = { handleDrop }
327339 >
328340 < div className = "mb-4" >
329- < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
341+ { isProcessing ? (
342+ < CircleNotch size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground animate-spin" />
343+ ) : (
344+ < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
345+ ) }
330346 </ div >
331347
332- < h2 className = "text-xl font-medium mb-2" > Upload CSV File</ h2 >
348+ < h2 className = "text-xl font-medium mb-2" >
349+ { isProcessing ? "Processing CSV..." : "Upload CSV File" }
350+ </ h2 >
333351 < p className = "text-muted-foreground mb-4 max-w-md mx-auto" >
334- { isDragging
335- ? "Drop your file here..."
336- : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
352+ { isProcessing
353+ ? "Please wait while we process your file..."
354+ : isDragging
355+ ? "Drop your file here..."
356+ : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
337357 </ p >
338358
339- < Button onClick = { handleButtonClick } className = "cursor-pointer" >
340- Select CSV File
359+ < Button
360+ onClick = { handleButtonClick }
361+ className = "cursor-pointer"
362+ disabled = { isProcessing }
363+ >
364+ { isProcessing ? "Processing..." : "Select CSV File" }
341365 </ Button >
342366 < input
343367 ref = { fileInputRef }
@@ -346,6 +370,7 @@ function App() {
346370 accept = ".csv"
347371 onChange = { handleFileUpload }
348372 className = "hidden"
373+ disabled = { isProcessing }
349374 />
350375 </ div >
351376 </ Card >
@@ -586,7 +611,6 @@ function App() {
586611 </ Table >
587612 </ div >
588613 </ Card >
589- </ div >
590614 </ div >
591615
592616 < div >
0 commit comments