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 ,
@@ -33,6 +33,7 @@ function App() {
3333 const [ dailyModelData , setDailyModelData ] = useState < DailyModelData [ ] > ( [ ] ) ;
3434 const [ powerUserSummary , setPowerUserSummary ] = useState < PowerUserSummary | null > ( null ) ;
3535 const [ isDragging , setIsDragging ] = useState ( false ) ;
36+ const [ isProcessing , setIsProcessing ] = useState ( false ) ;
3637 const fileInputRef = useRef < HTMLInputElement > ( null ) ;
3738
3839 const processFile = useCallback ( ( file : File ) => {
@@ -44,9 +45,12 @@ function App() {
4445 return ;
4546 }
4647
48+ setIsProcessing ( true ) ;
49+
4750 const reader = new FileReader ( ) ;
4851
4952 reader . onerror = ( ) => {
53+ setIsProcessing ( false ) ;
5054 toast . error ( "Failed to read the file. The file may be corrupted or unreadable." ) ;
5155 } ;
5256
@@ -88,6 +92,7 @@ function App() {
8892 const powerUsers = getPowerUsers ( parsedData ) ;
8993 setPowerUserSummary ( powerUsers ) ;
9094
95+ setIsProcessing ( false ) ;
9196 toast . success ( `Loaded ${ parsedData . length } records successfully` ) ;
9297 } catch ( error ) {
9398 // Provide user-friendly error messages
@@ -119,6 +124,7 @@ function App() {
119124 }
120125 }
121126
127+ setIsProcessing ( false ) ;
122128 toast . error ( errorMessage ) ;
123129 setData ( null ) ;
124130 setAggregatedData ( [ ] ) ;
@@ -132,6 +138,8 @@ function App() {
132138 } , [ ] ) ;
133139
134140 const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
141+ if ( isProcessing ) return ;
142+
135143 const file = event . target . files ?. [ 0 ] ;
136144 if ( file ) {
137145 processFile ( file ) ;
@@ -140,25 +148,29 @@ function App() {
140148 if ( event . target ) {
141149 event . target . value = '' ;
142150 }
143- } , [ processFile ] ) ;
151+ } , [ processFile , isProcessing ] ) ;
144152
145153 const handleButtonClick = ( ) => {
154+ if ( isProcessing ) return ;
146155 fileInputRef . current ?. click ( ) ;
147156 } ;
148157
149158 const handleDragOver = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
159+ if ( isProcessing ) return ;
150160 e . preventDefault ( ) ;
151161 e . stopPropagation ( ) ;
152162 setIsDragging ( true ) ;
153- } , [ ] ) ;
163+ } , [ isProcessing ] ) ;
154164
155165 const handleDragLeave = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
166+ if ( isProcessing ) return ;
156167 e . preventDefault ( ) ;
157168 e . stopPropagation ( ) ;
158169 setIsDragging ( false ) ;
159- } , [ ] ) ;
170+ } , [ isProcessing ] ) ;
160171
161172 const handleDrop = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
173+ if ( isProcessing ) return ;
162174 e . preventDefault ( ) ;
163175 e . stopPropagation ( ) ;
164176 setIsDragging ( false ) ;
@@ -172,7 +184,7 @@ function App() {
172184 toast . error ( "Please upload a CSV file. Supported formats: .csv files or text files with CSV content." ) ;
173185 }
174186 }
175- } , [ processFile ] ) ;
187+ } , [ processFile , isProcessing ] ) ;
176188
177189 // Generate chart data grouped by date with total compliant and exceeding requests
178190 const chartData = useCallback ( ( ) => {
@@ -296,24 +308,36 @@ function App() {
296308 { ! ( data && data . length > 0 ) && (
297309 < Card className = "mb-8" >
298310 < div
299- className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } transition-colors duration-200` }
311+ className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } ${ isProcessing ? 'opacity-50 pointer-events-none' : '' } transition-colors duration-200` }
300312 onDragOver = { handleDragOver }
301313 onDragLeave = { handleDragLeave }
302314 onDrop = { handleDrop }
303315 >
304316 < div className = "mb-4" >
305- < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
317+ { isProcessing ? (
318+ < CircleNotch size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground animate-spin" />
319+ ) : (
320+ < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
321+ ) }
306322 </ div >
307323
308- < h2 className = "text-xl font-medium mb-2" > Upload CSV File</ h2 >
324+ < h2 className = "text-xl font-medium mb-2" >
325+ { isProcessing ? "Processing CSV..." : "Upload CSV File" }
326+ </ h2 >
309327 < p className = "text-muted-foreground mb-4 max-w-md mx-auto" >
310- { isDragging
311- ? "Drop your file here..."
312- : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
328+ { isProcessing
329+ ? "Please wait while we process your file..."
330+ : isDragging
331+ ? "Drop your file here..."
332+ : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
313333 </ p >
314334
315- < Button onClick = { handleButtonClick } className = "cursor-pointer" >
316- Select CSV File
335+ < Button
336+ onClick = { handleButtonClick }
337+ className = "cursor-pointer"
338+ disabled = { isProcessing }
339+ >
340+ { isProcessing ? "Processing..." : "Select CSV File" }
317341 </ Button >
318342 < input
319343 ref = { fileInputRef }
@@ -322,6 +346,7 @@ function App() {
322346 accept = ".csv"
323347 onChange = { handleFileUpload }
324348 className = "hidden"
349+ disabled = { isProcessing }
325350 />
326351 </ div >
327352 </ Card >
0 commit comments