141 lines
4.8 KiB
TypeScript
141 lines
4.8 KiB
TypeScript
// components/file-upload.tsx (atau lokasi file Anda)
|
|
"use client";
|
|
|
|
import { UploadCloud, X, RefreshCcw, File as FileIcon } from "lucide-react";
|
|
import { useCallback, useEffect, useState } from "react"; // Tambah useEffect
|
|
import { useDropzone } from "react-dropzone";
|
|
import { Button } from "@/shared/components/ui/button"; // Sesuaikan path
|
|
|
|
interface FileUploadProps {
|
|
value?: string;
|
|
fileName?: string;
|
|
// onChange existing ini sepertinya untuk hasil upload (ID), kita biarkan opsional
|
|
onChange?: (value: { id: string; name: string }) => void;
|
|
// 🔥 TAMBAHAN: Callback untuk mengirim Raw File ke Controller
|
|
onFileSelect?: (file: File) => void;
|
|
onRemove: () => void;
|
|
description?: string;
|
|
// Prop tambahan untuk mengontrol dari luar (Controlled Component)
|
|
filePreview?: File | null;
|
|
}
|
|
|
|
export function FileUpload({
|
|
value,
|
|
fileName,
|
|
onChange,
|
|
onFileSelect, // ⬅️ Gunakan ini
|
|
onRemove,
|
|
description,
|
|
filePreview // ⬅️ Gunakan ini jika ingin state dikontrol parent sepenuhnya
|
|
}: FileUploadProps) {
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
const [internalFile, setInternalFile] = useState<File | null>(null);
|
|
|
|
// Sinkronisasi jika parent mengirim file (opsional, agar stateless)
|
|
const selectedFile = filePreview || internalFile;
|
|
|
|
const onDrop = useCallback((acceptedFiles: File[]) => {
|
|
const file = acceptedFiles[0];
|
|
if (!file) return;
|
|
|
|
setInternalFile(file); // Set local state untuk UI
|
|
|
|
// 🔥 Kirim file mentah ke parent hook
|
|
if (onFileSelect) {
|
|
onFileSelect(file);
|
|
}
|
|
|
|
}, [onFileSelect]);
|
|
|
|
const handleRemove = (e: React.MouseEvent) => {
|
|
e.stopPropagation();
|
|
setInternalFile(null);
|
|
onRemove();
|
|
};
|
|
|
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
onDrop,
|
|
accept: {
|
|
"text/csv": [".csv"],
|
|
"application/vnd.ms-excel": [".xls"],
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
|
|
".xlsx",
|
|
],
|
|
"application/pdf": [".pdf"],
|
|
"application/zip": [".zip"],
|
|
},
|
|
maxFiles: 1,
|
|
maxSize: 100 * 1024 * 1024,
|
|
disabled: isUploading,
|
|
});
|
|
|
|
// Tentukan apakah file sudah ada (baik dari value/server atau lokal/selected)
|
|
const hasFile = selectedFile || value;
|
|
|
|
return (
|
|
<div
|
|
{...getRootProps()}
|
|
className={`flex flex-col justify-center rounded-lg border-2 border-dashed p-4 transition-colors cursor-pointer w-full
|
|
${isDragActive ? "border-primary bg-primary/5" : "border-input"}
|
|
${isUploading ? "pointer-events-none opacity-50" : ""}
|
|
${hasFile ? "bg-slate-50 border-solid" : "hover:bg-slate-50"}
|
|
`}
|
|
>
|
|
<input {...getInputProps()} />
|
|
|
|
{/* STATE: BELUM ADA FILE */}
|
|
{!hasFile && (
|
|
<div className="flex flex-col items-center py-4">
|
|
<UploadCloud className="mb-3 h-10 w-10 text-muted-foreground" />
|
|
<p className="text-sm text-muted-foreground text-center font-medium">
|
|
Drag & drop file, atau klik untuk pilih
|
|
</p>
|
|
<p className="mt-1 text-xs text-muted-foreground text-center">
|
|
{/* CSV, XLS, XLSX, PDF, ZIP — Maks 100MB */}
|
|
.xlsx, .csv, .pdf, .zip (SHP/GDB) - Maks 100MB
|
|
</p>
|
|
{description && (
|
|
<p className="mt-2 text-xs text-slate-400 italic">{description}</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* STATE: FILE SUDAH DIPILIH */}
|
|
{hasFile && (
|
|
<div className="flex w-full items-center justify-between gap-4 p-2">
|
|
<div className="flex items-center gap-3 overflow-hidden">
|
|
<div className="p-2 bg-white rounded-md border shadow-sm">
|
|
<FileIcon className={`h-6 w-6 ${selectedFile?.name.endsWith('.pdf') ? 'text-red-500' : 'text-green-500'}`} />
|
|
</div>
|
|
<div className="overflow-hidden flex flex-col">
|
|
<p className="truncate text-sm font-medium text-slate-900">
|
|
{selectedFile?.name || fileName}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
|
{selectedFile ? (
|
|
<>
|
|
<span>{(selectedFile.size / 1024 / 1024).toFixed(2)} MB</span>
|
|
<span className="text-slate-300">•</span>
|
|
<span className="text-amber-600">Siap diproses</span>
|
|
</>
|
|
) : "File berhasil diunggah"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2 shrink-0">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
className="text-red-600 hover:text-red-700 hover:bg-red-50 border-red-200"
|
|
onClick={handleRemove}
|
|
>
|
|
<X className="w-4 h-4 mr-1"/> Hapus
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |