From 4bad21ef79fdda5f016e2012ba673d6ee6abb5fc Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 28 May 2025 17:23:13 +0000 Subject: (대표님) lib 파트 개발 0528 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/investigation-table.tsx | 1 - .../table/update-investigation-sheet.tsx | 656 ++++++++++++--------- lib/vendor-investigation/validations.ts | 28 +- 3 files changed, 400 insertions(+), 285 deletions(-) (limited to 'lib/vendor-investigation') diff --git a/lib/vendor-investigation/table/investigation-table.tsx b/lib/vendor-investigation/table/investigation-table.tsx index 40b849fc..d5dc05ac 100644 --- a/lib/vendor-investigation/table/investigation-table.tsx +++ b/lib/vendor-investigation/table/investigation-table.tsx @@ -44,7 +44,6 @@ export function VendorsInvestigationTable({ promises }: VendorsTableProps) { }) }, [rawResponse.data]) - console.log(transformedData) const pageCount = rawResponse.pageCount diff --git a/lib/vendor-investigation/table/update-investigation-sheet.tsx b/lib/vendor-investigation/table/update-investigation-sheet.tsx index 69f0d9ae..29f0fa92 100644 --- a/lib/vendor-investigation/table/update-investigation-sheet.tsx +++ b/lib/vendor-investigation/table/update-investigation-sheet.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" -import { CalendarIcon, Loader } from "lucide-react" +import { CalendarIcon, Loader, X } from "lucide-react" import { format } from "date-fns" import { toast } from "sonner" @@ -49,6 +49,16 @@ import { DropzoneDescription, DropzoneInput } from "@/components/ui/dropzone" +import { + FileList, + FileListAction, + FileListHeader, + FileListIcon, + FileListInfo, + FileListItem, + FileListName, + FileListSize, +} from "@/components/ui/file-list" import { updateVendorInvestigationSchema, @@ -56,6 +66,7 @@ import { } from "../validations" import { updateVendorInvestigationAction, getInvestigationAttachments, deleteInvestigationAttachment } from "../service" import { VendorInvestigationsViewWithContacts } from "@/config/vendorInvestigationsColumnsConfig" +import prettyBytes from "pretty-bytes" interface UpdateVendorInvestigationSheetProps extends React.ComponentPropsWithoutRef { @@ -70,7 +81,7 @@ const getFileUploadConfig = (status: string) => { enabled: false, label: "", description: "", - accept: undefined, // undefined로 변경 + accept: undefined, maxSize: 0, maxSizeText: "" } @@ -122,7 +133,7 @@ export function UpdateVendorInvestigationSheet({ evaluationScore: investigation?.evaluationScore ?? undefined, evaluationResult: investigation?.evaluationResult ?? undefined, investigationNotes: investigation?.investigationNotes ?? "", - attachments: undefined, // 파일은 매번 새로 업로드 + attachments: undefined, }, }) @@ -142,9 +153,9 @@ export function UpdateVendorInvestigationSheet({ evaluationScore: investigation.evaluationScore ?? undefined, evaluationResult: investigation.evaluationResult ?? undefined, investigationNotes: investigation.investigationNotes ?? "", - attachments: undefined, // 파일은 매번 새로 업로드 + attachments: undefined, }) - + // 기존 첨부파일 로드 loadExistingAttachments(investigation.investigationId) } @@ -171,32 +182,46 @@ export function UpdateVendorInvestigationSheet({ // 첨부파일 삭제 함수 const handleDeleteAttachment = async (attachmentId: number) => { if (!investigation) return - + try { const response = await fetch(`/api/vendor-investigations/${investigation.investigationId}/attachments?attachmentId=${attachmentId}`, { method: "DELETE", }) - + if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || "첨부파일 삭제 실패") } - + toast.success("첨부파일이 삭제되었습니다.") // 목록 새로고침 loadExistingAttachments(investigation.investigationId) - + } catch (error) { console.error("첨부파일 삭제 오류:", error) toast.error(error instanceof Error ? error.message : "첨부파일 삭제 중 오류가 발생했습니다.") } } + // 선택된 파일에서 특정 파일 제거 + const handleRemoveSelectedFile = (indexToRemove: number) => { + const currentFiles = form.getValues("attachments") || [] + const updatedFiles = currentFiles.filter((_, index) => index !== indexToRemove) + form.setValue("attachments", updatedFiles.length > 0 ? updatedFiles : undefined) + + if (updatedFiles.length === 0) { + toast.success("모든 선택된 파일이 제거되었습니다.") + } else { + toast.success("파일이 제거되었습니다.") + } + } + // 파일 업로드 섹션 렌더링 const renderFileUploadSection = () => { const currentStatus = form.watch("investigationStatus") + const selectedFiles = form.watch("attachments") as File[] | undefined const config = getFileUploadConfig(currentStatus) - + if (!config.enabled) return null return ( @@ -269,10 +294,13 @@ export function UpdateVendorInvestigationSheet({ } }) } - + if (acceptedFiles.length > 0) { - onChange(acceptedFiles) - toast.success(`${acceptedFiles.length}개 파일이 선택되었습니다.`) + // 기존 파일들과 새로 선택된 파일들을 합치기 + const currentFiles = form.getValues("attachments") || [] + const newFiles = [...currentFiles, ...acceptedFiles] + onChange(newFiles) + toast.success(`${acceptedFiles.length}개 파일이 추가되었습니다.`) } }} accept={config.accept} @@ -283,8 +311,8 @@ export function UpdateVendorInvestigationSheet({ - {isPending || uploadingFiles - ? "파일 업로드 중..." + {isPending || uploadingFiles + ? "파일 업로드 중..." : "파일을 드래그하거나 클릭하여 업로드" } @@ -299,6 +327,50 @@ export function UpdateVendorInvestigationSheet({ )} /> + + {/* 선택된 파일 목록 */} + {selectedFiles && selectedFiles.length > 0 && ( +
+ {/* 선택된 파일 ({selectedFiles.length}개) */} + + + 업로드 예정 파일 ({selectedFiles.length}개) + + {selectedFiles.map((file, index) => ( + + {/* 왼쪽 아이콘 */} + + + {/* 가운데 이름 + 사이즈 */} + + {file.name} + + {file.size} + + + + {/* 오른쪽 삭제 버튼 */} + + + + + + ))} + +
+ )} ) } @@ -308,80 +380,87 @@ export function UpdateVendorInvestigationSheet({ const uploadPromises = files.map(async (file) => { const formData = new FormData() formData.append("file", file) - + const response = await fetch(`/api/vendor-investigations/${investigationId}/attachments`, { method: "POST", body: formData, }) - + if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || "파일 업로드 실패") } - + return await response.json() }) - + return await Promise.all(uploadPromises) } // Submit handler async function onSubmit(values: UpdateVendorInvestigationSchema) { - if (!values.investigationId) return + console.log("onSubmit 호출됨:", values) + + if (!values.investigationId) { + console.log("investigationId가 없음:", values.investigationId) + return + } startTransition(async () => { try { + console.log("startTransition 시작") + // 1) 먼저 텍스트 데이터 업데이트 const formData = new FormData() // 필수 필드 formData.append("investigationId", String(values.investigationId)) formData.append("investigationStatus", values.investigationStatus) - + // 선택적 필드들 if (values.evaluationType) { formData.append("evaluationType", values.evaluationType) } - + if (values.investigationAddress) { formData.append("investigationAddress", values.investigationAddress) } - + if (values.investigationMethod) { formData.append("investigationMethod", values.investigationMethod) } - + if (values.forecastedAt) { formData.append("forecastedAt", values.forecastedAt.toISOString()) } - + if (values.requestedAt) { formData.append("requestedAt", values.requestedAt.toISOString()) } - + if (values.confirmedAt) { formData.append("confirmedAt", values.confirmedAt.toISOString()) } - + if (values.completedAt) { formData.append("completedAt", values.completedAt.toISOString()) } - + if (values.evaluationScore !== undefined) { formData.append("evaluationScore", String(values.evaluationScore)) } - + if (values.evaluationResult) { formData.append("evaluationResult", values.evaluationResult) } - + if (values.investigationNotes) { formData.append("investigationNotes", values.investigationNotes) } // 텍스트 데이터 업데이트 const { error } = await updateVendorInvestigationAction(formData) - + if (error) { toast.error(error) return @@ -390,14 +469,15 @@ export function UpdateVendorInvestigationSheet({ // 2) 파일이 있으면 업로드 if (values.attachments && values.attachments.length > 0) { setUploadingFiles(true) - + try { await uploadFiles(values.attachments, values.investigationId) toast.success(`실사 정보와 ${values.attachments.length}개 파일이 업데이트되었습니다!`) - + // 첨부파일 목록 새로고침 loadExistingAttachments(values.investigationId) } catch (fileError) { + console.error("파일 업로드 에러:", fileError) toast.error(`데이터는 저장되었지만 파일 업로드 중 오류가 발생했습니다: ${fileError}`) } finally { setUploadingFiles(false) @@ -408,7 +488,7 @@ export function UpdateVendorInvestigationSheet({ form.reset() props.onOpenChange?.(false) - + } catch (error) { console.error("실사 정보 업데이트 오류:", error) toast.error("실사 정보 업데이트 중 오류가 발생했습니다.") @@ -416,9 +496,26 @@ export function UpdateVendorInvestigationSheet({ }) } + // 디버깅을 위한 버튼 클릭 핸들러 + const handleSaveClick = async () => { + console.log("저장 버튼 클릭됨") + console.log("현재 폼 값:", form.getValues()) + console.log("폼 에러:", form.formState.errors) + + // 폼 검증 실행 + const isValid = await form.trigger() + console.log("폼 검증 결과:", isValid) + + if (isValid) { + form.handleSubmit(onSubmit)() + } else { + console.log("폼 검증 실패, 에러:", form.formState.errors) + } + } + return ( - + 실사 업데이트 @@ -433,250 +530,51 @@ export function UpdateVendorInvestigationSheet({
- {/* 실사 상태 */} - ( - - 실사 상태 - - - - - - )} - /> - - {/* 평가 유형 */} - ( - - 평가 유형 - - - - - - )} - /> - - {/* 실사 주소 */} - ( - - 실사 주소 - -