11<script setup lang="ts">
2- import { ref } from ' vue'
2+ import { ref , computed , onMounted , onUnmounted } from ' vue'
33import type { CsvData } from ' @/types'
44
55const emit = defineEmits <{
@@ -9,15 +9,42 @@ const emit = defineEmits<{
99
1010const fileInputRef = ref <HTMLInputElement >()
1111const uploadedFile = ref <CsvData | null >(null )
12+ const isOpen = ref (false )
13+ const isDragging = ref (false )
14+ const dropdownRef = ref <HTMLElement >()
15+
16+ const parseCsvInfo = (content : string ): { rows: number ; columns: number ; columnNames: string [] } => {
17+ const lines = content .trim ().split (' \n ' )
18+ const columnNames = lines [0 ].split (' ,' ).map (name => name .trim ().replace (/ ^ "| "$ / g , ' ' ))
19+ return {
20+ rows: lines .length - 1 , // Exclude header row
21+ columns: columnNames .length ,
22+ columnNames
23+ }
24+ }
1225
1326const handleDrop = (event : DragEvent ) => {
1427 event .preventDefault ()
28+ event .stopPropagation ()
29+ isDragging .value = false
1530 const files = event .dataTransfer ?.files
1631 if (files && files .length > 0 ) {
1732 processFile (files [0 ])
1833 }
1934}
2035
36+ const handleDragOver = (event : DragEvent ) => {
37+ event .preventDefault ()
38+ event .stopPropagation ()
39+ isDragging .value = true
40+ }
41+
42+ const handleDragLeave = (event : DragEvent ) => {
43+ event .preventDefault ()
44+ event .stopPropagation ()
45+ isDragging .value = false
46+ }
47+
2148const handleFileSelect = (event : Event ) => {
2249 const target = event .target as HTMLInputElement
2350 if (target .files && target .files .length > 0 ) {
@@ -34,9 +61,13 @@ const processFile = (file: File) => {
3461 const reader = new FileReader ()
3562 reader .onload = (e ) => {
3663 const content = e .target ?.result as string
64+ const { rows, columns, columnNames } = parseCsvInfo (content )
3765 const csvData: CsvData = {
3866 name: file .name ,
3967 content ,
68+ rows ,
69+ columns ,
70+ columnNames
4071 }
4172 uploadedFile .value = csvData
4273 emit (' fileUploaded' , csvData )
@@ -46,16 +77,45 @@ const processFile = (file: File) => {
4677
4778const removeFile = () => {
4879 uploadedFile .value = null
80+ isOpen .value = false
4981 if (fileInputRef .value ) {
5082 fileInputRef .value .value = ' '
5183 }
5284 emit (' fileRemoved' )
5385}
86+
87+ const toggleDropdown = () => {
88+ if (uploadedFile .value ) {
89+ isOpen .value = ! isOpen .value
90+ }
91+ }
92+
93+ const handleClickOutside = (event : MouseEvent ) => {
94+ if (dropdownRef .value && ! dropdownRef .value .contains (event .target as Node )) {
95+ isOpen .value = false
96+ }
97+ }
98+
99+ onMounted (() => {
100+ document .addEventListener (' click' , handleClickOutside )
101+ })
102+
103+ onUnmounted (() => {
104+ document .removeEventListener (' click' , handleClickOutside )
105+ })
54106 </script >
55107
56108<template >
57- <div class =" file-upload" >
58- <div v-if =" !uploadedFile" class =" upload-button" @click =" $refs.fileInputRef?.click()" >
109+ <div class =" file-upload" ref =" dropdownRef" >
110+ <div
111+ v-if =" !uploadedFile"
112+ class =" upload-button"
113+ :class =" { 'dragging': isDragging }"
114+ @click =" fileInputRef?.click()"
115+ @drop =" handleDrop"
116+ @dragover =" handleDragOver"
117+ @dragleave =" handleDragLeave"
118+ >
59119 <input
60120 ref =" fileInputRef"
61121 type =" file"
@@ -64,24 +124,70 @@ const removeFile = () => {
64124 class =" file-input"
65125 style =" display : none ;"
66126 />
67- 📁 Upload Data
127+ <span class =" upload-icon" >📁</span >
128+ <span class =" upload-text" >Upload CSV</span >
129+ <span class =" drag-hint" >or drop file here</span >
68130 </div >
69- <div v-else class =" uploaded-file" >
70- <span class =" file-name" >📄 {{ uploadedFile.name }}</span >
71- <button @click =" removeFile" class =" remove-btn" >×</button >
131+
132+ <div v-else class =" csv-info-container" >
133+ <button
134+ @click =" toggleDropdown"
135+ class =" csv-button"
136+ >
137+ <span class =" csv-icon" >📊</span >
138+ <span class =" csv-text" >{{ uploadedFile.name }} ({{ uploadedFile.rows }} × {{ uploadedFile.columns }})</span >
139+ <span class =" dropdown-arrow" :class =" { 'open': isOpen }" >▼</span >
140+ </button >
141+
142+ <div v-if =" isOpen" class =" csv-dropdown" >
143+ <div class =" csv-header" >
144+ <span class =" header-text" >CSV Information</span >
145+ <button @click =" removeFile" class =" remove-btn" title =" Remove file" >×</button >
146+ </div >
147+ <div class =" csv-details" >
148+ <div class =" detail-item" >
149+ <span class =" detail-label" >File:</span >
150+ <span class =" detail-value" >{{ uploadedFile.name }}</span >
151+ </div >
152+ <div class =" detail-item" >
153+ <span class =" detail-label" >Rows:</span >
154+ <span class =" detail-value" >{{ uploadedFile.rows.toLocaleString() }}</span >
155+ </div >
156+ <div class =" detail-item" >
157+ <span class =" detail-label" >Columns:</span >
158+ <span class =" detail-value" >{{ uploadedFile.columns }}</span >
159+ </div >
160+ <div class =" columns-section" >
161+ <span class =" columns-header" >Column Names:</span >
162+ <div class =" columns-list" >
163+ <span
164+ v-for =" (col, index) in uploadedFile.columnNames"
165+ :key =" index"
166+ class =" column-name"
167+ >
168+ {{ col }}
169+ </span >
170+ </div >
171+ </div >
172+ </div >
173+ </div >
72174 </div >
73175 </div >
74176</template >
75177
76178<style scoped>
77179.file-upload {
180+ position : relative ;
78181 display : flex ;
79182 align-items : center ;
80183}
81184
82185.upload-button {
186+ display : flex ;
187+ align-items : center ;
188+ gap : 0.5rem ;
83189 background : #f3f4f6 ;
84- border : 1 px solid #d1d5db ;
190+ border : 2 px dashed #d1d5db ;
85191 border-radius : 6px ;
86192 padding : 0.5rem 0.75rem ;
87193 font-size : 0.875rem ;
@@ -95,20 +201,93 @@ const removeFile = () => {
95201 border-color : #9ca3af ;
96202}
97203
98- .uploaded-file {
204+ .upload-button.dragging {
205+ background : #dbeafe ;
206+ border-color : #3b82f6 ;
207+ border-style : solid ;
208+ }
209+
210+ .upload-icon {
211+ font-size : 0.875rem ;
212+ }
213+
214+ .upload-text {
215+ font-weight : 500 ;
216+ }
217+
218+ .drag-hint {
219+ font-size : 0.75rem ;
220+ color : #6b7280 ;
221+ margin-left : 0.25rem ;
222+ }
223+
224+ .csv-info-container {
225+ position : relative ;
226+ }
227+
228+ .csv-button {
99229 display : flex ;
100230 align-items : center ;
101231 gap : 0.5rem ;
102- background : #ecfdf5 ;
103- border : 1px solid #d1fae5 ;
232+ background : #f3f4f6 ;
233+ border : 1px solid #d1d5db ;
104234 border-radius : 6px ;
105235 padding : 0.5rem 0.75rem ;
106236 font-size : 0.875rem ;
237+ cursor : pointer ;
238+ transition : all 0.3s ease ;
239+ white-space : nowrap ;
240+ }
241+
242+ .csv-button :hover {
243+ background : #e5e7eb ;
244+ border-color : #9ca3af ;
245+ }
246+
247+ .csv-icon {
248+ font-size : 0.875rem ;
107249}
108250
109- .file-name {
110- color : #065f46 ;
251+ .csv-text {
111252 font-weight : 500 ;
253+ color : #374151 ;
254+ }
255+
256+ .dropdown-arrow {
257+ font-size : 0.75rem ;
258+ transition : transform 0.3s ease ;
259+ margin-left : 0.25rem ;
260+ }
261+
262+ .dropdown-arrow.open {
263+ transform : rotate (180deg );
264+ }
265+
266+ .csv-dropdown {
267+ position : absolute ;
268+ top : calc (100% + 0.25rem );
269+ right : 0 ;
270+ background : white ;
271+ border : 1px solid #e5e7eb ;
272+ border-radius : 6px ;
273+ box-shadow : 0 4px 6px rgba (0 , 0 , 0 , 0.1 );
274+ z-index : 50 ;
275+ min-width : 280px ;
276+ }
277+
278+ .csv-header {
279+ display : flex ;
280+ align-items : center ;
281+ justify-content : space-between ;
282+ padding : 0.75rem ;
283+ border-bottom : 1px solid #e5e7eb ;
284+ background : #f9fafb ;
285+ }
286+
287+ .header-text {
288+ font-size : 0.875rem ;
289+ font-weight : 600 ;
290+ color : #374151 ;
112291}
113292
114293.remove-btn {
@@ -117,9 +296,9 @@ const removeFile = () => {
117296 color : #6b7280 ;
118297 cursor : pointer ;
119298 padding : 0 ;
120- font-size : 1 rem ;
121- width : 1.25 rem ;
122- height : 1.25 rem ;
299+ font-size : 1.25 rem ;
300+ width : 1.5 rem ;
301+ height : 1.5 rem ;
123302 display : flex ;
124303 align-items : center ;
125304 justify-content : center ;
@@ -129,6 +308,57 @@ const removeFile = () => {
129308
130309.remove-btn :hover {
131310 background : #f3f4f6 ;
311+ color : #ef4444 ;
312+ }
313+
314+ .csv-details {
315+ padding : 0.75rem ;
316+ }
317+
318+ .detail-item {
319+ display : flex ;
320+ align-items : center ;
321+ gap : 0.5rem ;
322+ padding : 0.25rem 0 ;
323+ font-size : 0.875rem ;
324+ }
325+
326+ .detail-label {
327+ font-weight : 500 ;
328+ color : #6b7280 ;
329+ min-width : 60px ;
330+ }
331+
332+ .detail-value {
333+ color : #374151 ;
334+ }
335+
336+ .columns-section {
337+ margin-top : 0.75rem ;
338+ padding-top : 0.75rem ;
339+ border-top : 1px solid #e5e7eb ;
340+ }
341+
342+ .columns-header {
343+ font-size : 0.875rem ;
344+ font-weight : 500 ;
345+ color : #6b7280 ;
346+ display : block ;
347+ margin-bottom : 0.5rem ;
348+ }
349+
350+ .columns-list {
351+ display : flex ;
352+ flex-wrap : wrap ;
353+ gap : 0.5rem ;
354+ }
355+
356+ .column-name {
357+ background : #f3f4f6 ;
358+ padding : 0.25rem 0.5rem ;
359+ border-radius : 4px ;
360+ font-size : 0.75rem ;
132361 color : #374151 ;
362+ font-family : monospace ;
133363}
134364 </style >
0 commit comments