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; |