diff options
Diffstat (limited to 'lib/vendor-investigation/table/investigation-progress-sheet.tsx')
| -rw-r--r-- | lib/vendor-investigation/table/investigation-progress-sheet.tsx | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/lib/vendor-investigation/table/investigation-progress-sheet.tsx b/lib/vendor-investigation/table/investigation-progress-sheet.tsx new file mode 100644 index 00000000..c0357f5c --- /dev/null +++ b/lib/vendor-investigation/table/investigation-progress-sheet.tsx @@ -0,0 +1,324 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { CalendarIcon, Loader } from "lucide-react" +import { format } from "date-fns" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Textarea } from "@/components/ui/textarea" +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Calendar } from "@/components/ui/calendar" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" + +import { + updateVendorInvestigationProgressSchema, + type UpdateVendorInvestigationProgressSchema, +} from "../validations" +import { updateVendorInvestigationProgressAction } from "../service" +import { VendorInvestigationsViewWithContacts } from "@/config/vendorInvestigationsColumnsConfig" + +interface InvestigationProgressSheetProps + extends React.ComponentPropsWithoutRef<typeof Sheet> { + investigation: VendorInvestigationsViewWithContacts | null +} + +/** + * 실사 진행 관리 시트 + */ +export function InvestigationProgressSheet({ + investigation, + ...props +}: InvestigationProgressSheetProps) { + const [isPending, startTransition] = React.useTransition() + + // RHF + Zod + const form = useForm<UpdateVendorInvestigationProgressSchema>({ + resolver: zodResolver(updateVendorInvestigationProgressSchema), + defaultValues: { + investigationId: investigation?.investigationId ?? 0, + investigationAddress: investigation?.investigationAddress ?? "", + investigationMethod: investigation?.investigationMethod ?? undefined, + forecastedAt: investigation?.forecastedAt ?? undefined, + confirmedAt: investigation?.confirmedAt ?? undefined, + }, + }) + + // investigation이 변경될 때마다 폼 리셋 + React.useEffect(() => { + if (investigation) { + form.reset({ + investigationId: investigation.investigationId, + investigationAddress: investigation.investigationAddress ?? "", + investigationMethod: investigation.investigationMethod ?? undefined, + forecastedAt: investigation.forecastedAt ?? undefined, + confirmedAt: investigation.confirmedAt ?? undefined, + }) + } + }, [investigation, form]) + + // Submit handler + async function onSubmit(values: UpdateVendorInvestigationProgressSchema) { + console.log("실사 진행 관리 onSubmit 호출됨:", values) + + if (!values.investigationId) { + console.log("investigationId가 없음:", values.investigationId) + return + } + + startTransition(async () => { + try { + console.log("실사 진행 관리 startTransition 시작") + + // FormData 생성 + const formData = new FormData() + + // 필수 필드 + formData.append("investigationId", String(values.investigationId)) + + // 선택적 필드들 + 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.confirmedAt) { + formData.append("confirmedAt", values.confirmedAt.toISOString()) + } + + // 실사 진행 관리 업데이트 (PLANNED -> IN_PROGRESS) + const { error } = await updateVendorInvestigationProgressAction(formData) + + if (error) { + toast.error(error) + return + } + + toast.success("실사 진행 정보가 업데이트되었습니다!") + form.reset() + props.onOpenChange?.(false) + + } catch (error) { + console.error("실사 진행 관리 업데이트 오류:", error) + toast.error("실사 진행 관리 업데이트 중 오류가 발생했습니다.") + } + }) + } + + // 디버깅을 위한 버튼 클릭 핸들러 + 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-xl" > + <SheetHeader className="text-left flex-shrink-0"> + <SheetTitle>실사 진행 관리</SheetTitle> + <SheetDescription> + {investigation?.vendorName && ( + <span className="font-medium">{investigation.vendorName}</span> + )}의 실사 진행 정보를 관리합니다. + </SheetDescription> + </SheetHeader> + + <div className="flex-1 overflow-y-auto py-4"> + <Form {...form}> + <form + onSubmit={form.handleSubmit(onSubmit)} + className="flex flex-col gap-4" + id="investigation-progress-form" + > + {/* 실사 주소 */} + <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> + <Select value={field.value || ""} onValueChange={field.onChange}> + <SelectTrigger> + <SelectValue placeholder="실사 방법을 선택하세요" /> + </SelectTrigger> + <SelectContent> + <SelectGroup> + <SelectItem value="PURCHASE_SELF_EVAL">구매자체평가</SelectItem> + <SelectItem value="DOCUMENT_EVAL">서류평가</SelectItem> + <SelectItem value="PRODUCT_INSPECTION">제품검사평가</SelectItem> + <SelectItem value="SITE_VISIT_EVAL">방문실사평가</SelectItem> + </SelectGroup> + </SelectContent> + </Select> + </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> + )} + /> + </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}> + 취소 + </Button> + </SheetClose> + <Button + disabled={isPending} + onClick={handleSaveClick} + > + {isPending && ( + <Loader className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" /> + )} + {isPending ? "저장 중..." : "저장"} + </Button> + </SheetFooter> + </SheetContent> + </Sheet> + ) +} |
