summaryrefslogtreecommitdiff
path: root/lib/vendor-investigation/table/update-investigation-sheet.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-investigation/table/update-investigation-sheet.tsx')
-rw-r--r--lib/vendor-investigation/table/update-investigation-sheet.tsx713
1 files changed, 562 insertions, 151 deletions
diff --git a/lib/vendor-investigation/table/update-investigation-sheet.tsx b/lib/vendor-investigation/table/update-investigation-sheet.tsx
index fe30c892..69f0d9ae 100644
--- a/lib/vendor-investigation/table/update-investigation-sheet.tsx
+++ b/lib/vendor-investigation/table/update-investigation-sheet.tsx
@@ -3,7 +3,8 @@
import * as React from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
-import { Loader } from "lucide-react"
+import { CalendarIcon, Loader } from "lucide-react"
+import { format } from "date-fns"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
@@ -16,6 +17,7 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
+import { Textarea } from "@/components/ui/textarea"
import {
Sheet,
SheetClose,
@@ -33,33 +35,76 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
+import { Calendar } from "@/components/ui/calendar"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+import {
+ Dropzone,
+ DropzoneZone,
+ DropzoneUploadIcon,
+ DropzoneTitle,
+ DropzoneDescription,
+ DropzoneInput
+} from "@/components/ui/dropzone"
import {
updateVendorInvestigationSchema,
type UpdateVendorInvestigationSchema,
} from "../validations"
-import { updateVendorInvestigationAction } from "../service"
+import { updateVendorInvestigationAction, getInvestigationAttachments, deleteInvestigationAttachment } from "../service"
import { VendorInvestigationsViewWithContacts } from "@/config/vendorInvestigationsColumnsConfig"
-/**
- * The shape of `vendorInvestigation`
- * might come from your `vendorInvestigationsView` row
- * or your existing type for a single investigation.
- */
-
interface UpdateVendorInvestigationSheetProps
extends React.ComponentPropsWithoutRef<typeof Sheet> {
investigation: VendorInvestigationsViewWithContacts | null
}
+// 첨부파일 정책 정의
+const getFileUploadConfig = (status: string) => {
+ // 취소된 상태에서만 파일 업로드 비활성화
+ if (status === "CANCELED") {
+ return {
+ enabled: false,
+ label: "",
+ description: "",
+ accept: undefined, // undefined로 변경
+ maxSize: 0,
+ maxSizeText: ""
+ }
+ }
+
+ // 모든 활성 상태에서 동일한 정책 적용
+ return {
+ enabled: true,
+ label: "실사 관련 첨부파일",
+ description: "실사와 관련된 모든 문서와 이미지를 첨부할 수 있습니다.",
+ accept: {
+ 'application/pdf': ['.pdf'],
+ 'application/msword': ['.doc'],
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
+ 'application/vnd.ms-excel': ['.xls'],
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx'],
+ 'image/*': ['.png', '.jpg', '.jpeg', '.gif'],
+ },
+ maxSize: 10 * 1024 * 1024, // 10MB
+ maxSizeText: "10MB"
+ }
+}
+
/**
- * A sheet for updating a vendor investigation (plus optional attachments).
+ * 실사 정보 수정 시트
*/
export function UpdateVendorInvestigationSheet({
investigation,
...props
}: UpdateVendorInvestigationSheetProps) {
const [isPending, startTransition] = React.useTransition()
+ const [existingAttachments, setExistingAttachments] = React.useState<any[]>([])
+ const [loadingAttachments, setLoadingAttachments] = React.useState(false)
+ const [uploadingFiles, setUploadingFiles] = React.useState(false)
// RHF + Zod
const form = useForm<UpdateVendorInvestigationSchema>({
@@ -67,138 +112,346 @@ export function UpdateVendorInvestigationSheet({
defaultValues: {
investigationId: investigation?.investigationId ?? 0,
investigationStatus: investigation?.investigationStatus ?? "PLANNED",
- scheduledStartAt: investigation?.scheduledStartAt ?? undefined,
- scheduledEndAt: investigation?.scheduledEndAt ?? undefined,
+ evaluationType: investigation?.evaluationType ?? undefined,
+ investigationAddress: investigation?.investigationAddress ?? "",
+ investigationMethod: investigation?.investigationMethod ?? "",
+ forecastedAt: investigation?.forecastedAt ?? undefined,
+ requestedAt: investigation?.requestedAt ?? undefined,
+ confirmedAt: investigation?.confirmedAt ?? undefined,
completedAt: investigation?.completedAt ?? undefined,
+ evaluationScore: investigation?.evaluationScore ?? undefined,
+ evaluationResult: investigation?.evaluationResult ?? undefined,
investigationNotes: investigation?.investigationNotes ?? "",
+ attachments: undefined, // 파일은 매번 새로 업로드
},
})
+ // investigation이 변경될 때마다 폼 리셋
React.useEffect(() => {
if (investigation) {
form.reset({
investigationId: investigation.investigationId,
investigationStatus: investigation.investigationStatus || "PLANNED",
- scheduledStartAt: investigation.scheduledStartAt ?? undefined,
- scheduledEndAt: investigation.scheduledEndAt ?? undefined,
+ evaluationType: investigation.evaluationType ?? undefined,
+ investigationAddress: investigation.investigationAddress ?? "",
+ investigationMethod: investigation.investigationMethod ?? "",
+ forecastedAt: investigation.forecastedAt ?? undefined,
+ requestedAt: investigation.requestedAt ?? undefined,
+ confirmedAt: investigation.confirmedAt ?? undefined,
completedAt: investigation.completedAt ?? undefined,
+ evaluationScore: investigation.evaluationScore ?? undefined,
+ evaluationResult: investigation.evaluationResult ?? undefined,
investigationNotes: investigation.investigationNotes ?? "",
+ attachments: undefined, // 파일은 매번 새로 업로드
})
+
+ // 기존 첨부파일 로드
+ loadExistingAttachments(investigation.investigationId)
}
}, [investigation, form])
- // Format date for form data
- const formatDateForFormData = (date: Date | undefined): string | null => {
- if (!date) return null;
- return date.toISOString();
+ // 기존 첨부파일 로드 함수
+ const loadExistingAttachments = async (investigationId: number) => {
+ setLoadingAttachments(true)
+ try {
+ const result = await getInvestigationAttachments(investigationId)
+ if (result.success) {
+ setExistingAttachments(result.attachments || [])
+ } else {
+ toast.error("첨부파일 목록을 불러오는데 실패했습니다.")
+ }
+ } catch (error) {
+ console.error("첨부파일 로드 실패:", error)
+ toast.error("첨부파일 목록을 불러오는 중 오류가 발생했습니다.")
+ } finally {
+ setLoadingAttachments(false)
+ }
}
- // Submit handler
- async function onSubmit(values: UpdateVendorInvestigationSchema) {
- if (!values.investigationId) return
-
- startTransition(async () => {
- // 1) Build a FormData object for the server action
- const formData = new FormData()
-
- // Add text fields
- formData.append("investigationId", String(values.investigationId))
- formData.append("investigationStatus", values.investigationStatus)
-
- // Format dates properly before appending to FormData
- if (values.scheduledStartAt) {
- const formattedDate = formatDateForFormData(values.scheduledStartAt)
- if (formattedDate) formData.append("scheduledStartAt", formattedDate)
- }
+ // 첨부파일 삭제 함수
+ const handleDeleteAttachment = async (attachmentId: number) => {
+ if (!investigation) return
+
+ try {
+ const response = await fetch(`/api/vendor-investigations/${investigation.investigationId}/attachments?attachmentId=${attachmentId}`, {
+ method: "DELETE",
+ })
- if (values.scheduledEndAt) {
- const formattedDate = formatDateForFormData(values.scheduledEndAt)
- if (formattedDate) formData.append("scheduledEndAt", formattedDate)
+ if (!response.ok) {
+ const errorData = await response.json()
+ throw new Error(errorData.error || "첨부파일 삭제 실패")
}
- if (values.completedAt) {
- const formattedDate = formatDateForFormData(values.completedAt)
- if (formattedDate) formData.append("completedAt", formattedDate)
- }
+ toast.success("첨부파일이 삭제되었습니다.")
+ // 목록 새로고침
+ loadExistingAttachments(investigation.investigationId)
- if (values.investigationNotes) {
- formData.append("investigationNotes", values.investigationNotes)
- }
+ } catch (error) {
+ console.error("첨부파일 삭제 오류:", error)
+ toast.error(error instanceof Error ? error.message : "첨부파일 삭제 중 오류가 발생했습니다.")
+ }
+ }
- // Add attachments (if any)
- // Note: If you have multiple files in "attachments", we store them in the form under the same key.
- const attachmentValue = form.getValues("attachments");
- if (attachmentValue instanceof FileList) {
- for (let i = 0; i < attachmentValue.length; i++) {
- formData.append("attachments", attachmentValue[i]);
- }
- }
+ // 파일 업로드 섹션 렌더링
+ const renderFileUploadSection = () => {
+ const currentStatus = form.watch("investigationStatus")
+ const config = getFileUploadConfig(currentStatus)
+
+ if (!config.enabled) return null
- const { error } = await updateVendorInvestigationAction(formData)
- if (error) {
- toast.error(error)
- return
- }
+ return (
+ <>
+ {/* 기존 첨부파일 목록 */}
+ {(existingAttachments.length > 0 || loadingAttachments) && (
+ <div className="space-y-2">
+ <FormLabel>기존 첨부파일</FormLabel>
+ <div className="border rounded-md p-3 space-y-2 max-h-32 overflow-y-auto">
+ {loadingAttachments ? (
+ <div className="flex items-center justify-center py-4">
+ <Loader className="h-4 w-4 animate-spin" />
+ <span className="ml-2 text-sm text-muted-foreground">
+ 첨부파일 로딩 중...
+ </span>
+ </div>
+ ) : existingAttachments.length > 0 ? (
+ existingAttachments.map((attachment) => (
+ <div key={attachment.id} className="flex items-center justify-between text-sm">
+ <div className="flex items-center space-x-2 flex-1 min-w-0">
+ <span className="text-xs px-2 py-1 bg-muted rounded">
+ {attachment.attachmentType}
+ </span>
+ <span className="truncate">{attachment.fileName}</span>
+ <span className="text-muted-foreground">
+ ({Math.round(attachment.fileSize / 1024)}KB)
+ </span>
+ </div>
+ <Button
+ type="button"
+ variant="ghost"
+ size="sm"
+ onClick={() => handleDeleteAttachment(attachment.id)}
+ className="text-destructive hover:text-destructive"
+ disabled={isPending}
+ >
+ 삭제
+ </Button>
+ </div>
+ ))
+ ) : (
+ <div className="text-sm text-muted-foreground text-center py-2">
+ 첨부된 파일이 없습니다.
+ </div>
+ )}
+ </div>
+ </div>
+ )}
- toast.success("Investigation updated!")
- form.reset()
- props.onOpenChange?.(false)
- })
+ {/* 새 파일 업로드 */}
+ <FormField
+ control={form.control}
+ name="attachments"
+ render={({ field: { onChange, ...field } }) => (
+ <FormItem>
+ <FormLabel>{config.label}</FormLabel>
+ <FormControl>
+ <Dropzone
+ onDrop={(acceptedFiles, rejectedFiles) => {
+ // 거부된 파일에 대한 상세 에러 메시지
+ if (rejectedFiles.length > 0) {
+ rejectedFiles.forEach((file) => {
+ const error = file.errors[0]
+ if (error.code === 'file-too-large') {
+ toast.error(`${file.file.name}: 파일 크기가 ${config.maxSizeText}를 초과합니다.`)
+ } else if (error.code === 'file-invalid-type') {
+ toast.error(`${file.file.name}: 지원하지 않는 파일 형식입니다.`)
+ } else {
+ toast.error(`${file.file.name}: 파일 업로드에 실패했습니다.`)
+ }
+ })
+ }
+
+ if (acceptedFiles.length > 0) {
+ onChange(acceptedFiles)
+ toast.success(`${acceptedFiles.length}개 파일이 선택되었습니다.`)
+ }
+ }}
+ accept={config.accept}
+ multiple
+ maxSize={config.maxSize}
+ disabled={isPending || uploadingFiles}
+ >
+ <DropzoneZone>
+ <DropzoneUploadIcon />
+ <DropzoneTitle>
+ {isPending || uploadingFiles
+ ? "파일 업로드 중..."
+ : "파일을 드래그하거나 클릭하여 업로드"
+ }
+ </DropzoneTitle>
+ <DropzoneDescription>
+ {config.description} (최대 {config.maxSizeText})
+ </DropzoneDescription>
+ <DropzoneInput />
+ </DropzoneZone>
+ </Dropzone>
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ </>
+ )
}
- // Format date value for input field
- const formatDateForInput = (date: Date | undefined): string => {
- if (!date) return "";
- return date instanceof Date ? date.toISOString().slice(0, 10) : "";
+ // 파일 업로드 함수
+ const uploadFiles = async (files: File[], investigationId: number) => {
+ 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)
}
- // Handle date input change
- const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>, onChange: (...event: any[]) => void) => {
- const val = e.target.value;
- if (val) {
- // Ensure proper date handling by setting to noon to avoid timezone issues
- const newDate = new Date(`${val}T12:00:00`);
- onChange(newDate);
- } else {
- onChange(undefined);
- }
+ // Submit handler
+ async function onSubmit(values: UpdateVendorInvestigationSchema) {
+ if (!values.investigationId) return
+
+ startTransition(async () => {
+ try {
+ // 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
+ }
+
+ // 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) {
+ toast.error(`데이터는 저장되었지만 파일 업로드 중 오류가 발생했습니다: ${fileError}`)
+ } finally {
+ setUploadingFiles(false)
+ }
+ } else {
+ toast.success("실사 정보가 업데이트되었습니다!")
+ }
+
+ form.reset()
+ props.onOpenChange?.(false)
+
+ } catch (error) {
+ console.error("실사 정보 업데이트 오류:", error)
+ toast.error("실사 정보 업데이트 중 오류가 발생했습니다.")
+ }
+ })
}
return (
<Sheet {...props}>
- <SheetContent className="flex flex-col gap-6 sm:max-w-md">
- <SheetHeader className="text-left">
- <SheetTitle>Update Investigation</SheetTitle>
+ <SheetContent className="flex flex-col h-full sm:max-w-md">
+ <SheetHeader className="text-left flex-shrink-0">
+ <SheetTitle>실사 업데이트</SheetTitle>
<SheetDescription>
- Change the investigation details &amp; attachments
+ {investigation?.vendorName && (
+ <span className="font-medium">{investigation.vendorName}</span>
+ )}의 실사 정보를 수정합니다.
</SheetDescription>
</SheetHeader>
- <Form {...form}>
- <form
- onSubmit={form.handleSubmit(onSubmit)}
- className="flex flex-col gap-4"
- // Must use multipart to support file uploads
- encType="multipart/form-data"
- >
- {/* investigationStatus */}
+ <div className="flex-1 overflow-y-auto py-4">
+ <Form {...form}>
+ <form
+ onSubmit={form.handleSubmit(onSubmit)}
+ className="flex flex-col gap-4"
+ >
+ {/* 실사 상태 */}
<FormField
control={form.control}
name="investigationStatus"
render={({ field }) => (
<FormItem>
- <FormLabel>Status</FormLabel>
+ <FormLabel>실사 상태</FormLabel>
<FormControl>
<Select value={field.value} onValueChange={field.onChange}>
- <SelectTrigger className="capitalize">
- <SelectValue placeholder="Select a status" />
+ <SelectTrigger>
+ <SelectValue placeholder="상태를 선택하세요" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
- <SelectItem value="PLANNED">PLANNED</SelectItem>
- <SelectItem value="IN_PROGRESS">IN_PROGRESS</SelectItem>
- <SelectItem value="COMPLETED">COMPLETED</SelectItem>
- <SelectItem value="CANCELED">CANCELED</SelectItem>
+ <SelectItem value="PLANNED">계획됨</SelectItem>
+ <SelectItem value="IN_PROGRESS">진행 중</SelectItem>
+ <SelectItem value="COMPLETED">완료됨</SelectItem>
+ <SelectItem value="CANCELED">취소됨</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
@@ -208,37 +461,43 @@ export function UpdateVendorInvestigationSheet({
)}
/>
- {/* scheduledStartAt */}
+ {/* 평가 유형 */}
<FormField
control={form.control}
- name="scheduledStartAt"
+ name="evaluationType"
render={({ field }) => (
<FormItem>
- <FormLabel>Scheduled Start</FormLabel>
+ <FormLabel>평가 유형</FormLabel>
<FormControl>
- <Input
- type="date"
- value={formatDateForInput(field.value)}
- onChange={(e) => handleDateChange(e, field.onChange)}
- />
+ <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>
)}
/>
- {/* scheduledEndAt */}
+ {/* 실사 주소 */}
<FormField
control={form.control}
- name="scheduledEndAt"
+ name="investigationAddress"
render={({ field }) => (
<FormItem>
- <FormLabel>Scheduled End</FormLabel>
+ <FormLabel>실사 주소</FormLabel>
<FormControl>
- <Input
- type="date"
- value={formatDateForInput(field.value)}
- onChange={(e) => handleDateChange(e, field.onChange)}
+ <Textarea
+ placeholder="실사가 진행될 주소를 입력하세요..."
+ {...field}
+ className="min-h-[60px]"
/>
</FormControl>
<FormMessage />
@@ -246,55 +505,200 @@ export function UpdateVendorInvestigationSheet({
)}
/>
- {/* completedAt */}
+ {/* 실사 방법 */}
<FormField
control={form.control}
- name="completedAt"
+ name="investigationMethod"
render={({ field }) => (
<FormItem>
- <FormLabel>Completed At</FormLabel>
+ <FormLabel>실사 방법</FormLabel>
<FormControl>
- <Input
- type="date"
- value={formatDateForInput(field.value)}
- onChange={(e) => handleDateChange(e, field.onChange)}
- />
+ <Input placeholder="실사 방법을 입력하세요..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
- {/* investigationNotes */}
+ {/* 실사 예정일 */}
<FormField
control={form.control}
- name="investigationNotes"
+ name="forecastedAt"
render={({ field }) => (
- <FormItem>
- <FormLabel>Notes</FormLabel>
- <FormControl>
- <Input placeholder="Notes about the investigation..." {...field} />
- </FormControl>
+ <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>
)}
/>
- {/* attachments: multiple file upload */}
+ {/* 실사 확정일 */}
<FormField
control={form.control}
- name="attachments"
- render={({ field: { value, onChange, ...fieldProps } }) => (
+ 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>
+ )}
+ />
+ )}
+
+ {/* QM 의견 */}
+ <FormField
+ control={form.control}
+ name="investigationNotes"
+ render={({ field }) => (
<FormItem>
- <FormLabel>Attachments</FormLabel>
+ <FormLabel>QM 의견</FormLabel>
<FormControl>
- <Input
- type="file"
- multiple
- onChange={(e) => {
- onChange(e.target.files); // Store the FileList directly
- }}
- {...fieldProps}
+ <Textarea
+ placeholder="실사에 대한 QM 의견을 입력하세요..."
+ {...field}
+ className="min-h-[80px]"
/>
</FormControl>
<FormMessage />
@@ -302,22 +706,29 @@ export function UpdateVendorInvestigationSheet({
)}
/>
- {/* Footer Buttons */}
- <SheetFooter className="gap-2 pt-2 sm:space-x-0">
- <SheetClose asChild>
- <Button type="button" variant="outline">
- Cancel
- </Button>
- </SheetClose>
- <Button disabled={isPending}>
- {isPending && (
- <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
- )}
- Save
- </Button>
- </SheetFooter>
- </form>
- </Form>
+ {/* 파일 첨부 섹션 */}
+ {renderFileUploadSection()}
+ </form>
+ </Form>
+ </div>
+
+ {/* Footer Buttons */}
+ <SheetFooter className="gap-2 pt-2 sm:space-x-0 flex-shrink-0">
+ <SheetClose asChild>
+ <Button type="button" variant="outline" disabled={isPending || uploadingFiles}>
+ 취소
+ </Button>
+ </SheetClose>
+ <Button
+ disabled={isPending || uploadingFiles}
+ onClick={form.handleSubmit(onSubmit)}
+ >
+ {(isPending || uploadingFiles) && (
+ <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />
+ )}
+ {uploadingFiles ? "업로드 중..." : isPending ? "저장 중..." : "저장"}
+ </Button>
+ </SheetFooter>
</SheetContent>
</Sheet>
)