From 2fc9e5492e220041ba322d9a1479feb7803228cf Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 29 Oct 2025 06:20:56 +0000
Subject: (최겸) 구매 PQ수정, 정규업체 결재 개발(진행중)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/vendors/table/request-pq-dialog.tsx | 277 ++++++++++++++++++--------------
 1 file changed, 157 insertions(+), 120 deletions(-)

(limited to 'lib/vendors/table/request-pq-dialog.tsx')

diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx
index 2f39cae1..07057dbe 100644
--- a/lib/vendors/table/request-pq-dialog.tsx
+++ b/lib/vendors/table/request-pq-dialog.tsx
@@ -46,10 +46,11 @@ import { useSession } from "next-auth/react"
 import { DatePicker } from "@/components/ui/date-picker"
 import { getALLBasicContractTemplates } from "@/lib/basic-contract/service"
 import type { BasicContractTemplate } from "@/db/schema"
-import { searchItemsForPQ } from "@/lib/items/service"
-import { saveNdaAttachments } from "../service"
+import { saveNdaAttachments, getVendorPQHistory } from "../service"
 import { useRouter } from "next/navigation"
 import { createGtcVendorDocuments, createProjectGtcVendorDocuments, getStandardGtcDocumentId, getProjectGtcDocumentId } from "@/lib/gtc-contract/service"
