123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
|
|
import { TrashIcon } from "@heroicons/react/24/outline";
|
|||
|
|
import { useState, useRef, ChangeEvent } from "react";
|
|||
|
|
|
|||
|
|
interface FileUploaderProps {
|
|||
|
|
onFileSelected: (base64: string) => void;
|
|||
|
|
error?: string;
|
|||
|
|
defaultValue?: string;
|
|||
|
|
title?: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function FileUploader({
|
|||
|
|
onFileSelected,
|
|||
|
|
error,
|
|||
|
|
defaultValue,
|
|||
|
|
title = "سند",
|
|||
|
|
}: FileUploaderProps) {
|
|||
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|||
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|||
|
|
|
|||
|
|
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
|||
|
|
const file = e.target.files?.[0];
|
|||
|
|
if (!file) return;
|
|||
|
|
|
|||
|
|
setSelectedFile(file);
|
|||
|
|
|
|||
|
|
const base64 = await convertToBase64(file);
|
|||
|
|
onFileSelected(base64);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const convertToBase64 = (file: File): Promise<string> => {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
reader.onload = () => resolve(reader.result as string);
|
|||
|
|
reader.onerror = (error) => reject(error);
|
|||
|
|
});
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleRemoveFile = () => {
|
|||
|
|
setSelectedFile(null);
|
|||
|
|
if (fileInputRef.current) {
|
|||
|
|
fileInputRef.current.value = "";
|
|||
|
|
}
|
|||
|
|
onFileSelected("");
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const handleButtonClick = () => {
|
|||
|
|
fileInputRef.current?.click();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const formatFileSize = (size: number) => {
|
|||
|
|
const sizeInMB = size / (1024 * 1024);
|
|||
|
|
return sizeInMB.toFixed(2) + " MB";
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const getDefaultDocumentName = () => {
|
|||
|
|
const name = defaultValue?.split("/")[defaultValue?.split("/")?.length - 1];
|
|||
|
|
|
|||
|
|
if (name) {
|
|||
|
|
return name;
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<div className="flex items-center w-full gap-2 border border-gray1-200 dark:border-dark-400 rounded-lg">
|
|||
|
|
<input
|
|||
|
|
type="file"
|
|||
|
|
ref={fileInputRef}
|
|||
|
|
onChange={handleFileChange}
|
|||
|
|
className="hidden"
|
|||
|
|
accept="*/*"
|
|||
|
|
/>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={handleButtonClick}
|
|||
|
|
className={`flex justify-center cursor-pointer items-center px-4 py-2 rounded-lg transition-colors w-full ${
|
|||
|
|
selectedFile
|
|||
|
|
? "border-l border-gray1-200 dark:border-dark-400 bg-gray-50 dark:bg-dark-600 dark:text-dark-100 hover:bg-gray-100 dark:hover:bg-dark-600 text-gray-700"
|
|||
|
|
: "bg-white1-200 dark:bg-dark-500 dark:text-dark-100 text-gray-700 hover:bg-white1-300 dark:hover:bg-dark-600"
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
{selectedFile || getDefaultDocumentName() ? (
|
|||
|
|
<div className="flex items-center gap-2">
|
|||
|
|
<div className="flex justify-center">
|
|||
|
|
<span className=" text-[10px] w-10 bg-info-500 rounded-2xl text-white">
|
|||
|
|
{title}
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
{selectedFile ? (
|
|||
|
|
<span className="">
|
|||
|
|
{selectedFile.name?.slice(0, 15)}
|
|||
|
|
{selectedFile.name?.length > 15 ? "..." : ""} (
|
|||
|
|
{formatFileSize(selectedFile.size)})
|
|||
|
|
</span>
|
|||
|
|
) : (
|
|||
|
|
<span>{getDefaultDocumentName()}</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
) : (
|
|||
|
|
<span>انتخاب {title}</span>
|
|||
|
|
)}
|
|||
|
|
</button>
|
|||
|
|
{selectedFile && (
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={handleRemoveFile}
|
|||
|
|
className="p-2 text-red-400 rounded-lg transition-colors"
|
|||
|
|
aria-label="Remove file"
|
|||
|
|
>
|
|||
|
|
<TrashIcon className="h-5 w-5" />
|
|||
|
|
</button>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
{error && (
|
|||
|
|
<p className="text-xs text-red-500 dark:text-red-400">{error}</p>
|
|||
|
|
)}
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|