From 2eb717eb2bbfd97a5f149d13049aa336c26c393b Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 29 Oct 2025 07:43:44 +0000 Subject: (최겸) 구매 실사 개발(진행중) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../investigation/supplement-response-dialog.tsx | 483 +++++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 components/investigation/supplement-response-dialog.tsx (limited to 'components/investigation/supplement-response-dialog.tsx') diff --git a/components/investigation/supplement-response-dialog.tsx b/components/investigation/supplement-response-dialog.tsx new file mode 100644 index 00000000..8490c15c --- /dev/null +++ b/components/investigation/supplement-response-dialog.tsx @@ -0,0 +1,483 @@ +"use client" + +import * as React from "react" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { Badge } from "@/components/ui/badge" +import { useToast } from "@/hooks/use-toast" +import { + Dropzone, + DropzoneDescription, + DropzoneInput, + DropzoneTitle, + DropzoneUploadIcon, + DropzoneZone, +} from "@/components/ui/dropzone" +import { + FileList, + FileListAction, + FileListDescription, + FileListHeader, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, +} from "@/components/ui/file-list" +import { X, Download } from "lucide-react" +import prettyBytes from "pretty-bytes" +import { + submitSupplementDocumentResponseAction +} from "@/lib/vendor-investigation/service" +import { uploadVendorFileAction } from "@/lib/pq/service" + +interface SupplementResponseDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + investigationId: number + supplementType: "REINSPECT" | "DOCUMENT" + vendorName: string + requiredDocuments?: string[] + additionalRequests?: string +} + +interface LocalFileState { + fileObj: File + uploaded: boolean +} + +export function SupplementResponseDialog({ + open, + onOpenChange, + investigationId, + supplementType, + vendorName, + requiredDocuments = [], + additionalRequests = "" +}: SupplementResponseDialogProps) { + const { toast } = useToast() + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [isUploading, setIsUploading] = React.useState(false) + + // 서류제출 응답 데이터 + const [responseData, setResponseData] = React.useState({ + responseText: "", + uploadedFiles: [] as Array<{ + fileName: string + url: string + size?: number + }>, + newUploads: [] as LocalFileState[] + }) + + // 재실사 응답 데이터 + const [reinspectData, setReinspectData] = React.useState({ + inspectionDate: "", + inspectionDuration: 1.0, + inspectionResults: "", + additionalNotes: "" + }) + + const handleFileDrop = (files: File[]) => { + const newFiles: LocalFileState[] = files.map(file => ({ + fileObj: file, + uploaded: false + })) + + setResponseData(prev => ({ + ...prev, + newUploads: [...prev.newUploads, ...newFiles] + })) + } + + const removeNewUpload = (index: number) => { + setResponseData(prev => ({ + ...prev, + newUploads: prev.newUploads.filter((_, i) => i !== index) + })) + } + + const removeUploadedFile = (index: number) => { + setResponseData(prev => ({ + ...prev, + uploadedFiles: prev.uploadedFiles.filter((_, i) => i !== index) + })) + } + + const uploadFiles = async () => { + if (responseData.newUploads.length === 0) return + + setIsUploading(true) + try { + for (const localFile of responseData.newUploads) { + const uploadResult = await uploadVendorFileAction(localFile.fileObj) + setResponseData(prev => ({ + ...prev, + uploadedFiles: [...prev.uploadedFiles, { + fileName: uploadResult.fileName, + url: uploadResult.url, + size: uploadResult.size + }] + })) + } + + setResponseData(prev => ({ + ...prev, + newUploads: [] + })) + + toast({ + title: "파일 업로드 완료", + description: "파일이 성공적으로 업로드되었습니다.", + }) + } catch (error) { + console.error("파일 업로드 오류:", error) + toast({ + title: "업로드 실패", + description: "파일 업로드 중 오류가 발생했습니다.", + variant: "destructive" + }) + } finally { + setIsUploading(false) + } + } + + const handleSubmit = async () => { + if (supplementType === "DOCUMENT") { + // 서류제출 응답 검증 + if (!responseData.responseText.trim()) { + toast({ + title: "응답 필요", + description: "응답 내용을 입력해주세요.", + variant: "destructive" + }) + return + } + + if (responseData.uploadedFiles.length === 0 && responseData.newUploads.length > 0) { + toast({ + title: "파일 업로드 필요", + description: "새로 추가한 파일을 먼저 업로드해주세요.", + variant: "destructive" + }) + return + } + } else { + // 재실사 응답 검증 + if (!reinspectData.inspectionDate || !reinspectData.inspectionResults.trim()) { + toast({ + title: "필수 정보 필요", + description: "실사 일정과 결과를 입력해주세요.", + variant: "destructive" + }) + return + } + } + + try { + setIsSubmitting(true) + + if (supplementType === "DOCUMENT") { + const result = await submitSupplementDocumentResponseAction({ + investigationId, + responseData: { + responseText: responseData.responseText, + attachments: responseData.uploadedFiles.map(file => ({ + fileName: file.fileName, + url: file.url, + size: file.size + })) + } + }) + + if (result.success) { + toast({ + title: "서류제출 응답 완료", + description: "서류제출 응답이 성공적으로 제출되었습니다.", + }) + onOpenChange(false) + } else { + toast({ + title: "제출 실패", + description: result.error || "서류제출 응답 중 오류가 발생했습니다.", + variant: "destructive" + }) + } + } else { + // 재실사 응답은 별도 액션 필요 (구현 예정) + toast({ + title: "재실사 응답", + description: "재실사 응답 기능은 구현 예정입니다.", + }) + } + } catch (error) { + console.error("보완 응답 오류:", error) + toast({ + title: "제출 실패", + description: "보완 응답 중 오류가 발생했습니다.", + variant: "destructive" + }) + } finally { + setIsSubmitting(false) + } + } + + return ( + + + + + {supplementType === "REINSPECT" ? "보완-재실사 응답" : "보완-서류제출 응답"} + + + {vendorName}에 대한 보완 요청에 응답합니다. + + + +
+ {/* 서류제출 응답 폼 */} + {supplementType === "DOCUMENT" && ( + <> + {/* 요청된 서류 목록 */} + {requiredDocuments.length > 0 && ( +
+ +
+ {requiredDocuments.map((doc, index) => ( +
+ {index + 1} + {doc} +
+ ))} +
+
+ )} + + {/* 추가 요청사항 */} + {additionalRequests && ( +
+ +
+ {additionalRequests} +
+
+ )} + + {/* 응답 내용 */} +
+ +