satupeta-main/shared/components/file-upload.tsx

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>
);
}