From 2fc9e5492e220041ba322d9a1479feb7803228cf Mon Sep 17 00:00:00 2001 From: dujinkim 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 { @@ -69,11 +70,7 @@ interface RequestPQDialogProps extends React.ComponentPropsWithoutRef(null) const [agreements, setAgreements] = React.useState>({}) const [extraNote, setExtraNote] = React.useState("") - const [pqItems, setPqItems] = React.useState([]) + const [pqItems, setPqItems] = React.useState([]) - // 아이템 검색 관련 상태 - const [itemSearchQuery, setItemSearchQuery] = React.useState("") - const [filteredItems, setFilteredItems] = React.useState([]) - const [showItemDropdown, setShowItemDropdown] = React.useState(false) + // PQ 품목 선택 관련 상태는 MaterialGroupSelectorDialogMulti에서 관리됨 const [isLoadingProjects, setIsLoadingProjects] = React.useState(false) const [basicContractTemplates, setBasicContractTemplates] = React.useState([]) const [selectedTemplateIds, setSelectedTemplateIds] = React.useState([]) @@ -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>({}) + 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 = {} + + 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 ( +
+
+ + PQ 히스토리 로딩 중... +
+
+ ) + } + + const hasAnyHistory = Object.values(pqHistory).some(history => history.length > 0) + + if (!hasAnyHistory) { + return ( +
+
+ 최근 PQ 요청 내역이 없습니다. +
+
+ ) + } + + return ( +
+
+
+ 최근 PQ 요청 내역 +
+ {vendors.map((vendor) => { + const vendorHistory = pqHistory[vendor.id] || [] + if (vendorHistory.length === 0) return null + + return ( +
+
+ {vendor.vendorName} +
+
+ {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 ( +
+
+ + + {statusText} + +
+
+
+ {pq.type === 'GENERAL' ? '일반' : pq.type === 'PROJECT' ? '프로젝트' : '미실사'} +
+
+ {createdDate} +
+
+
+ ) + })} + {vendorHistory.length > 3 && ( +
+ 외 {vendorHistory.length - 3}건 더 있음 +
+ )} +
+
+ ) + })} +
+
+ ) + } + + const dialogContent = (
@@ -734,68 +814,23 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro {/* PQ 대상품목 */}
- - - {/* 선택된 아이템들 표시 */} + +
+ + {pqItems.length > 0 && ( -
- {pqItems.map((item) => ( - - - {item.itemCode} - {item.itemName} - - - - ))} +
+ {pqItems.length}개 자재 그룹이 선택되었습니다.
)} - - {/* 검색 입력 */} -
-
- setItemSearchQuery(e.target.value)} - className="pl-9" - /> -
- - {/* 검색 결과 드롭다운 */} - {showItemDropdown && ( -
- {filteredItems.length > 0 ? ( - filteredItems.map((item) => ( - - )) - ) : ( -
- 검색 결과가 없습니다. -
- )} -
- )} -
- -
- 아이템 코드나 이름을 입력하여 검색하고 선택하세요. (선택사항) -
{/* 추가 안내사항 */} @@ -957,6 +992,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro {vendors.length === 1 ? "개 협력업체" : "개 협력업체들"}에게 PQ를 요청합니다. +
{dialogContent}
@@ -1010,6 +1046,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro {vendors.length === 1 ? "개 협력업체" : "개 협력업체들"}에게 PQ를 요청합니다. +
{dialogContent}
-- cgit v1.2.3