| import React, { useCallback, useState } from 'react'; |
| import { CsvInputRow, REQUIRED_CSV_HEADERS } from '../types'; |
| import { UploadIcon } from './Icons'; |
|
|
| |
| 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; |