import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogDescription, DialogTitle, } from "@/Components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/Components/ui/alert-dialog"; import { Button } from "@/Components/ui/button"; import { FileText, Loader2, Trash2, Eye, Upload } from "lucide-react"; import { UtilityFee } from "./UtilityFeeDialog"; import { toast } from "sonner"; import axios from "axios"; interface Attachment { id: number; url: string; original_name: string; mime_type: string; size: number; } interface Props { open: boolean; onOpenChange: (open: boolean) => void; fee: UtilityFee | null; onAttachmentsChange?: () => void; } export default function AttachmentDialog({ open, onOpenChange, fee, onAttachmentsChange, }: Props) { const [attachments, setAttachments] = useState([]); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [deleteId, setDeleteId] = useState(null); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); useEffect(() => { if (open && fee) { fetchAttachments(); } else { setAttachments([]); } }, [open, fee]); const fetchAttachments = async () => { if (!fee) return; setLoading(true); try { const response = await axios.get(route("utility-fees.attachments", fee.id)); setAttachments(response.data.attachments || []); } catch (error) { toast.error("取得附件失敗"); } finally { setLoading(false); } }; const handleUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0 || !fee) return; const file = files[0]; // 驗證檔案大小 (2MB) if (file.size > 2 * 1024 * 1024) { toast.error("檔案大小不能超過 2MB"); return; } // 驗證檔案類型 const allowedTypes = ["image/jpeg", "image/png", "image/webp", "application/pdf"]; if (!allowedTypes.includes(file.type)) { toast.error("僅支援 JPG, PNG, WebP 圖片及 PDF 文件"); return; } // 驗證數量限制 (3張) if (attachments.length >= 3) { toast.error("最多僅可上傳 3 個附件"); return; } const formData = new FormData(); formData.append("file", file); setUploading(true); try { await axios.post(route("utility-fees.upload-attachment", fee.id), formData, { headers: { "Content-Type": "multipart/form-data" }, }); toast.success("上傳成功"); fetchAttachments(); onAttachmentsChange?.(); } catch (error: any) { const errorMsg = error.response?.data?.message || Object.values(error.response?.data?.errors || {})[0] || "上傳失敗"; toast.error(errorMsg as string); } finally { setUploading(false); // 清空 input 讓同一個檔案可以重複觸發 onChange e.target.value = ""; } }; const confirmDelete = (id: number) => { setDeleteId(id); setIsDeleteDialogOpen(true); }; const handleDelete = async () => { if (!deleteId || !fee) return; setIsDeleting(true); try { await axios.delete(route("utility-fees.delete-attachment", [fee.id, deleteId])); toast.success("附件已刪除"); setAttachments(attachments.filter((a) => a.id !== deleteId)); onAttachmentsChange?.(); } catch (error) { toast.error("刪除失敗"); } finally { setIsDeleting(false); setDeleteId(null); setIsDeleteDialogOpen(false); } }; const formatSize = (bytes: number) => { if (bytes === 0) return "0 B"; const k = 1024; const sizes = ["B", "KB", "MB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; return ( <> 附件管理 管理 {fee?.billing_month} {fee?.category_name} 的支援文件(對帳單、收據等)。 最多可上傳 3 個檔案,單一檔案上限 2MB。
{/* 上傳區塊 */}
{attachments.length < 3 ? `還可以上傳 ${3 - attachments.length} 個附件` : "已達到上傳數量上限"}
= 3} accept=".jpg,.jpeg,.png,.webp,.pdf" />
{/* 附件列表 */}
{loading ? (

載入中...

) : attachments.length === 0 ? (

目前尚無附件

) : (
{attachments.map((file) => (
{file.mime_type.startsWith("image/") ? ( {file.original_name} { (e.target as HTMLImageElement).src = ""; (e.target as HTMLImageElement).className = "hidden"; (e.target as HTMLImageElement).parentElement?.classList.add("bg-slate-200"); }} /> ) : ( )}

{file.original_name}

{formatSize(file.size)}

))}
)}
確認刪除附件? 這將永久刪除此附件,此操作無法撤銷。 取消 確認刪除 ); }