Files
vexplor_dev/frontend/components/common/PdfUpload.tsx
2026-04-15 11:46:05 +09:00

149 lines
5.4 KiB
TypeScript

"use client";
import React, { useState, useRef, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Upload, X, Loader2, FileText, ExternalLink } from "lucide-react";
import { cn } from "@/lib/utils";
import { apiClient } from "@/lib/api/client";
import { toast } from "sonner";
interface PdfUploadProps {
value?: string;
onChange?: (value: string) => void;
tableName?: string;
recordId?: string;
columnName?: string;
height?: string;
disabled?: boolean;
className?: string;
}
export function PdfUpload({
value, onChange, tableName, recordId, columnName,
height = "h-40", disabled = false, className,
}: PdfUploadProps) {
const [uploading, setUploading] = useState(false);
const [dragOver, setDragOver] = useState(false);
const fileRef = useRef<HTMLInputElement>(null);
const apiBase = typeof window !== "undefined"
? (process.env.NEXT_PUBLIC_API_URL || "").replace(/\/api\/?$/, "")
: "";
const pdfUrl = value
? (value.startsWith("http") || value.startsWith("/"))
? value
: `${apiBase}/api/files/preview/${value}`
: null;
const handleUpload = useCallback(async (file: File) => {
const isPdf = file.type === "application/pdf" || file.name.toLowerCase().endsWith(".pdf");
if (!isPdf) {
toast.error("PDF 파일만 업로드 가능합니다.");
return;
}
if (file.size > 20 * 1024 * 1024) {
toast.error("파일 크기는 20MB 이하만 가능합니다.");
return;
}
setUploading(true);
try {
const formData = new FormData();
formData.append("files", file);
formData.append("docType", "PDF");
formData.append("docTypeName", "도면PDF");
if (tableName) formData.append("linkedTable", tableName);
if (recordId) formData.append("recordId", recordId);
if (columnName) formData.append("columnName", columnName);
if (tableName && recordId) {
formData.append("autoLink", "true");
if (columnName) formData.append("isVirtualFileColumn", "true");
}
const res = await apiClient.post("/files/upload", formData, {
headers: { "Content-Type": "multipart/form-data" },
});
if (res.data?.success && (res.data.files?.length > 0 || res.data.data?.length > 0)) {
const uploaded = res.data.files?.[0] || res.data.data?.[0];
const objid = uploaded.objid;
onChange?.(objid);
toast.success("PDF가 업로드되었습니다.");
} else {
toast.error(res.data?.message || "업로드에 실패했습니다.");
}
} catch (err: any) {
console.error("PDF 업로드 실패:", err);
toast.error(err.response?.data?.message || "업로드에 실패했습니다.");
} finally {
setUploading(false);
}
}, [tableName, recordId, columnName, onChange]);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) handleUpload(file);
e.target.value = "";
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
const file = e.dataTransfer.files?.[0];
if (file) handleUpload(file);
};
const handleRemove = () => onChange?.("");
const handleOpen = (e: React.MouseEvent) => {
e.stopPropagation();
if (pdfUrl) window.open(pdfUrl, "_blank", "noopener,noreferrer");
};
return (
<div className={cn("relative", className)}>
<div
className={cn(
"border-2 border-dashed rounded-lg flex flex-col items-center justify-center cursor-pointer transition-colors overflow-hidden",
height,
dragOver ? "border-primary bg-primary/5" : pdfUrl ? "border-primary/40 bg-muted/20" : "border-muted-foreground/25 hover:border-primary hover:bg-muted/50",
disabled && "opacity-50 cursor-not-allowed",
)}
onClick={() => !disabled && !uploading && fileRef.current?.click()}
onDragOver={(e) => { e.preventDefault(); if (!disabled) setDragOver(true); }}
onDragLeave={() => setDragOver(false)}
onDrop={!disabled ? handleDrop : undefined}
>
{uploading ? (
<div className="flex flex-col items-center gap-2">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
<span className="text-xs text-muted-foreground"> ...</span>
</div>
) : pdfUrl ? (
<div className="flex flex-col items-center gap-2">
<FileText className="h-10 w-10 text-primary" />
<span className="text-xs text-foreground font-medium">PDF </span>
<Button variant="outline" size="sm" className="h-7 text-xs" onClick={handleOpen}>
<ExternalLink className="h-3 w-3 mr-1" />
</Button>
</div>
) : (
<div className="flex flex-col items-center gap-2">
<Upload className="h-8 w-8 text-muted-foreground/50" />
<span className="text-xs text-muted-foreground">PDF </span>
</div>
)}
</div>
{pdfUrl && !disabled && (
<Button variant="destructive" size="sm" className="absolute top-1 right-1 h-6 w-6 p-0 rounded-full"
onClick={(e) => { e.stopPropagation(); handleRemove(); }}>
<X className="h-3 w-3" />
</Button>
)}
<input ref={fileRef} type="file" accept="application/pdf,.pdf" onChange={handleFileChange} className="hidden" />
</div>
);
}