1- import { useState , useCallback } from "react" ;
1+ import { useState , useCallback , useRef , DragEvent } from "react" ;
22import { Upload } from "@phosphor-icons/react" ;
33import { toast } from "sonner" ;
44import {
@@ -14,9 +14,10 @@ function App() {
1414 const [ data , setData ] = useState < CopilotUsageData [ ] | null > ( null ) ;
1515 const [ aggregatedData , setAggregatedData ] = useState < AggregatedData [ ] > ( [ ] ) ;
1616 const [ uniqueModels , setUniqueModels ] = useState < string [ ] > ( [ ] ) ;
17+ const [ isDragging , setIsDragging ] = useState ( false ) ;
18+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
1719
18- const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
19- const file = event . target . files ?. [ 0 ] ;
20+ const processFile = useCallback ( ( file : File ) => {
2021 if ( ! file ) return ;
2122
2223 const reader = new FileReader ( ) ;
@@ -48,6 +49,49 @@ function App() {
4849 reader . readAsText ( file ) ;
4950 } , [ ] ) ;
5051
52+ const handleFileUpload = useCallback ( ( event : React . ChangeEvent < HTMLInputElement > ) => {
53+ const file = event . target . files ?. [ 0 ] ;
54+ if ( file ) {
55+ processFile ( file ) ;
56+ }
57+ // Reset the input value to allow selecting the same file again
58+ if ( event . target ) {
59+ event . target . value = '' ;
60+ }
61+ } , [ processFile ] ) ;
62+
63+ const handleButtonClick = ( ) => {
64+ fileInputRef . current ?. click ( ) ;
65+ } ;
66+
67+ const handleDragOver = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
68+ e . preventDefault ( ) ;
69+ e . stopPropagation ( ) ;
70+ setIsDragging ( true ) ;
71+ } , [ ] ) ;
72+
73+ const handleDragLeave = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
74+ e . preventDefault ( ) ;
75+ e . stopPropagation ( ) ;
76+ setIsDragging ( false ) ;
77+ } , [ ] ) ;
78+
79+ const handleDrop = useCallback ( ( e : DragEvent < HTMLDivElement > ) => {
80+ e . preventDefault ( ) ;
81+ e . stopPropagation ( ) ;
82+ setIsDragging ( false ) ;
83+
84+ const files = e . dataTransfer ?. files ;
85+ if ( files && files . length > 0 ) {
86+ const file = files [ 0 ] ;
87+ if ( file . type === "text/csv" || file . name . endsWith ( '.csv' ) ) {
88+ processFile ( file ) ;
89+ } else {
90+ toast . error ( "Please upload a CSV file." ) ;
91+ }
92+ }
93+ } , [ processFile ] ) ;
94+
5195 // Generate chart data grouped by date with a stacked entry for each model
5296 const chartData = useCallback ( ( ) => {
5397 if ( ! aggregatedData . length ) return [ ] ;
@@ -83,28 +127,34 @@ function App() {
83127 </ header >
84128
85129 < Card className = "mb-8" >
86- < div className = "p-6 text-center" >
130+ < div
131+ className = { `p-6 text-center ${ isDragging ? 'bg-secondary/50' : '' } transition-colors duration-200` }
132+ onDragOver = { handleDragOver }
133+ onDragLeave = { handleDragLeave }
134+ onDrop = { handleDrop }
135+ >
87136 < div className = "mb-4" >
88137 < Upload size = { 48 } weight = "thin" className = "mx-auto text-muted-foreground" />
89138 </ div >
90139
91140 < h2 className = "text-xl font-medium mb-2" > Upload CSV File</ h2 >
92141 < p className = "text-muted-foreground mb-4 max-w-md mx-auto" >
93- Upload your GitHub Copilot premium requests usage CSV export to visualize the data
142+ { isDragging
143+ ? "Drop your file here..."
144+ : "Upload your GitHub Copilot premium requests usage CSV export to visualize the data. Drag and drop or select a file." }
94145 </ p >
95146
96- < label htmlFor = "csv-upload" >
97- < Button as = "div" className = "cursor-pointer" >
98- Select CSV File
99- </ Button >
100- < input
101- id = "csv-upload"
102- type = "file"
103- accept = ".csv"
104- onChange = { handleFileUpload }
105- className = "hidden"
106- />
107- </ label >
147+ < Button onClick = { handleButtonClick } className = "cursor-pointer" >
148+ Select CSV File
149+ </ Button >
150+ < input
151+ ref = { fileInputRef }
152+ id = "csv-upload"
153+ type = "file"
154+ accept = ".csv"
155+ onChange = { handleFileUpload }
156+ className = "hidden"
157+ />
108158 </ div >
109159 </ Card >
110160
0 commit comments