Spaces:
Sleeping
Sleeping
| import React, { useCallback, useState } from 'react'; | |
| import { CsvInputRow, REQUIRED_CSV_HEADERS } from '../types'; | |
| import { UploadIcon } from './Icons'; | |
| // This tells TypeScript that `Papa` is available on the global window object. | |
| declare const Papa: any; | |
| interface FileUploadProps { | |
| onFileParsed: (data: CsvInputRow[], file: File) => void; | |
| onParseError: (message: string) => void; | |
| disabled: boolean; | |
| } | |
| const FileUpload: React.FC<FileUploadProps> = ({ onFileParsed, onParseError, disabled }) => { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const handleFile = useCallback((file: File) => { | |
| if (!file) { | |
| onParseError('No file selected.'); | |
| return; | |
| } | |
| if (file.type !== 'text/csv') { | |
| onParseError('Invalid file type. Please upload a CSV file.'); | |
| return; | |
| } | |
| Papa.parse(file, { | |
| header: true, | |
| skipEmptyLines: true, | |
| complete: (results: any) => { | |
| if (results.errors.length > 0) { | |
| console.error('CSV Parsing Errors:', results.errors); | |
| onParseError(`Error parsing CSV: ${results.errors[0].message}`); | |
| return; | |
| } | |
| const headers = results.meta.fields; | |
| const missingHeaders = REQUIRED_CSV_HEADERS.filter( | |
| (requiredHeader) => !headers.includes(requiredHeader) | |
| ); | |
| if (missingHeaders.length > 0) { | |
| onParseError(`Missing required CSV columns: ${missingHeaders.join(', ')}`); | |
| return; | |
| } | |
| onFileParsed(results.data as CsvInputRow[], file); | |
| }, | |
| error: (error: any) => { | |
| console.error('PapaParse Error:', error); | |
| onParseError('An unexpected error occurred while parsing the file.'); | |
| }, | |
| }); | |
| }, [onFileParsed, onParseError]); | |
| const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (!disabled) setIsDragging(true); | |
| }; | |
| const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| setIsDragging(false); | |
| }; | |
| const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }; | |
| const handleDrop = (e: React.DragEvent<HTMLDivElement>) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| setIsDragging(false); | |
| if (disabled) return; | |
| if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { | |
| handleFile(e.dataTransfer.files[0]); | |
| e.dataTransfer.clearData(); | |
| } | |
| }; | |
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |
| if (e.target.files && e.target.files.length > 0) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }; | |
| const borderColor = isDragging ? 'border-blue-500' : 'border-gray-600'; | |
| const bgColor = isDragging ? 'bg-gray-700' : 'bg-gray-800'; | |
| return ( | |
| <div | |
| className={`relative p-8 border-2 ${borderColor} border-dashed rounded-xl text-center transition-all duration-300 ${bgColor} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`} | |
| onDragEnter={handleDragEnter} | |
| onDragLeave={handleDragLeave} | |
| onDragOver={handleDragOver} | |
| onDrop={handleDrop} | |
| > | |
| <input | |
| type="file" | |
| id="file-upload" | |
| className="hidden" | |
| accept=".csv" | |
| onChange={handleInputChange} | |
| disabled={disabled} | |
| /> | |
| <label htmlFor="file-upload" className={`${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`}> | |
| <div className="flex flex-col items-center"> | |
| <UploadIcon className="w-12 h-12 text-gray-400 mb-4" /> | |
| <p className="text-lg font-semibold text-white"> | |
| Drag & drop your CSV file here | |
| </p> | |
| <p className="text-gray-400">or click to browse</p> | |
| </div> | |
| </label> | |
| </div> | |
| ); | |
| }; | |
| export default FileUpload; |