From 2eb717eb2bbfd97a5f149d13049aa336c26c393b Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 29 Oct 2025 07:43:44 +0000
Subject: (최겸) 구매 실사 개발(진행중)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 components/pq-input/pq-review-wrapper.tsx | 260 ++++++++++++++++++++++++++----
 1 file changed, 226 insertions(+), 34 deletions(-)

(limited to 'components/pq-input/pq-review-wrapper.tsx')

diff --git a/components/pq-input/pq-review-wrapper.tsx b/components/pq-input/pq-review-wrapper.tsx
index 1545314c..9b719644 100644
--- a/components/pq-input/pq-review-wrapper.tsx
+++ b/components/pq-input/pq-review-wrapper.tsx
@@ -23,7 +23,7 @@ import {
 import { useToast } from "@/hooks/use-toast"
 import { CheckCircle, AlertCircle, Paperclip, Square } from "lucide-react"
 import { PQGroupData } from "@/lib/pq/service"
-import { approvePQAction, rejectPQAction, updateSHICommentAction } from "@/lib/pq/service"
+import { approvePQAction, rejectPQAction, updateSHICommentAction, approveQMReviewAction, rejectQMReviewAction } from "@/lib/pq/service"
 // import * as ExcelJS from 'exceljs';
 // import { saveAs } from "file-saver";
 
@@ -61,9 +61,14 @@ export function PQReviewWrapper({
   const { toast } = useToast()
   const [isApproving, setIsApproving] = React.useState(false)
   const [isRejecting, setIsRejecting] = React.useState(false)
+  const [isQMApproving, setIsQMApproving] = React.useState(false)
+  const [isQMRejecting, setIsQMRejecting] = React.useState(false)
   const [showApproveDialog, setShowApproveDialog] = React.useState(false)
   const [showRejectDialog, setShowRejectDialog] = React.useState(false)
+  const [showQMApproveDialog, setShowQMApproveDialog] = React.useState(false)
+  const [showQMRejectDialog, setShowQMRejectDialog] = React.useState(false)
   const [rejectReason, setRejectReason] = React.useState("")
+  const [qmRejectReason, setQmRejectReason] = React.useState("")
   const [shiComments, setShiComments] = React.useState<Record<number, string>>({})
   const [isUpdatingComment, setIsUpdatingComment] = React.useState<number | null>(null)
 
@@ -103,7 +108,7 @@ export function PQReviewWrapper({
     setShiComments(initialComments)
   }, [pqData])
   
-  // PQ 승인 처리
+  // PQ 승인 처리 (구매 담당자)
   const handleApprove = async () => {
     try {
       setIsApproving(true)
@@ -116,7 +121,7 @@ export function PQReviewWrapper({
       if (result.ok) {
         toast({
           title: "PQ 승인 완료",
-          description: "PQ가 성공적으로 승인되었습니다.",
+          description: "PQ가 QM 검토 단계로 전환되었습니다.",
         })
         // 페이지 새로고침
         router.refresh()
@@ -139,6 +144,90 @@ export function PQReviewWrapper({
       setShowApproveDialog(false)
     }
   }
+
+  // QM 승인 처리
+  const handleQMApprove = async () => {
+    try {
+      setIsQMApproving(true)
+      
+      const result = await approveQMReviewAction({
+        pqSubmissionId: pqSubmission.id,
+        vendorId: vendorId
+      })
+      
+      if (result.ok) {
+        toast({
+          title: "QM 승인 완료",
+          description: "PQ가 최종 승인되어 실사 프로세스가 시작됩니다.",
+        })
+        // 페이지 새로고침
+        router.refresh()
+      } else {
+        toast({
+          title: "QM 승인 실패",
+          description: result.error || "QM 승인 중 오류가 발생했습니다.",
+          variant: "destructive"
+        })
+      }
+    } catch (error) {
+      console.error("QM 승인 오류:", error)
+      toast({
+        title: "QM 승인 실패",
+        description: "QM 승인 중 오류가 발생했습니다.",
+        variant: "destructive"
+      })
+    } finally {
+      setIsQMApproving(false)
+      setShowQMApproveDialog(false)
+    }
+  }
+
+  // QM 거절 처리
+  const handleQMReject = async () => {
+    if (!qmRejectReason.trim()) {
+      toast({
+        title: "거절 사유 필요",
+        description: "거절 사유를 입력해주세요.",
+        variant: "destructive"
+      })
+      return
+    }
+    
+    try {
+      setIsQMRejecting(true)
+      
+      const result = await rejectQMReviewAction({
+        pqSubmissionId: pqSubmission.id,
+        vendorId: vendorId,
+        rejectReason: qmRejectReason
+      })
+      
+      if (result.ok) {
+        toast({
+          title: "QM 거절 완료",
+          description: "PQ가 QM에 의해 거절되었습니다.",
+        })
+        // 페이지 새로고침
+        router.refresh()
+      } else {
+        toast({
+          title: "QM 거절 실패",
+          description: result.error || "QM 거절 중 오류가 발생했습니다.",
+          variant: "destructive"
+        })
+      }
+    } catch (error) {
+      console.error("QM 거절 오류:", error)
+      toast({
+        title: "QM 거절 실패",
+        description: "QM 거절 중 오류가 발생했습니다.",
+        variant: "destructive"
+      })
+    } finally {
+      setIsQMRejecting(false)
+      setShowQMRejectDialog(false)
+    }
+  }
   
   // SHI 코멘트 업데이트 처리
   const handleSHICommentUpdate = async (answerId: number) => {
@@ -643,48 +732,87 @@ export function PQReviewWrapper({
         </div>
       ))}
       
-      {/* 검토 버튼 */}
+      {/* 검토 버튼 - 상태에 따라 다른 버튼 표시 */}
         <div className="fixed bottom-4 right-4 bg-background p-4 rounded-lg shadow-md border">
           <div className="flex gap-2">
+            {/* SUBMITTED 상태: 구매 담당자 승인/거절 */}
+            {pqSubmission.status === "SUBMITTED" && (
+              <>
+                <Button
+                  variant="outline"
+                  onClick={() => setShowRejectDialog(true)}
+                  disabled={isRejecting}
+                >
+                  {isRejecting ? "거부 중..." : "거부"}
+                </Button>
+                <Button
+                  variant="default"
+                  onClick={() => setShowApproveDialog(true)}
+                  disabled={isApproving}
+                >
+                  {isApproving ? "승인 중..." : "구매 승인"}
+                </Button>
+              </>
+            )}
 
+            {/* QM_REVIEWING 상태: QM 승인/거절 */}
+            {pqSubmission.status === "QM_REVIEWING" && (
+              <>
+                <Button
+                  variant="outline"
+                  onClick={() => setShowQMRejectDialog(true)}
+                  disabled={isQMRejecting}
+                >
+                  {isQMRejecting ? "QM 거절 중..." : "QM 거절"}
+                </Button>
+                <Button
+                  variant="default"
+                  onClick={() => setShowQMApproveDialog(true)}
+                  disabled={isQMApproving}
+                >
+                  {isQMApproving ? "QM 승인 중..." : "QM 승인"}
+                </Button>
+              </>
+            )}
 
-            {/* <Button
-              variant="outline"
-              onClick={handleExportToExcel}
-              disabled={isExporting}
-            >
-              <Download className="h-4 w-4 mr-2" />
-              {isExporting ? "내보내기 중..." : "Excel 내보내기"}
-            </Button> */}
-            <Button
-              variant="outline"
-              onClick={() => setShowRejectDialog(true)}
-              disabled={isRejecting}
-            >
-              {isRejecting ? "거부 중..." : "거부"}
-            </Button>
-            <Button
-              variant="default"
-              onClick={() => setShowApproveDialog(true)}
-              disabled={isApproving}
-            >
-              {isApproving ? "승인 중..." : "승인"}
-            </Button>
+            {/* QM_APPROVED 상태: 완료 표시 */}
+            {pqSubmission.status === "QM_APPROVED" && (
+              <div className="flex items-center gap-2 text-green-600">
+                <CheckCircle className="h-4 w-4" />
+                <span>QM 승인 완료</span>
+              </div>
+            )}
+
+            {/* QM_REJECTED 상태: 거절 표시 */}
+            {pqSubmission.status === "QM_REJECTED" && (
+              <div className="flex items-center gap-2 text-red-600">
+                <AlertCircle className="h-4 w-4" />
+                <span>QM 거절됨</span>
+              </div>
+            )}
+
+            {/* REJECTED 상태: 거절 표시 */}
+            {pqSubmission.status === "REJECTED" && (
+              <div className="flex items-center gap-2 text-red-600">
+                <AlertCircle className="h-4 w-4" />
+                <span>거절됨</span>
+              </div>
+            )}
           </div>
         </div>
 
       
-      {/* 승인 확인 다이얼로그 */}
+      {/* 구매 승인 확인 다이얼로그 */}
       <Dialog open={showApproveDialog} onOpenChange={setShowApproveDialog}>
         <DialogContent>
           <DialogHeader>
-            <DialogTitle>PQ 승인 확인</DialogTitle>
+            <DialogTitle>PQ 구매 승인 확인</DialogTitle>
             <DialogDescription>
               {pqSubmission.vendorName || "알 수 없는 업체"}의 {
                 pqSubmission.type === "GENERAL" ? "일반" : 
                 pqSubmission.type === "PROJECT" ? "프로젝트" : 
                 pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반"
-              } PQ를 승인하시겠습니까?
+              } PQ를 구매 승인하여 QM 검토 단계로 전환하시겠습니까?
               {pqSubmission.projectId && (
                 <span> 프로젝트: {pqSubmission.projectName}</span>
               )}
@@ -695,23 +823,50 @@ export function PQReviewWrapper({
               취소
             </Button>
             <Button onClick={handleApprove} disabled={isApproving}>
-              {isApproving ? "승인 중..." : "승인"}
+              {isApproving ? "승인 중..." : "구매 승인"}
+            </Button>
+          </DialogFooter>
+        </DialogContent>
+      </Dialog>
+
+      {/* QM 승인 확인 다이얼로그 */}
+      <Dialog open={showQMApproveDialog} onOpenChange={setShowQMApproveDialog}>
+        <DialogContent>
+          <DialogHeader>
+            <DialogTitle>QM 승인 확인</DialogTitle>
+            <DialogDescription>
+              {pqSubmission.vendorName || "알 수 없는 업체"}의 {
+                pqSubmission.type === "GENERAL" ? "일반" : 
+                pqSubmission.type === "PROJECT" ? "프로젝트" : 
+                pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반"
+              } PQ를 QM에서 최종 승인하여 실사 프로세스를 시작하시겠습니까?
+              {pqSubmission.projectId && (
+                <span> 프로젝트: {pqSubmission.projectName}</span>
+              )}
+            </DialogDescription>
+          </DialogHeader>
+          <DialogFooter>
+            <Button variant="outline" onClick={() => setShowQMApproveDialog(false)}>
+              취소
+            </Button>
+            <Button onClick={handleQMApprove} disabled={isQMApproving}>
+              {isQMApproving ? "QM 승인 중..." : "QM 승인"}
             </Button>
           </DialogFooter>
         </DialogContent>
       </Dialog>
       
-      {/* 거부 확인 다이얼로그 */}
+      {/* 구매 거부 확인 다이얼로그 */}
       <Dialog open={showRejectDialog} onOpenChange={setShowRejectDialog}>
         <DialogContent>
           <DialogHeader>
-            <DialogTitle>PQ 거부</DialogTitle>
+            <DialogTitle>PQ 구매 거부</DialogTitle>
             <DialogDescription>
               {pqSubmission.vendorName || "알 수 없는 업체"}의 {
                 pqSubmission.type === "GENERAL" ? "일반" : 
                 pqSubmission.type === "PROJECT" ? "프로젝트" : 
                 pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반"
-              } PQ를 거부하는 이유를 입력해주세요.
+              } PQ를 구매에서 거부하는 이유를 입력해주세요.
               {pqSubmission.projectId && (
                 <span> 프로젝트: {pqSubmission.projectName}</span>
               )}
@@ -732,7 +887,44 @@ export function PQReviewWrapper({
               onClick={handleReject} 
               disabled={isRejecting || !rejectReason.trim()}
             >
-              {isRejecting ? "거부 중..." : "거부"}
+              {isRejecting ? "거부 중..." : "구매 거부"}
+            </Button>
+          </DialogFooter>
+        </DialogContent>
+      </Dialog>
+
+      {/* QM 거절 확인 다이얼로그 */}
+      <Dialog open={showQMRejectDialog} onOpenChange={setShowQMRejectDialog}>
+        <DialogContent>
+          <DialogHeader>
+            <DialogTitle>QM 거절</DialogTitle>
+            <DialogDescription>
+              {pqSubmission.vendorName || "알 수 없는 업체"}의 {
+                pqSubmission.type === "GENERAL" ? "일반" : 
+                pqSubmission.type === "PROJECT" ? "프로젝트" : 
+                pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반"
+              } PQ를 QM에서 거절하는 이유를 입력해주세요.
+              {pqSubmission.projectId && (
+                <span> 프로젝트: {pqSubmission.projectName}</span>
+              )}
+            </DialogDescription>
+          </DialogHeader>
+          <Textarea
+            value={qmRejectReason}
+            onChange={(e) => setQmRejectReason(e.target.value)}
+            placeholder="QM 거절 사유를 입력하세요"
+            className="min-h-24"
+          />
+          <DialogFooter>
+            <Button variant="outline" onClick={() => setShowQMRejectDialog(false)}>
+              취소
+            </Button>
+            <Button 
+              variant="destructive" 
+              onClick={handleQMReject} 
+              disabled={isQMRejecting || !qmRejectReason.trim()}
+            >
+              {isQMRejecting ? "QM 거절 중..." : "QM 거절"}
             </Button>
           </DialogFooter>
         </DialogContent>
-- 
cgit v1.2.3