+import { MaterialGroupSelectorDialogMulti } from "@/components/common/material/material-group-selector-dialog-multi"
+import type { MaterialSearchItem } from "@/lib/material/material-group-service"
 // import { PQContractViewer } from "../pq-contract-viewer" // 더 이상 사용하지 않음
 
 interface RequestPQDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> {
@@ -69,11 +70,7 @@ interface RequestPQDialogProps extends React.ComponentPropsWithoutRef<typeof Dia
 //   "GTC 합의",
 // ]
 
-// PQ 대상 품목 타입 정의
-interface PQItem {
-  itemCode: string
-  itemName: string
-}
+// PQ 대상 품목 타입 정의 (Material Group 기반) - MaterialSearchItem 사용
 
 export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...props }: RequestPQDialogProps) {
   const [isApprovePending, startApproveTransition] = React.useTransition()
@@ -86,12 +83,9 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
   const [selectedProjectId, setSelectedProjectId] = React.useState<number | null>(null)
   const [agreements, setAgreements] = React.useState<Record<string, boolean>>({})
   const [extraNote, setExtraNote] = React.useState<string>("")
-  const [pqItems, setPqItems] = React.useState<PQItem[]>([])
+  const [pqItems, setPqItems] = React.useState<MaterialSearchItem[]>([])
   
-  // 아이템 검색 관련 상태
-  const [itemSearchQuery, setItemSearchQuery] = React.useState<string>("")
-  const [filteredItems, setFilteredItems] = React.useState<PQItem[]>([])
-  const [showItemDropdown, setShowItemDropdown] = React.useState<boolean>(false)
+  // PQ 품목 선택 관련 상태는 MaterialGroupSelectorDialogMulti에서 관리됨
   const [isLoadingProjects, setIsLoadingProjects] = React.useState(false)
   const [basicContractTemplates, setBasicContractTemplates] = React.useState<BasicContractTemplate[]>([])
   const [selectedTemplateIds, setSelectedTemplateIds] = React.useState<number[]>([])
@@ -106,31 +100,10 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
   const [currentStep, setCurrentStep] = React.useState("")
   const [showProgress, setShowProgress] = React.useState(false)
 
-  // 아이템 검색 필터링
-  React.useEffect(() => {
-    if (itemSearchQuery.trim() === "") {
-      setFilteredItems([])
-      setShowItemDropdown(false)
-      return
-    }
+  // PQ 히스토리 관련 상태
+  const [pqHistory, setPqHistory] = React.useState<Record<number, any[]>>({})
+  const [isLoadingHistory, setIsLoadingHistory] = React.useState(false)
 
-    const searchItems = async () => {
-      try {
-        const results = await searchItemsForPQ(itemSearchQuery)
-        setFilteredItems(results)
-        setShowItemDropdown(true)
-      } catch (error) {
-        console.error("아이템 검색 오류:", error)
-        toast.error("아이템 검색 중 오류가 발생했습니다.")
-        setFilteredItems([])
-        setShowItemDropdown(false)
-      }
-    }
-
-    // 디바운싱: 300ms 후에 검색 실행
-    const timeoutId = setTimeout(searchItems, 300)
-    return () => clearTimeout(timeoutId)
-  }, [itemSearchQuery])
 
   React.useEffect(() => {
     if (type === "PROJECT") {
@@ -140,9 +113,37 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
     }
   }, [type])
 
-  // 기본계약서 템플릿 로딩 및 자동 선택
+  // 기본계약서 템플릿 로딩 및 자동 선택, PQ 히스토리 로딩
   React.useEffect(() => {
     setIsLoadingTemplates(true)
+    const loadPQHistory = async () => {
+      if (vendors.length === 0) return
+
+      setIsLoadingHistory(true)
+      try {
+        const historyPromises = vendors.map(async (vendor) => {
+          console.log("vendor.id", vendor.id)
+          const result = await getVendorPQHistory(vendor.id)
+          console.log("result", result)
+          return { vendorId: vendor.id, history: result.success ? result.data : [] }
+        })
+
+        const results = await Promise.all(historyPromises)
+        const historyMap: Record<number, any[]> = {}
+
+        results.forEach(({ vendorId, history }) => {
+          historyMap[vendorId] = history
+        })
+
+        setPqHistory(historyMap)
+      } catch (error) {
+        console.error('PQ 히스토리 로딩 실패:', error)
+        toast.error('PQ 히스토리 로딩 중 오류가 발생했습니다')
+      } finally {
+        setIsLoadingHistory(false)
+      }
+    }
+    loadPQHistory()
     getALLBasicContractTemplates()
       .then((templates) => {
 
@@ -213,37 +214,24 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
       setPqItems([])
       setExtraNote("")
       setSelectedTemplateIds([])
-      setItemSearchQuery("")
-      setFilteredItems([])
-      setShowItemDropdown(false)
       setNdaAttachments([])
       setIsUploadingNdaFiles(false)
       setProgressValue(0)
       setCurrentStep("")
       setShowProgress(false)
+      setPqHistory({})
+      setIsLoadingHistory(false)
     }
   }, [props.open])
 
-  // 아이템 선택 함수
-  const handleSelectItem = (item: PQItem) => {
-    // 이미 선택된 아이템인지 확인
-    const isAlreadySelected = pqItems.some(selectedItem => 
-      selectedItem.itemCode === item.itemCode
-    )
-    
-    if (!isAlreadySelected) {
-      setPqItems(prev => [...prev, item])
-    }
-    
-    // 검색 초기화
-    setItemSearchQuery("")
-    setFilteredItems([])
-    setShowItemDropdown(false)
+  // PQ 품목 선택 함수 (MaterialGroupSelectorDialogMulti에서 호출됨)
+  const handlePQItemsChange = (items: MaterialSearchItem[]) => {
+    setPqItems(items)
   }
 
-  // 아이템 제거 함수
-  const handleRemoveItem = (itemCode: string) => {
-    setPqItems(prev => prev.filter(item => item.itemCode !== itemCode))
+  // PQ 품목 제거 함수
+  const handleRemovePQItem = (materialGroupCode: string) => {
+    setPqItems(prev => prev.filter(item => item.materialGroupCode !== materialGroupCode))
   }
 
   // 비밀유지 계약서 첨부파일 추가 함수
@@ -274,6 +262,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
     if (!type) return toast.error("PQ 유형을 선택하세요.")
     if (type === "PROJECT" && !selectedProjectId) return toast.error("프로젝트를 선택하세요.")
     if (!dueDate) return toast.error("마감일을 선택하세요.")
+    if (pqItems.length === 0) return toast.error("PQ 대상 품목을 선택하세요.")
     if (!session?.user?.id) return toast.error("인증 실패")
 
     // GTC 템플릿 선택 검증
@@ -317,7 +306,10 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
           projectId: type === "PROJECT" ? selectedProjectId : null,
           type: type || "GENERAL",
           extraNote,
-          pqItems: JSON.stringify(pqItems),
+          pqItems: JSON.stringify(pqItems.map(item => ({
+            materialGroupCode: item.materialGroupCode,
+            materialGroupDescription: item.materialGroupDescription
+          }))),
           templateId: selectedTemplateIds.length > 0 ? selectedTemplateIds[0] : null,
         })
 
@@ -660,9 +652,97 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
     toast.error(`기본계약서 이메일 발송 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`)
   }
 }
