diff options
Diffstat (limited to 'lib/vendor-investigation')
| -rw-r--r-- | lib/vendor-investigation/table/investigation-table.tsx | 1 | ||||
| -rw-r--r-- | lib/vendor-investigation/table/update-investigation-sheet.tsx | 656 | ||||
| -rw-r--r-- | lib/vendor-investigation/validations.ts | 28 |
3 files changed, 400 insertions, 285 deletions
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<typeof Sheet> { @@ -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({ <DropzoneZone> <DropzoneUploadIcon /> <DropzoneTitle> - {isPending || uploadingFiles - ? "파일 업로드 중..." + {isPending || uploadingFiles + ? "파일 업로드 중..." : "파일을 드래그하거나 클릭하여 업로드" } </DropzoneTitle> @@ -299,6 +327,50 @@ export function UpdateVendorInvestigationSheet({ </FormItem> )} /> + + {/* 선택된 파일 목록 */} + {selectedFiles && selectedFiles.length > 0 && ( + <div className="space-y-2"> + {/* <FormLabel>선택된 파일 ({selectedFiles.length}개)</FormLabel> */} + <FileList> + <FileListHeader> + <span className="text-sm font-medium">업로드 예정 파일 ({selectedFiles.length}개)</span> + </FileListHeader> + {selectedFiles.map((file, index) => ( + <FileListItem + key={`${file.name}-${index}`} + className="flex items-center justify-between gap-2 px-2 py-2" + > + {/* 왼쪽 아이콘 */} + <FileListIcon className="shrink-0 h-4 w-4 text-muted-foreground" /> + + {/* 가운데 이름 + 사이즈 */} + <FileListInfo className="flex-1 min-w-0"> + <FileListName className="truncate">{file.name}</FileListName> + <FileListSize className="text-xs text-muted-foreground shrink-0"> + {file.size} + </FileListSize> + </FileListInfo> + + {/* 오른쪽 삭제 버튼 */} + <FileListAction className="shrink-0"> + <Button + type="button" + variant="ghost" + size="icon" + onClick={() => handleRemoveSelectedFile(index)} + disabled={isPending || uploadingFiles} + className="h-5 w-5 text-destructive hover:text-destructive" + > + <X className="h-4 w-4" /> + </Button> + </FileListAction> + </FileListItem> + + ))} + </FileList> + </div> + )} </> ) } @@ -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 ( <Sheet {...props}> - <SheetContent className="flex flex-col h-full sm:max-w-md"> + <SheetContent className="flex flex-col h-full sm:max-w-xl" > <SheetHeader className="text-left flex-shrink-0"> <SheetTitle>실사 업데이트</SheetTitle> <SheetDescription> @@ -433,250 +530,51 @@ export function UpdateVendorInvestigationSheet({ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4" + id="update-investigation-form" > - {/* 실사 상태 */} - <FormField - control={form.control} - name="investigationStatus" - render={({ field }) => ( - <FormItem> - <FormLabel>실사 상태</FormLabel> - <FormControl> - <Select value={field.value} onValueChange={field.onChange}> - <SelectTrigger> - <SelectValue placeholder="상태를 선택하세요" /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - <SelectItem value="PLANNED">계획됨</SelectItem> - <SelectItem value="IN_PROGRESS">진행 중</SelectItem> - <SelectItem value="COMPLETED">완료됨</SelectItem> - <SelectItem value="CANCELED">취소됨</SelectItem> - </SelectGroup> - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 평가 유형 */} - <FormField - control={form.control} - name="evaluationType" - render={({ field }) => ( - <FormItem> - <FormLabel>평가 유형</FormLabel> - <FormControl> - <Select value={field.value || ""} onValueChange={field.onChange}> - <SelectTrigger> - <SelectValue placeholder="평가 유형을 선택하세요" /> - </SelectTrigger> - <SelectContent> - <SelectGroup> - <SelectItem value="SITE_AUDIT">실사의뢰평가</SelectItem> - <SelectItem value="QM_SELF_AUDIT">QM자체평가</SelectItem> - </SelectGroup> - </SelectContent> - </Select> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 실사 주소 */} - <FormField - control={form.control} - name="investigationAddress" - render={({ field }) => ( - <FormItem> - <FormLabel>실사 주소</FormLabel> - <FormControl> - <Textarea - placeholder="실사가 진행될 주소를 입력하세요..." - {...field} - className="min-h-[60px]" - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 실사 방법 */} - <FormField - control={form.control} - name="investigationMethod" - render={({ field }) => ( - <FormItem> - <FormLabel>실사 방법</FormLabel> - <FormControl> - <Input placeholder="실사 방법을 입력하세요..." {...field} /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 실사 예정일 */} - <FormField - control={form.control} - name="forecastedAt" - render={({ field }) => ( - <FormItem className="flex flex-col"> - <FormLabel>실사 예정일</FormLabel> - <Popover> - <PopoverTrigger asChild> - <FormControl> - <Button - variant="outline" - className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} - > - {field.value ? ( - format(field.value, "yyyy년 MM월 dd일") - ) : ( - <span>날짜를 선택하세요</span> - )} - <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> - </Button> - </FormControl> - </PopoverTrigger> - <PopoverContent className="w-auto p-0" align="start"> - <Calendar - mode="single" - selected={field.value} - onSelect={field.onChange} - initialFocus - /> - </PopoverContent> - </Popover> - <FormMessage /> - </FormItem> - )} - /> - - {/* 실사 확정일 */} - <FormField - control={form.control} - name="confirmedAt" - render={({ field }) => ( - <FormItem className="flex flex-col"> - <FormLabel>실사 확정일</FormLabel> - <Popover> - <PopoverTrigger asChild> - <FormControl> - <Button - variant="outline" - className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} - > - {field.value ? ( - format(field.value, "yyyy년 MM월 dd일") - ) : ( - <span>날짜를 선택하세요</span> - )} - <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> - </Button> - </FormControl> - </PopoverTrigger> - <PopoverContent className="w-auto p-0" align="start"> - <Calendar - mode="single" - selected={field.value} - onSelect={field.onChange} - initialFocus - /> - </PopoverContent> - </Popover> - <FormMessage /> - </FormItem> - )} - /> - - {/* 실제 실사일 */} - <FormField - control={form.control} - name="completedAt" - render={({ field }) => ( - <FormItem className="flex flex-col"> - <FormLabel>실제 실사일</FormLabel> - <Popover> - <PopoverTrigger asChild> - <FormControl> - <Button - variant="outline" - className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} - > - {field.value ? ( - format(field.value, "yyyy년 MM월 dd일") - ) : ( - <span>날짜를 선택하세요</span> - )} - <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> - </Button> - </FormControl> - </PopoverTrigger> - <PopoverContent className="w-auto p-0" align="start"> - <Calendar - mode="single" - selected={field.value} - onSelect={field.onChange} - initialFocus - /> - </PopoverContent> - </Popover> - <FormMessage /> - </FormItem> - )} - /> - - {/* 평가 점수 - 완료된 상태일 때만 표시 */} - {form.watch("investigationStatus") === "COMPLETED" && ( + {/* 실사 상태 */} <FormField control={form.control} - name="evaluationScore" + name="investigationStatus" render={({ field }) => ( <FormItem> - <FormLabel>평가 점수</FormLabel> + <FormLabel>실사 상태</FormLabel> <FormControl> - <Input - type="number" - min={0} - max={100} - placeholder="0-100점" - {...field} - value={field.value || ""} - onChange={(e) => { - const value = e.target.value === "" ? undefined : parseInt(e.target.value, 10) - field.onChange(value) - }} - /> + <Select value={field.value} onValueChange={field.onChange}> + <SelectTrigger> + <SelectValue placeholder="상태를 선택하세요" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectItem value="PLANNED">계획됨</SelectItem> + <SelectItem value="IN_PROGRESS">진행 중</SelectItem> + <SelectItem value="COMPLETED">완료됨</SelectItem> + <SelectItem value="CANCELED">취소됨</SelectItem> + </SelectGroup> + </SelectContent> + </Select> </FormControl> <FormMessage /> </FormItem> )} /> - )} - {/* 평가 결과 - 완료된 상태일 때만 표시 */} - {form.watch("investigationStatus") === "COMPLETED" && ( + {/* 평가 유형 */} <FormField control={form.control} - name="evaluationResult" + name="evaluationType" render={({ field }) => ( <FormItem> - <FormLabel>평가 결과</FormLabel> + <FormLabel>평가 유형</FormLabel> <FormControl> <Select value={field.value || ""} onValueChange={field.onChange}> <SelectTrigger> - <SelectValue placeholder="평가 결과를 선택하세요" /> + <SelectValue placeholder="평가 유형을 선택하세요" /> </SelectTrigger> <SelectContent> <SelectGroup> - <SelectItem value="APPROVED">승인</SelectItem> - <SelectItem value="SUPPLEMENT">보완</SelectItem> - <SelectItem value="REJECTED">불가</SelectItem> + <SelectItem value="SITE_AUDIT">실사의뢰평가</SelectItem> + <SelectItem value="QM_SELF_AUDIT">QM자체평가</SelectItem> </SelectGroup> </SelectContent> </Select> @@ -685,29 +583,229 @@ export function UpdateVendorInvestigationSheet({ </FormItem> )} /> - )} - {/* QM 의견 */} - <FormField - control={form.control} - name="investigationNotes" - render={({ field }) => ( - <FormItem> - <FormLabel>QM 의견</FormLabel> - <FormControl> - <Textarea - placeholder="실사에 대한 QM 의견을 입력하세요..." - {...field} - className="min-h-[80px]" - /> - </FormControl> - <FormMessage /> - </FormItem> + {/* 실사 주소 */} + <FormField + control={form.control} + name="investigationAddress" + render={({ field }) => ( + <FormItem> + <FormLabel>실사 주소</FormLabel> + <FormControl> + <Textarea + placeholder="실사가 진행될 주소를 입력하세요..." + {...field} + className="min-h-[60px]" + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 실사 방법 */} + <FormField + control={form.control} + name="investigationMethod" + render={({ field }) => ( + <FormItem> + <FormLabel>실사 방법</FormLabel> + <FormControl> + <Input placeholder="실사 방법을 입력하세요..." {...field} /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 실사 예정일 */} + <FormField + control={form.control} + name="forecastedAt" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>실사 예정일</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} + > + {field.value ? ( + format(field.value, "yyyy년 MM월 dd일") + ) : ( + <span>날짜를 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + + {/* 실사 확정일 */} + <FormField + control={form.control} + name="confirmedAt" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>실사 확정일</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} + > + {field.value ? ( + format(field.value, "yyyy년 MM월 dd일") + ) : ( + <span>날짜를 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + + {/* 실제 실사일 */} + <FormField + control={form.control} + name="completedAt" + render={({ field }) => ( + <FormItem className="flex flex-col"> + <FormLabel>실제 실사일</FormLabel> + <Popover> + <PopoverTrigger asChild> + <FormControl> + <Button + variant="outline" + className={`w-full pl-3 text-left font-normal ${!field.value && "text-muted-foreground"}`} + > + {field.value ? ( + format(field.value, "yyyy년 MM월 dd일") + ) : ( + <span>날짜를 선택하세요</span> + )} + <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> + </Button> + </FormControl> + </PopoverTrigger> + <PopoverContent className="w-auto p-0" align="start"> + <Calendar + mode="single" + selected={field.value} + onSelect={field.onChange} + initialFocus + /> + </PopoverContent> + </Popover> + <FormMessage /> + </FormItem> + )} + /> + + {/* 평가 점수 - 완료된 상태일 때만 표시 */} + {form.watch("investigationStatus") === "COMPLETED" && ( + <FormField + control={form.control} + name="evaluationScore" + render={({ field }) => ( + <FormItem> + <FormLabel>평가 점수</FormLabel> + <FormControl> + <Input + type="number" + min={0} + max={100} + placeholder="0-100점" + {...field} + value={field.value || ""} + onChange={(e) => { + const value = e.target.value === "" ? undefined : parseInt(e.target.value, 10) + field.onChange(value) + }} + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + )} + + {/* 평가 결과 - 완료된 상태일 때만 표시 */} + {form.watch("investigationStatus") === "COMPLETED" && ( + <FormField + control={form.control} + name="evaluationResult" + render={({ field }) => ( + <FormItem> + <FormLabel>평가 결과</FormLabel> + <FormControl> + <Select value={field.value || ""} onValueChange={field.onChange}> + <SelectTrigger> + <SelectValue placeholder="평가 결과를 선택하세요" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectItem value="APPROVED">승인</SelectItem> + <SelectItem value="SUPPLEMENT">보완</SelectItem> + <SelectItem value="REJECTED">불가</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> )} - /> - {/* 파일 첨부 섹션 */} - {renderFileUploadSection()} + {/* QM 의견 */} + <FormField + control={form.control} + name="investigationNotes" + render={({ field }) => ( + <FormItem> + <FormLabel>QM 의견</FormLabel> + <FormControl> + <Textarea + placeholder="실사에 대한 QM 의견을 입력하세요..." + {...field} + className="min-h-[80px]" + /> + </FormControl> + <FormMessage /> + </FormItem> + )} + /> + + {/* 파일 첨부 섹션 */} + {renderFileUploadSection()} </form> </Form> </div> @@ -719,9 +817,9 @@ export function UpdateVendorInvestigationSheet({ 취소 </Button> </SheetClose> - <Button + <Button disabled={isPending || uploadingFiles} - onClick={form.handleSubmit(onSubmit)} + onClick={handleSaveClick} > {(isPending || uploadingFiles) && ( <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" /> diff --git a/lib/vendor-investigation/validations.ts b/lib/vendor-investigation/validations.ts index bfe2e988..d04f100f 100644 --- a/lib/vendor-investigation/validations.ts +++ b/lib/vendor-investigation/validations.ts @@ -70,10 +70,28 @@ export const updateVendorInvestigationSchema = z.object({ evaluationType: z.enum(["SITE_AUDIT", "QM_SELF_AUDIT"]).optional(), investigationAddress: z.string().optional(), investigationMethod: z.string().max(100, "실사 방법은 100자 이내로 입력해주세요.").optional(), - forecastedAt: z.date().optional(), - requestedAt: z.date().optional(), - confirmedAt: z.date().optional(), - completedAt: z.date().optional(), + + // 날짜 필드들을 string에서 Date로 변환하도록 수정 + forecastedAt: z.union([ + z.date(), + z.string().transform((str) => str ? new Date(str) : undefined) + ]).optional(), + + requestedAt: z.union([ + z.date(), + z.string().transform((str) => str ? new Date(str) : undefined) + ]).optional(), + + confirmedAt: z.union([ + z.date(), + z.string().transform((str) => str ? new Date(str) : undefined) + ]).optional(), + + completedAt: z.union([ + z.date(), + z.string().transform((str) => str ? new Date(str) : undefined) + ]).optional(), + evaluationScore: z.number() .int("평가 점수는 정수여야 합니다.") .min(0, "평가 점수는 0점 이상이어야 합니다.") @@ -84,4 +102,4 @@ export const updateVendorInvestigationSchema = z.object({ attachments: z.any().optional(), // File 업로드를 위한 필드 }) -export type UpdateVendorInvestigationSchema = z.infer<typeof updateVendorInvestigationSchema> +export type UpdateVendorInvestigationSchema = z.infer<typeof updateVendorInvestigationSchema>
\ No newline at end of file |
