diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-12-09 05:31:04 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-12-09 05:31:04 +0000 |
| commit | 3462d754574e2558c791c7958d3e5da013a7a573 (patch) | |
| tree | 6eb26fddda5e4081fdead977c0ec6b152286d164 /components/pq-input/pq-review-wrapper.tsx | |
| parent | 3f11179b2c50d7ee56b0cea38778191e3259b941 (diff) | |
(최겸) 구매 pq 내 안전 담당자 평가 기능 추가, vendor 안전적격성 평가 컬럼 추가
Diffstat (limited to 'components/pq-input/pq-review-wrapper.tsx')
| -rw-r--r-- | components/pq-input/pq-review-wrapper.tsx | 326 |
1 files changed, 188 insertions, 138 deletions
diff --git a/components/pq-input/pq-review-wrapper.tsx b/components/pq-input/pq-review-wrapper.tsx index efb078e0..ac9629cb 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, Download } from "lucide-react" import { PQGroupData } from "@/lib/pq/service" -import { approvePQAction, rejectPQAction, updateSHICommentAction, approveQMReviewAction, rejectQMReviewAction, requestPqSupplementAction } from "@/lib/pq/service" +import { approvePQAction, rejectPQAction, updateSHICommentAction, approveQMReviewAction, rejectQMReviewAction, requestPqSupplementAction, approveSafetyPQAction, rejectSafetyPQAction } from "@/lib/pq/service" import { FileList, FileListHeader, FileListInfo, FileListItem, FileListName, FileListDescription, FileListAction, FileListIcon } from "@/components/ui/file-list" // import * as ExcelJS from 'exceljs'; // import { saveAs } from "file-saver"; @@ -62,15 +62,20 @@ export function PQReviewWrapper({ }: PQReviewWrapperProps) { const router = useRouter() const { toast } = useToast() + const [isSafetyApproving, setIsSafetyApproving] = React.useState(false) + const [isSafetyRejecting, setIsSafetyRejecting] = React.useState(false) 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 [showSafetyApproveDialog, setShowSafetyApproveDialog] = React.useState(false) + const [showSafetyRejectDialog, setShowSafetyRejectDialog] = 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 [showSupplementDialog, setShowSupplementDialog] = React.useState(false) + const [safetyRejectReason, setSafetyRejectReason] = React.useState("") const [rejectReason, setRejectReason] = React.useState("") const [qmRejectReason, setQmRejectReason] = React.useState("") const [supplementComment, setSupplementComment] = React.useState("") @@ -140,6 +145,87 @@ export function PQReviewWrapper({ setShiComments(initialComments) }, [pqData]) + // 안전 PQ 승인 처리 + const handleSafetyApprove = async () => { + try { + setIsSafetyApproving(true) + const result = await approveSafetyPQAction({ + pqSubmissionId: pqSubmission.id, + vendorId: vendorId, + }) + + if (result.ok) { + toast({ + title: "안전 PQ 승인 완료", + description: "안전 검토가 승인되었습니다.", + }) + router.refresh() + } else { + toast({ + title: "안전 승인 실패", + description: result.error || "안전 PQ 승인 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + } catch (error) { + console.error("안전 PQ 승인 오류:", error) + toast({ + title: "안전 승인 실패", + description: "안전 PQ 승인 중 오류가 발생했습니다.", + variant: "destructive", + }) + } finally { + setIsSafetyApproving(false) + setShowSafetyApproveDialog(false) + } + } + + // 안전 PQ 거절 처리 + const handleSafetyReject = async () => { + if (!safetyRejectReason.trim()) { + toast({ + title: "거절 사유 필요", + description: "안전 거절 사유를 입력해주세요.", + variant: "destructive", + }) + return + } + + try { + setIsSafetyRejecting(true) + const result = await rejectSafetyPQAction({ + pqSubmissionId: pqSubmission.id, + vendorId: vendorId, + rejectReason: safetyRejectReason, + }) + + if (result.ok) { + toast({ + title: "안전 PQ 거절 완료", + description: "안전 검토에서 거절되었습니다.", + }) + router.refresh() + } else { + toast({ + title: "안전 거절 실패", + description: result.error || "안전 PQ 거절 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + } catch (error) { + console.error("안전 PQ 거절 오류:", error) + toast({ + title: "안전 거절 실패", + description: "안전 PQ 거절 중 오류가 발생했습니다.", + variant: "destructive", + }) + } finally { + setSafetyRejectReason("") + setIsSafetyRejecting(false) + setShowSafetyRejectDialog(false) + } + } + // PQ 승인 처리 const handleApprove = async () => { try { @@ -327,141 +413,6 @@ export function PQReviewWrapper({ } } - // // Excel export 처리 - // const handleExportToExcel = async () => { - // try { - // setIsExporting(true) - - // // 워크북 생성 - // const workbook = new ExcelJS.Workbook() - // workbook.creator = 'PQ Management System' - // workbook.created = new Date() - - // // 메인 시트 생성 - // const worksheet = workbook.addWorksheet("PQ 항목") - - // // 헤더 정의 - // const headers = [ - // "그룹명", - // "코드", - // "체크포인트", - // "설명", - // "입력형식", - // "필수여부", - // "벤더답변", - // "SHI 코멘트", - // "벤더 답변", - // ] - - // // 헤더 추가 - // worksheet.addRow(headers) - - // // 헤더 스타일 적용 - // const headerRow = worksheet.getRow(1) - // headerRow.font = { bold: true } - // headerRow.fill = { - // type: 'pattern', - // pattern: 'solid', - // fgColor: { argb: 'FFE0E0E0' } - // } - // headerRow.alignment = { vertical: 'middle', horizontal: 'center' } - - // // 컬럼 너비 설정 - // worksheet.columns = [ - // { header: "그룹명", key: "groupName", width: 15 }, - // { header: "코드", key: "code", width: 12 }, - // { header: "체크포인트", key: "checkPoint", width: 30 }, - // { header: "설명", key: "description", width: 40 }, - // { header: "입력형식", key: "inputFormat", width: 12 }, - - // { header: "벤더답변", key: "answer", width: 30 }, - // { header: "SHI 코멘트", key: "shiComment", width: 30 }, - // { header: "벤더 답변", key: "vendorReply", width: 30 }, - // ] - - // // 데이터 추가 - // pqData.forEach(group => { - // group.items.forEach(item => { - // const rowData = [ - // group.groupName, - // item.code, - // item.checkPoint, - // item.description || "", - // item.inputFormat || "", - - // item.answer || "", - // item.shiComment || "", - // item.vendorReply || "", - // ] - // worksheet.addRow(rowData) - // }) - // }) - - // // 전체 셀에 테두리 추가 - // worksheet.eachRow((row, rowNumber) => { - // row.eachCell((cell) => { - // cell.border = { - // top: { style: 'thin' }, - // left: { style: 'thin' }, - // bottom: { style: 'thin' }, - // right: { style: 'thin' } - // } - // // 긴 텍스트는 자동 줄바꿈 - // cell.alignment = { - // vertical: 'top', - // horizontal: 'left', - // wrapText: true - // } - // }) - // }) - - // // 정보 시트 생성 - // const infoSheet = workbook.addWorksheet("정보") - // infoSheet.addRow(["벤더명", pqSubmission.vendorName]) - // if (pqSubmission.projectName) { - // infoSheet.addRow(["프로젝트명", pqSubmission.projectName]) - // } - // infoSheet.addRow(["생성일", new Date().toLocaleDateString('ko-KR')]) - // infoSheet.addRow(["총 항목 수", pqData.reduce((total, group) => total + group.items.length, 0)]) - - // // 정보 시트 스타일링 - // infoSheet.columns = [ - // { header: "항목", key: "item", width: 20 }, - // { header: "값", key: "value", width: 40 } - // ] - - // const infoHeaderRow = infoSheet.getRow(1) - // infoHeaderRow.font = { bold: true } - // infoHeaderRow.fill = { - // type: 'pattern', - // pattern: 'solid', - // fgColor: { argb: 'FFE6F3FF' } - // } - - // // 파일명 생성 - // const defaultFilename = pqSubmission.projectName - // ? `${pqSubmission.vendorName}_${pqSubmission.projectName}_PQ_${new Date().toISOString().slice(0, 10)}` - // : `${pqSubmission.vendorName}_PQ_${new Date().toISOString().slice(0, 10)}` - // const finalFilename = defaultFilename - - // // 파일 다운로드 - // const buffer = await workbook.xlsx.writeBuffer() - // const blob = new Blob([buffer], { - // type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - // }) - // saveAs(blob, `${finalFilename}.xlsx`) - // } catch (error) { - // console.error("Excel export 오류:", error) - // toast({ - // title: "내보내기 실패", - // description: "Excel 내보내기 중 오류가 발생했습니다.", - // variant: "destructive" - // }) - // } finally { - // setIsExporting(false) - // } - // } - // PQ 거부 처리 const handleReject = async () => { if (!rejectReason.trim()) { @@ -839,15 +790,42 @@ export function PQReviewWrapper({ {/* 검토 버튼 - 상태에 따라 다른 버튼 표시 */} <div className="fixed bottom-4 right-4 bg-background p-4 rounded-lg shadow-md border"> <div className="flex gap-2"> - {/* SUBMITTED 상태: 구매 담당자 승인/거절 */} + {/* SUBMITTED 상태: 안전 담당자 승인/거절 */} {pqSubmission.status === "SUBMITTED" && ( <> <Button variant="outline" + onClick={() => setShowSafetyRejectDialog(true)} + disabled={isSafetyRejecting} + > + {isSafetyRejecting ? "안전 거절 중..." : "안전 거절"} + </Button> + <Button + variant="secondary" + onClick={() => setShowSupplementDialog(true)} + disabled={isSendingSupplement} + > + 보완요청 + </Button> + <Button + variant="default" + onClick={() => setShowSafetyApproveDialog(true)} + disabled={isSafetyApproving} + > + {isSafetyApproving ? "안전 승인 중..." : "안전 승인"} + </Button> + </> + )} + + {/* SAFETY_APPROVED 상태: 구매 담당자 승인/거절 */} + {pqSubmission.status === "SAFETY_APPROVED" && ( + <> + <Button + variant="outline" onClick={() => setShowRejectDialog(true)} disabled={isRejecting} > - {isRejecting ? "거부 중..." : "거부"} + {isRejecting ? "거부 중..." : "구매 거부"} </Button> <Button variant="secondary" @@ -916,10 +894,82 @@ export function PQReviewWrapper({ <span>거절됨</span> </div> )} + + {/* SAFETY_REJECTED 상태: 안전 거절 표시 */} + {pqSubmission.status === "SAFETY_REJECTED" && ( + <div className="flex items-center gap-2 text-red-600"> + <AlertCircle className="h-4 w-4" /> + <span>안전 거절됨</span> + </div> + )} </div> </div> + {/* 안전 승인 확인 다이얼로그 */} + <Dialog open={showSafetyApproveDialog} onOpenChange={setShowSafetyApproveDialog}> + <DialogContent> + <DialogHeader> + <DialogTitle>안전 PQ 승인 확인</DialogTitle> + <DialogDescription> + {pqSubmission.vendorName || "알 수 없는 업체"}의 { + pqSubmission.type === "GENERAL" ? "일반" : + pqSubmission.type === "PROJECT" ? "프로젝트" : + pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반" + } PQ를 안전 검토에서 승인하시겠습니까? + {pqSubmission.projectId && ( + <span> 프로젝트: {pqSubmission.projectName}</span> + )} + </DialogDescription> + </DialogHeader> + <DialogFooter> + <Button variant="outline" onClick={() => setShowSafetyApproveDialog(false)}> + 취소 + </Button> + <Button onClick={handleSafetyApprove} disabled={isSafetyApproving}> + {isSafetyApproving ? "안전 승인 중..." : "안전 승인"} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + + {/* 안전 거부 확인 다이얼로그 */} + <Dialog open={showSafetyRejectDialog} onOpenChange={setShowSafetyRejectDialog}> + <DialogContent> + <DialogHeader> + <DialogTitle>안전 PQ 거부</DialogTitle> + <DialogDescription> + {pqSubmission.vendorName || "알 수 없는 업체"}의 { + pqSubmission.type === "GENERAL" ? "일반" : + pqSubmission.type === "PROJECT" ? "프로젝트" : + pqSubmission.type === "NON_INSPECTION" ? "미실사" : "일반" + } PQ를 안전 검토에서 거부하는 이유를 입력해주세요. + {pqSubmission.projectId && ( + <span> 프로젝트: {pqSubmission.projectName}</span> + )} + </DialogDescription> + </DialogHeader> + <Textarea + value={safetyRejectReason} + onChange={(e) => setSafetyRejectReason(e.target.value)} + placeholder="안전 거부 사유를 입력하세요" + className="min-h-24" + /> + <DialogFooter> + <Button variant="outline" onClick={() => setShowSafetyRejectDialog(false)}> + 취소 + </Button> + <Button + variant="destructive" + onClick={handleSafetyReject} + disabled={isSafetyRejecting || !safetyRejectReason.trim()} + > + {isSafetyRejecting ? "안전 거절 중..." : "안전 거절"} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + {/* 승인 확인 다이얼로그 */} <Dialog open={showApproveDialog} onOpenChange={setShowApproveDialog}> <DialogContent> |
