File size: 4,663 Bytes
540f559
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import React, { useCallback, useState } from 'react';
import { UploadCloud, FileUp } from 'lucide-react';
import { FileItem, UploadStatus } from '../types';

const generateId = () => Math.random().toString(36).substring(2, 15);

interface FileUploaderProps {
  onFilesAdded: (files: FileItem[]) => void;
  disabled: boolean;
}

const sanitizeFileName = (fileName: string): string => {
  const timestamp = Date.now();
  const lastDotIndex = fileName.lastIndexOf('.');
  const name = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
  const ext = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : '';

  let cleanName = name;
  cleanName = cleanName.replace(/^\d+[-_.\s]*/, '');
  cleanName = cleanName.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
    .replace(/đ/g, 'd').replace(/Đ/g, 'D');
  cleanName = cleanName.replace(/[^a-zA-Z0-9]/g, '-');
  cleanName = cleanName.replace(/-+/g, '-').replace(/^-|-$/g, '');
  if (cleanName.length === 0) cleanName = 'file';

  return `${timestamp}-${cleanName}${ext}`.toLowerCase();
};

export const FileUploader: React.FC<FileUploaderProps> = ({ onFilesAdded, disabled }) => {
  const [isDragging, setIsDragging] = useState(false);

  const handleDragOver = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    if (!disabled) setIsDragging(true);
  }, [disabled]);

  const handleDragLeave = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    setIsDragging(false);
  }, []);

  const handleDrop = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    setIsDragging(false);
    if (disabled) return;

    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
      processFiles(e.dataTransfer.files);
    }
  }, [disabled]);

  const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files && e.target.files.length > 0) {
      processFiles(e.target.files);
      e.target.value = ''; // Reset input
    }
  };

  const processFiles = (fileList: FileList) => {
    const newFiles: FileItem[] = Array.from(fileList).map(file => {
      const cleanPath = sanitizeFileName(file.name);
      return {
        id: generateId(),
        file,
        path: cleanPath,
        status: UploadStatus.IDLE
      };
    });
    onFilesAdded(newFiles);
  };

  return (
    <div
      onDragOver={handleDragOver}
      onDragLeave={handleDragLeave}
      onDrop={handleDrop}
      className={`
        relative group border-2 border-dashed rounded-2xl p-10 text-center transition-all duration-300 ease-out overflow-hidden
        ${disabled ? 'opacity-60 cursor-not-allowed border-gray-200 bg-gray-50' : 'cursor-pointer'}
        ${isDragging 
            ? 'border-indigo-500 bg-indigo-50/50 scale-[1.01] shadow-lg' 
            : 'border-gray-200 hover:border-indigo-400 hover:bg-gray-50'}
      `}
    >
      <input
        type="file"
        multiple
        onChange={handleFileInput}
        disabled={disabled}
        className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed z-20"
      />
      
      {/* Decorative Background Glow */}
      <div className={`absolute inset-0 bg-gradient-to-tr from-indigo-100/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none`} />

      <div className="relative z-10 flex flex-col items-center justify-center space-y-4">
        <div className={`
            p-5 rounded-2xl shadow-sm transition-all duration-300
            ${isDragging ? 'bg-indigo-100 text-indigo-600 scale-110' : 'bg-white border border-gray-100 text-gray-400 group-hover:text-indigo-500 group-hover:scale-105 group-hover:shadow-md'}
        `}>
          <UploadCloud className="w-10 h-10" strokeWidth={1.5} />
        </div>
        
        <div>
          <p className="text-xl font-semibold text-gray-700 group-hover:text-indigo-900 transition-colors">
            {isDragging ? 'Drop files instantly' : 'Click or Drag files here'}
          </p>
          <p className="text-sm text-gray-400 mt-2 max-w-sm mx-auto">
            Supports images, JSON, CSV, and Parquet. 
            <span className="block mt-1 text-xs text-indigo-400 opacity-80">Files are auto-renamed with timestamps</span>
          </p>
        </div>

        <div className="pt-2">
            <span className="inline-flex items-center gap-1.5 px-3 py-1 rounded-md bg-gray-100 text-gray-500 text-xs font-medium group-hover:bg-indigo-50 group-hover:text-indigo-600 transition-colors">
                <FileUp className="w-3 h-3" />
                Bulk Upload Supported
            </span>
        </div>
      </div>
    </div>
  );
};