-    
 
-    
+  // PQ 히스토리 컴포넌트
+  const PQHistorySection = () => {
+    if (isLoadingHistory) {
+      return (
+        <div className="px-4 py-3 border-b bg-muted/30">
+          <div className="flex items-center gap-2 text-sm text-muted-foreground">
+            <Loader className="h-4 w-4 animate-spin" />
+            PQ 히스토리 로딩 중...
+          </div>
+        </div>
+      )
+    }
+
+    const hasAnyHistory = Object.values(pqHistory).some(history => history.length > 0)
+
+    if (!hasAnyHistory) {
+      return (
+        <div className="px-4 py-3 border-b bg-muted/30">
+          <div className="text-sm text-muted-foreground">
+            최근 PQ 요청 내역이 없습니다.
+          </div>
+        </div>
+      )
+    }
+
+    return (
+      <div className="px-4 py-3 border-b bg-muted/30 max-h-48 overflow-y-auto">
+        <div className="space-y-3">
+          <div className="text-sm font-medium text-muted-foreground">
+            최근 PQ 요청 내역
+          </div>
+          {vendors.map((vendor) => {
+            const vendorHistory = pqHistory[vendor.id] || []
+            if (vendorHistory.length === 0) return null
+
+            return (
+              <div key={vendor.id} className="space-y-2">
+                <div className="text-xs font-medium text-muted-foreground border-b pb-1">
+                  {vendor.vendorName}
+                </div>
+                <div className="space-y-1">
+                  {vendorHistory.slice(0, 3).map((pq) => {
+                    const createdDate = new Date(pq.createdAt).toLocaleDateString('ko-KR')
+                    const statusText =
+                      pq.status === 'REQUESTED' ? '요청됨' :
+                      pq.status === 'APPROVED' ? '승인됨' :
+                      pq.status === 'SUBMITTED' ? '제출됨' :
+                      pq.status === 'REJECTED' ? '거절됨' :
+                      pq.status
+
+                    return (
+                      <div key={pq.id} className="flex items-center justify-between text-xs bg-background rounded px-2 py-1">
+                        <div className="flex items-center gap-2 flex-1">
+                          <button
+                            type="button"
+                            onClick={() => router.push(`/evcp/pq_new?search=${encodeURIComponent(pq.pqNumber)}`)}
+                            className="font-mono text-blue-600 hover:text-blue-800 hover:underline cursor-pointer"
+                          >
+                            {pq.pqNumber}
+                          </button>
+                          <Badge variant={pq.status === 'SUBMITTED' ? 'default' : pq.status === 'COMPLETED' ? 'default' : 'outline'} className="text-xs">
+                            {statusText}
+                          </Badge>
+                        </div>
+                        <div className="text-right">
+                          <div className="text-muted-foreground">
+                            {pq.type === 'GENERAL' ? '일반' : pq.type === 'PROJECT' ? '프로젝트' : '미실사'}
+                          </div>
+                          <div className="text-muted-foreground text-xs">
+                            {createdDate}
+                          </div>
+                        </div>
+                      </div>
+                    )
+                  })}
+                  {vendorHistory.length > 3 && (
+                    <div className="text-xs text-muted-foreground text-center">
+                      외 {vendorHistory.length - 3}건 더 있음
+                    </div>
+                  )}
+                </div>
+              </div>
+            )
+          })}
+        </div>
+      </div>
+    )
+  }
+
+
 
   const dialogContent = (
     <div className="space-y-4 py-2">
@@ -734,68 +814,23 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
 
       {/* PQ 대상품목 */}
       <div className="space-y-2">
-        <Label>PQ 대상품목</Label>
-        
-        {/* 선택된 아이템들 표시 */}
+        <Label>PQ 대상품목 *</Label>
+          <br />
+        <MaterialGroupSelectorDialogMulti
+          triggerLabel="자재 그룹 선택"
+          selectedMaterials={pqItems}
+          onMaterialsSelect={handlePQItemsChange}
+          maxSelections={10}
+          placeholder="PQ 대상 자재 그룹을 검색하세요"
+          title="PQ 대상 자재 그룹 선택"
+          description="PQ를 요청할 자재 그룹을 선택해주세요."
+        />
+
         {pqItems.length > 0 && (
-          <div className="flex flex-wrap gap-2 mb-2">
-            {pqItems.map((item) => (
-              <Badge key={item.itemCode} variant="secondary" className="flex items-center gap-1">
-                <span className="text-xs">
-                  {item.itemCode} - {item.itemName}
-                </span>
-                <Button
-                  type="button"
-                  variant="ghost"
-                  size="sm"
-                  className="h-4 w-4 p-0 hover:bg-destructive hover:text-destructive-foreground"
-                  onClick={() => handleRemoveItem(item.itemCode)}
-                >
-                  <X className="h-3 w-3" />
-                </Button>
-              </Badge>
-            ))}
+          <div className="text-xs text-muted-foreground">
+            {pqItems.length}개 자재 그룹이 선택되었습니다.
           </div>
         )}
-        
-        {/* 검색 입력 */}
-        <div className="relative">
-          <div className="relative">
-            <Input
-              placeholder="아이템 코드 또는 이름으로 검색하세요"
-              value={itemSearchQuery}
-              onChange={(e) => setItemSearchQuery(e.target.value)}
-              className="pl-9"
-            />
-          </div>
-          
-          {/* 검색 결과 드롭다운 */}
-          {showItemDropdown && (
-            <div className="absolute top-full left-0 right-0 z-50 mt-1 max-h-48 overflow-y-auto bg-background border rounded-md shadow-lg">
-              {filteredItems.length > 0 ? (
-                filteredItems.map((item) => (
-                  <button
-                    key={item.itemCode}
-                    type="button"
-                    className="w-full px-3 py-2 text-left text-sm hover:bg-muted focus:bg-muted focus:outline-none"
-                    onClick={() => handleSelectItem(item)}
-                  >
-                    <div className="font-medium">{item.itemCode}</div>
-                    <div className="text-muted-foreground text-xs">{item.itemName}</div>
-                  </button>
-                ))
-              ) : (
-                <div className="px-3 py-2 text-sm text-muted-foreground">
-                  검색 결과가 없습니다.
-                </div>
-              )}
-            </div>
-          )}
-        </div>
-        
-        <div className="text-xs text-muted-foreground">
-          아이템 코드나 이름을 입력하여 검색하고 선택하세요. (선택사항)
-        </div>
       </div>
 
       {/* 추가 안내사항 */}
@@ -957,6 +992,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
               {vendors.length === 1 ? "개 협력업체" : "개 협력업체들"}에게 PQ를 요청합니다.
             </DialogDescription>
           </DialogHeader>
+          <PQHistorySection />
           <div className="flex-1 overflow-y-auto">
             {dialogContent}
           </div>
@@ -1010,6 +1046,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro
             {vendors.length === 1 ? "개 협력업체" : "개 협력업체들"}에게 PQ를 요청합니다.
           </DrawerDescription>
         </DrawerHeader>
+        <PQHistorySection />
         <div className="flex-1 overflow-y-auto px-4">
           {dialogContent}
         </div>
-- 
cgit v1.2.3

