From b84621f9b2b7161a5ad4f0b194264e9df3e65dbf Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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<typeof shortListSchema>
+
+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<ShortListFormData>({
+    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 (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-4xl max-h-[80vh]">
+        <DialogHeader>
+          <DialogTitle className="flex items-center gap-2">
+            <CheckCircle2 className="h-5 w-5 text-green-600" />
+            Short List 확정
+          </DialogTitle>
+          <DialogDescription>
+            최종 RFQ로 진행할 벤더를 선택해주세요. 선택되지 않은 벤더에게는 자동으로 Letter of Regret이 발송됩니다.
+          </DialogDescription>
+        </DialogHeader>
+
+        <Form {...form}>
+          <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
+            <FormField
+              control={form.control}
+              name="selectedVendorIds"
+              render={() => (
+                <FormItem>
+                  <FormLabel className="text-base font-semibold">
+                    벤더 선택 ({vendors.length}개 업체)
+                  </FormLabel>
+                  <FormControl>
+                    <ScrollArea className="h-[400px] border rounded-md p-4">
+                      <div className="space-y-4">
+                        {vendors.map((vendor) => {
+                          const isSelected = vendor.vendorId && watchedSelectedIds.includes(vendor.vendorId)
+                          
+                          return (
+                            <div
+                              key={vendor.vendorId}
+                              className={`flex items-start space-x-3 p-3 rounded-lg border transition-colors ${
+                                isSelected 
+                                  ? 'border-green-200 bg-green-50' 
+                                  : 'border-red-100 bg-red-50'
+                              }`}
+                            >
+                              <Checkbox
+                                checked={isSelected}
+                                onCheckedChange={(checked) => 
+                                  vendor.vendorId && handleVendorToggle(vendor.vendorId, !!checked)
+                                }
+                                className="mt-1"
+                              />
+                              <div className="flex-1 space-y-2">
+                                <div className="flex items-center gap-2">
+                                  <Building className="h-4 w-4 text-muted-foreground" />
+                                  <span className="font-medium">{vendor.vendorName}</span>
+                                  {isSelected ? (
+                                    <Badge variant="secondary" className="bg-green-100 text-green-800">
+                                      선택됨
+                                    </Badge>
+                                  ) : (
+                                    <Badge variant="secondary" className="bg-red-100 text-red-800">
+                                      탈락
+                                    </Badge>
+                                  )}
+                                </div>
+                                <div className="text-sm text-muted-foreground">
+                                  <span className="font-mono">{vendor.vendorCode}</span>
+                                  {vendor.vendorCountry && (
+                                    <>
+                                      <span className="mx-2">•</span>
+                                      <span>{vendor.vendorCountry === "KR" ? "국내" : "해외"}</span>
+                                    </>
+                                  )}
+                                  {vendor.vendorCategory && (
+                                    <>
+                                      <span className="mx-2">•</span>
+                                      <span>{vendor.vendorCategory}</span>
+                                    </>
+                                  )}
+                                  {vendor.vendorBusinessSize && (
+                                    <>
+                                      <span className="mx-2">•</span>
+                                      <span>{vendor.vendorBusinessSize}</span>
+                                    </>
+                                  )}
+                                </div>
+                                <div className="text-xs text-muted-foreground">
+                                  RFQ 상태: <Badge variant="outline" className="text-xs">
+                                    {vendor.initialRfqStatus}
+                                  </Badge>
+                                </div>
+                              </div>
+                            </div>
+                          )
+                        })}
+                      </div>
+                    </ScrollArea>
+                  </FormControl>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+
+            {/* 요약 정보 */}
+            <div className="grid grid-cols-2 gap-4 p-4 bg-muted/50 rounded-lg">
+              <div className="space-y-2">
+                <div className="flex items-center gap-2 text-green-700">
+                  <CheckCircle2 className="h-4 w-4" />
+                  <span className="font-medium">선택된 벤더</span>
+                </div>
+                <div className="text-2xl font-bold text-green-700">
+                  {selectedVendors.length}개 업체
+                </div>
+                {selectedVendors.length > 0 && (
+                  <div className="text-sm text-muted-foreground">
+                    {selectedVendors.map(v => v.vendorName).join(", ")}
+                  </div>
+                )}
+              </div>
+              <div className="space-y-2">
+                <div className="flex items-center gap-2 text-red-700">
+                  <XCircle className="h-4 w-4" />
+                  <span className="font-medium">탈락 벤더</span>
+                </div>
+                <div className="text-2xl font-bold text-red-700">
+                  {rejectedVendors.length}개 업체
+                </div>
+                {rejectedVendors.length > 0 && (
+                  <div className="text-sm text-muted-foreground">
+                    Letter of Regret 발송 예정
+                  </div>
+                )}
+              </div>
+            </div>
+
+            <DialogFooter>
+              <Button
+                type="button"
+                variant="outline"
+                onClick={() => onOpenChange(false)}
+                disabled={isLoading}
+              >
+                취소
+              </Button>
+              <Button
+                type="submit"
+                disabled={isLoading || selectedVendors.length === 0}
+              >
+                {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
+                Short List 확정
+              </Button>
+            </DialogFooter>
+          </form>
+        </Form>
+      </DialogContent>
+    </Dialog>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

