diff options
Diffstat (limited to 'lib/cbe/table/invite-vendors-dialog.tsx')
| -rw-r--r-- | lib/cbe/table/invite-vendors-dialog.tsx | 428 |
1 files changed, 0 insertions, 428 deletions
diff --git a/lib/cbe/table/invite-vendors-dialog.tsx b/lib/cbe/table/invite-vendors-dialog.tsx deleted file mode 100644 index 38edddc1..00000000 --- a/lib/cbe/table/invite-vendors-dialog.tsx +++ /dev/null @@ -1,428 +0,0 @@ -"use client" - -import * as React from "react" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import { Loader, Send, User } from "lucide-react" -import { toast } from "sonner" -import { z } from "zod" - -import { useMediaQuery } from "@/hooks/use-media-query" -import { Button } from "@/components/ui/button" -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog" -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "@/components/ui/drawer" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - FormDescription, -} from "@/components/ui/form" -import { type Row } from "@tanstack/react-table" -import { Badge } from "@/components/ui/badge" -import { ScrollArea } from "@/components/ui/scroll-area" - -import { VendorWithCbeFields } from "@/config/vendorCbeColumnsConfig" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { createCbeEvaluation } from "@/lib/rfqs/service" - -// 컴포넌트 내부에서 사용할 폼 스키마 정의 -const formSchema = z.object({ - paymentTerms: z.string().min(1, "지급 조건을 입력하세요"), - incoterms: z.string().min(1, "Incoterms를 입력하세요"), - deliverySchedule: z.string().min(1, "배송 일정을 입력하세요"), - notes: z.string().optional(), -}) - -type FormValues = z.infer<typeof formSchema> - -interface InviteVendorsDialogProps - extends React.ComponentPropsWithoutRef<typeof Dialog> { - rfqId: number - vendors: Row<VendorWithCbeFields>["original"][] - currentUserId?: number - currentUser?: { - id: string - name?: string | null - email?: string | null - image?: string | null - companyId?: number | null - domain?: string | null - } - showTrigger?: boolean - onSuccess?: () => void - hasMultipleRfqIds?: boolean -} - -export function InviteVendorsDialog({ - rfqId, - vendors, - currentUserId, - currentUser, - showTrigger = true, - onSuccess, - hasMultipleRfqIds, - ...props -}: InviteVendorsDialogProps) { - const [files, setFiles] = React.useState<FileList | null>(null) - const isDesktop = useMediaQuery("(min-width: 640px)") - const [isSubmitting, setIsSubmitting] = React.useState(false) - - // 로컬 스키마와 폼 값을 사용하도록 수정 - const form = useForm<FormValues>({ - resolver: zodResolver(formSchema), - defaultValues: { - paymentTerms: "", - incoterms: "", - deliverySchedule: "", - notes: "", - }, - mode: "onChange", - }) - - // 폼 상태 감시 - const { formState } = form - const isValid = formState.isValid && - !!form.getValues("paymentTerms") && - !!form.getValues("incoterms") && - !!form.getValues("deliverySchedule") - - // 디버깅용 상태 트래킹 - React.useEffect(() => { - const subscription = form.watch((value) => { - // 폼 값이 변경될 때마다 실행되는 콜백 - console.log("Form values changed:", value); - }); - - return () => subscription.unsubscribe(); - }, [form]); - - async function onSubmit(data: FormValues) { - try { - setIsSubmitting(true) - - // 기본 FormData 생성 - const formData = new FormData() - - // rfqId 추가 - formData.append("rfqId", String(rfqId)) - - // 폼 데이터 추가 - Object.entries(data).forEach(([key, value]) => { - if (value !== undefined && value !== null) { - formData.append(key, String(value)) - } - }) - - // 현재 사용자 ID 추가 - if (currentUserId) { - formData.append("evaluatedBy", String(currentUserId)) - } - - // 협력업체 ID만 추가 (서버에서 연락처 정보를 조회) - vendors.forEach((vendor) => { - formData.append("vendorIds[]", String(vendor.vendorId)) - }) - - // 파일 추가 (있는 경우에만) - if (files && files.length > 0) { - for (let i = 0; i < files.length; i++) { - formData.append("files", files[i]) - } - } - - // 서버 액션 호출 - const response = await createCbeEvaluation(formData) - - if (response.error) { - toast.error(response.error) - return - } - - // 성공 처리 - toast.success(`${vendors.length}개 협력업체에 CBE 평가가 성공적으로 전송되었습니다!`) - form.reset() - setFiles(null) - props.onOpenChange?.(false) - onSuccess?.() - } catch (error) { - console.error(error) - toast.error("CBE 평가 생성 중 오류가 발생했습니다.") - } finally { - setIsSubmitting(false) - } - } - - function handleDialogOpenChange(nextOpen: boolean) { - if (!nextOpen) { - form.reset() - setFiles(null) - } - props.onOpenChange?.(nextOpen) - } - - // 필수 필드 라벨에 추가할 요소 - const RequiredLabel = ( - <span className="text-destructive ml-1 font-medium">*</span> - ) - - const formContent = ( - <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> - {/* 선택된 협력업체 정보 표시 */} - <div className="space-y-2"> - <FormLabel>선택된 협력업체 ({vendors.length})</FormLabel> - <ScrollArea className="h-20 border rounded-md p-2"> - <div className="flex flex-wrap gap-2"> - {vendors.map((vendor, index) => ( - <Badge key={index} variant="secondary" className="py-1"> - {vendor.vendorName || `협력업체 #${vendor.vendorCode}`} - </Badge> - ))} - </div> - </ScrollArea> - <FormDescription> - 선택된 모든 협력업체의 등록된 연락처에게 CBE 평가 알림이 전송됩니다. - </FormDescription> - </div> - - {/* 작성자 정보 (읽기 전용) */} - {currentUser && ( - <div className="border rounded-md p-3 space-y-2"> - <FormLabel>작성자</FormLabel> - <div className="flex items-center gap-3"> - {currentUser.image ? ( - <Avatar className="h-8 w-8"> - <AvatarImage src={currentUser.image} alt={currentUser.name || ""} /> - <AvatarFallback> - {currentUser.name?.charAt(0) || <User className="h-4 w-4" />} - </AvatarFallback> - </Avatar> - ) : ( - <Avatar className="h-8 w-8"> - <AvatarFallback> - {currentUser.name?.charAt(0) || <User className="h-4 w-4" />} - </AvatarFallback> - </Avatar> - )} - <div> - <p className="text-sm font-medium">{currentUser.name || "Unknown User"}</p> - <p className="text-xs text-muted-foreground">{currentUser.email || ""}</p> - </div> - </div> - </div> - )} - - {/* 지급 조건 - 필수 필드 */} - <FormField - control={form.control} - name="paymentTerms" - render={({ field }) => ( - <FormItem> - <FormLabel> - 지급 조건{RequiredLabel} - </FormLabel> - <FormControl> - <Input {...field} placeholder="예: Net 30" /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* Incoterms - 필수 필드 */} - <FormField - control={form.control} - name="incoterms" - render={({ field }) => ( - <FormItem> - <FormLabel> - Incoterms{RequiredLabel} - </FormLabel> - <FormControl> - <Input {...field} placeholder="예: FOB, CIF" /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 배송 일정 - 필수 필드 */} - <FormField - control={form.control} - name="deliverySchedule" - render={({ field }) => ( - <FormItem> - <FormLabel> - 배송 일정{RequiredLabel} - </FormLabel> - <FormControl> - <Textarea - {...field} - placeholder="배송 일정 세부사항을 입력하세요" - rows={3} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 비고 - 선택적 필드 */} - <FormField - control={form.control} - name="notes" - render={({ field }) => ( - <FormItem> - <FormLabel>비고</FormLabel> - <FormControl> - <Textarea - {...field} - placeholder="추가 비고 사항을 입력하세요" - rows={3} - /> - </FormControl> - <FormMessage /> - </FormItem> - )} - /> - - {/* 파일 첨부 (옵션) */} - <div className="space-y-2"> - <FormLabel htmlFor="files">첨부 파일 (선택사항)</FormLabel> - <Input - id="files" - type="file" - multiple - onChange={(e) => setFiles(e.target.files)} - /> - {files && files.length > 0 && ( - <p className="text-sm text-muted-foreground"> - {files.length}개 파일이 첨부되었습니다 - </p> - )} - </div> - - {/* 필수 입력 항목 안내 */} - <div className="text-sm text-muted-foreground"> - <span className="text-destructive">*</span> 표시는 필수 입력 항목입니다. - </div> - - {/* 모바일에서는 Drawer 내부에서 버튼이 렌더링되므로 여기서는 숨김 */} - {isDesktop && ( - <DialogFooter className="gap-2 pt-4"> - <DialogClose asChild> - <Button - type="button" - variant="outline" - > - 취소 - </Button> - </DialogClose> - <Button - type="submit" - disabled={isSubmitting || !isValid} - > - {isSubmitting && ( - <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> - )} - {vendors.length > 1 ? `${vendors.length}개 협력업체에 전송` : "전송"} - </Button> - </DialogFooter> - )} - </form> - </Form> - ) - if (hasMultipleRfqIds) { - toast.error("동일한 RFQ에 대해 선택해주세요"); - return; - } - // Desktop Dialog - if (isDesktop) { - return ( - <Dialog {...props} onOpenChange={handleDialogOpenChange}> - {showTrigger ? ( - <DialogTrigger asChild> - <Button variant="outline" size="sm"> - <Send className="mr-2 size-4" aria-hidden="true" /> - CBE 평가 전송 ({vendors.length}) - </Button> - </DialogTrigger> - ) : null} - <DialogContent className="sm:max-w-[600px]"> - <DialogHeader> - <DialogTitle>CBE 평가 생성 및 전송</DialogTitle> - <DialogDescription> - 선택한 {vendors.length}개 협력업체에 대한 상업 입찰 평가를 생성하고 알림을 전송합니다. - </DialogDescription> - </DialogHeader> - - {formContent} - </DialogContent> - </Dialog> - ) - } - - // Mobile Drawer - return ( - <Drawer {...props} onOpenChange={handleDialogOpenChange}> - {showTrigger ? ( - <DrawerTrigger asChild> - <Button variant="outline" size="sm"> - <Send className="mr-2 size-4" aria-hidden="true" /> - CBE 평가 전송 ({vendors.length}) - </Button> - </DrawerTrigger> - ) : null} - <DrawerContent> - <DrawerHeader> - <DrawerTitle>CBE 평가 생성 및 전송</DrawerTitle> - <DrawerDescription> - 선택한 {vendors.length}개 협력업체에 대한 상업 입찰 평가를 생성하고 알림을 전송합니다. - </DrawerDescription> - </DrawerHeader> - - <div className="px-4"> - {formContent} - </div> - - <DrawerFooter className="gap-2 sm:space-x-0"> - <DrawerClose asChild> - <Button variant="outline">취소</Button> - </DrawerClose> - <Button - onClick={form.handleSubmit(onSubmit)} - disabled={isSubmitting || !isValid} - > - {isSubmitting && ( - <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> - )} - {vendors.length > 1 ? `${vendors.length}개 협력업체에 전송` : "전송"} - </Button> - </DrawerFooter> - </DrawerContent> - </Drawer> - ) -}
\ No newline at end of file |
