Spaces:
Running
Running
| import { useCallback } from 'react'; | |
| import { useDropzone } from 'react-dropzone'; | |
| import { Upload, FileImage, FileText, Loader2 } from 'lucide-react'; | |
| import { cn } from '../lib/utils'; | |
| import { isDicomFile } from '../lib/api'; | |
| interface FileUploadProps { | |
| onUpload: (file: File) => void; | |
| preview: string | null; | |
| currentFile: File | null; | |
| isLoading?: boolean; | |
| } | |
| export function FileUpload({ onUpload, preview, currentFile, isLoading = false }: FileUploadProps) { | |
| const onDrop = useCallback( | |
| (acceptedFiles: File[]) => { | |
| if (acceptedFiles.length > 0) { | |
| onUpload(acceptedFiles[0]); | |
| } | |
| }, | |
| [onUpload] | |
| ); | |
| const { getRootProps, getInputProps, isDragActive } = useDropzone({ | |
| onDrop, | |
| accept: { | |
| 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'], | |
| 'application/dicom': ['.dcm', '.dicom'], | |
| 'application/octet-stream': ['.dcm', '.dicom'], | |
| }, | |
| maxFiles: 1, | |
| }); | |
| const isDicom = currentFile ? isDicomFile(currentFile.name) : false; | |
| return ( | |
| <div className="h-full w-full flex flex-col"> | |
| <div | |
| {...getRootProps()} | |
| className={cn( | |
| 'flex-1 relative border-2 border-dashed rounded-xl transition-all duration-200 cursor-pointer overflow-hidden', | |
| 'hover:border-nvidia-green hover:bg-nvidia-green/5', | |
| isDragActive | |
| ? 'border-nvidia-green bg-nvidia-green/10' | |
| : 'border-dark-border bg-dark-input' | |
| )} | |
| > | |
| <input {...getInputProps()} /> | |
| {isLoading ? ( | |
| // Loading state for DICOM preview | |
| <div className="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-slate-900"> | |
| <Loader2 className="w-8 h-8 text-nvidia-green animate-spin" /> | |
| <p className="text-white text-sm">Loading DICOM preview...</p> | |
| </div> | |
| ) : preview ? ( | |
| // Show preview image - dark background for medical images | |
| <div className="absolute inset-0 flex items-center justify-center bg-slate-900 rounded-lg"> | |
| <img | |
| src={preview} | |
| alt="Preview" | |
| className="max-w-full max-h-full w-full h-full object-contain" | |
| /> | |
| <div className="absolute inset-0 bg-black/60 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center rounded-lg"> | |
| <p className="text-white text-sm font-medium">Click or drop to replace</p> | |
| </div> | |
| {/* File type badge */} | |
| <div className={cn( | |
| 'absolute top-3 right-3 px-2.5 py-1 rounded-full text-xs font-semibold shadow-lg', | |
| isDicom ? 'bg-nvidia-green text-white' : 'bg-accent-blue text-white' | |
| )}> | |
| {isDicom ? 'DICOM' : 'IMAGE'} | |
| </div> | |
| </div> | |
| ) : ( | |
| // Empty state / upload prompt | |
| <div className="absolute inset-0 flex flex-col items-center justify-center gap-4 p-4"> | |
| <div className="p-4 rounded-full bg-white border border-dark-border shadow-card"> | |
| <Upload className="w-8 h-8 text-text-muted" /> | |
| </div> | |
| <div className="text-center"> | |
| <p className="text-text-primary font-medium text-sm mb-1"> | |
| {isDragActive ? 'Drop file here' : 'Drop or click to upload'} | |
| </p> | |
| <p className="text-text-muted text-xs"> | |
| Supports DICOM and image files | |
| </p> | |
| </div> | |
| {/* Format hints */} | |
| <div className="flex gap-3 mt-2"> | |
| <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-nvidia-green/10 border border-nvidia-green/30"> | |
| <FileText className="w-3.5 h-3.5 text-nvidia-green" /> | |
| <span className="text-xs font-medium text-nvidia-green">DICOM</span> | |
| <span className="text-[10px] px-1.5 py-0.5 rounded-full bg-nvidia-green text-white">Best</span> | |
| </div> | |
| <div className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-accent-blue/10 border border-accent-blue/30"> | |
| <FileImage className="w-3.5 h-3.5 text-accent-blue" /> | |
| <span className="text-xs font-medium text-accent-blue">PNG/JPEG</span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |