From b84621f9b2b7161a5ad4f0b194264e9df3e65dbf Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 8 Jul 2025 11:23:40 +0000 Subject: (대표님) 20250708 미반영분 커밋 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/b-rfq/initial/short-list-confirm-dialog.tsx | 269 ++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 lib/b-rfq/initial/short-list-confirm-dialog.tsx (limited to 'lib/b-rfq/initial/short-list-confirm-dialog.tsx') diff --git a/lib/b-rfq/initial/short-list-confirm-dialog.tsx b/lib/b-rfq/initial/short-list-confirm-dialog.tsx new file mode 100644 index 00000000..92c62dc0 --- /dev/null +++ b/lib/b-rfq/initial/short-list-confirm-dialog.tsx @@ -0,0 +1,269 @@ +"use client" + +import * as React from "react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import { z } from "zod" +import { Loader2, Building, CheckCircle2, XCircle } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Checkbox } from "@/components/ui/checkbox" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" +import { ScrollArea } from "@/components/ui/scroll-area" + +import { shortListConfirm } from "../service" +import { InitialRfqDetailView } from "@/db/schema" + +const shortListSchema = z.object({ + selectedVendorIds: z.array(z.number()).min(1, "최소 1개 이상의 벤더를 선택해야 합니다."), +}) + +type ShortListFormData = z.infer + +interface ShortListConfirmDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + rfqId: number + vendors: InitialRfqDetailView[] + onSuccess?: () => void +} + +export function ShortListConfirmDialog({ + open, + onOpenChange, + rfqId, + vendors, + onSuccess +}: ShortListConfirmDialogProps) { + const [isLoading, setIsLoading] = React.useState(false) + + const form = useForm({ + resolver: zodResolver(shortListSchema), + defaultValues: { + selectedVendorIds: vendors + .filter(vendor => vendor.shortList === true) + .map(vendor => vendor.vendorId) + .filter(Boolean) as number[] + }, + }) + + const watchedSelectedIds = form.watch("selectedVendorIds") + + // 선택된/탈락된 벤더 계산 + const selectedVendors = vendors.filter(vendor => + vendor.vendorId && watchedSelectedIds.includes(vendor.vendorId) + ) + const rejectedVendors = vendors.filter(vendor => + vendor.vendorId && !watchedSelectedIds.includes(vendor.vendorId) + ) + + async function onSubmit(data: ShortListFormData) { + if (!rfqId) return + + setIsLoading(true) + + try { + const result = await shortListConfirm({ + rfqId, + selectedVendorIds: data.selectedVendorIds, + rejectedVendorIds: vendors + .filter(v => v.vendorId && !data.selectedVendorIds.includes(v.vendorId)) + .map(v => v.vendorId!) + }) + + if (result.success) { + toast.success(result.message) + onOpenChange(false) + form.reset() + onSuccess?.() + } else { + toast.error(result.message || "Short List 확정에 실패했습니다.") + } + } catch (error) { + console.error("Short List confirm error:", error) + toast.error("Short List 확정 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + } + } + + const handleVendorToggle = (vendorId: number, checked: boolean) => { + const currentSelected = form.getValues("selectedVendorIds") + + if (checked) { + form.setValue("selectedVendorIds", [...currentSelected, vendorId]) + } else { + form.setValue("selectedVendorIds", currentSelected.filter(id => id !== vendorId)) + } + } + + return ( + + + + + + Short List 확정 + + + 최종 RFQ로 진행할 벤더를 선택해주세요. 선택되지 않은 벤더에게는 자동으로 Letter of Regret이 발송됩니다. + + + +
+ + ( + + + 벤더 선택 ({vendors.length}개 업체) + + + +
+ {vendors.map((vendor) => { + const isSelected = vendor.vendorId && watchedSelectedIds.includes(vendor.vendorId) + + return ( +
+ + vendor.vendorId && handleVendorToggle(vendor.vendorId, !!checked) + } + className="mt-1" + /> +
+
+ + {vendor.vendorName} + {isSelected ? ( + + 선택됨 + + ) : ( + + 탈락 + + )} +
+
+ {vendor.vendorCode} + {vendor.vendorCountry && ( + <> + + {vendor.vendorCountry === "KR" ? "국내" : "해외"} + + )} + {vendor.vendorCategory && ( + <> + + {vendor.vendorCategory} + + )} + {vendor.vendorBusinessSize && ( + <> + + {vendor.vendorBusinessSize} + + )} +
+
+ RFQ 상태: + {vendor.initialRfqStatus} + +
+
+
+ ) + })} +
+
+
+ +
+ )} + /> + + {/* 요약 정보 */} +
+
+
+ + 선택된 벤더 +
+
+ {selectedVendors.length}개 업체 +
+ {selectedVendors.length > 0 && ( +
+ {selectedVendors.map(v => v.vendorName).join(", ")} +
+ )} +
+
+
+ + 탈락 벤더 +
+
+ {rejectedVendors.length}개 업체 +
+ {rejectedVendors.length > 0 && ( +
+ Letter of Regret 발송 예정 +
+ )} +
+
+ + + + + + + +
+
+ ) +} \ No newline at end of file -- cgit v1.2.3