"use client" import * as React from "react" import { useRouter } from "next/navigation" import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Textarea } from "@/components/ui/textarea" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" 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 * as ExcelJS from 'exceljs'; // import { saveAs } from "file-saver"; // PQ 제출 정보 타입 interface PQSubmission { id: number vendorId: number vendorName: string | null vendorCode: string | null type: string status: string projectId: number | null projectName: string | null projectCode: string | null submittedAt: Date | null approvedAt: Date | null rejectedAt: Date | null rejectReason: string | null } interface PQReviewWrapperProps { pqData: PQGroupData[] vendorId: number pqSubmission: PQSubmission vendorInfo?: any // 협력업체 정보 (선택사항) } export function PQReviewWrapper({ pqData, vendorId, pqSubmission, vendorInfo }: PQReviewWrapperProps) { const router = useRouter() const { toast } = useToast() const [isApproving, setIsApproving] = React.useState(false) const [isRejecting, setIsRejecting] = React.useState(false) const [showApproveDialog, setShowApproveDialog] = React.useState(false) const [showRejectDialog, setShowRejectDialog] = React.useState(false) const [rejectReason, setRejectReason] = React.useState("") const [shiComments, setShiComments] = React.useState>({}) const [isUpdatingComment, setIsUpdatingComment] = React.useState(null) // 코드 순서로 정렬하는 함수 (1-1-1, 1-1-2, 1-2-1 순서) const sortByCode = (items: any[]) => { return [...items].sort((a, b) => { const parseCode = (code: string) => { return code.split('-').map(part => parseInt(part, 10)) } const aCode = parseCode(a.code) const bCode = parseCode(b.code) for (let i = 0; i < Math.max(aCode.length, bCode.length); i++) { const aPart = aCode[i] || 0 const bPart = bCode[i] || 0 if (aPart !== bPart) { return aPart - bPart } } return 0 }) } // 기존 SHI 코멘트를 로컬 상태에 초기화 React.useEffect(() => { const initialComments: Record = {} pqData.forEach(group => { group.items.forEach(item => { if (item.answerId && item.shiComment) { initialComments[item.answerId] = item.shiComment } }) }) setShiComments(initialComments) }, [pqData]) // PQ 승인 처리 const handleApprove = async () => { try { setIsApproving(true) const result = await approvePQAction({ pqSubmissionId: pqSubmission.id, vendorId: vendorId }) if (result.ok) { toast({ title: "PQ 승인 완료", description: "PQ가 성공적으로 승인되었습니다.", }) // 페이지 새로고침 router.refresh() } else { toast({ title: "승인 실패", description: result.error || "PQ 승인 중 오류가 발생했습니다.", variant: "destructive" }) } } catch (error) { console.error("PQ 승인 오류:", error) toast({ title: "승인 실패", description: "PQ 승인 중 오류가 발생했습니다.", variant: "destructive" }) } finally { setIsApproving(false) setShowApproveDialog(false) } } // SHI 코멘트 업데이트 처리 const handleSHICommentUpdate = async (answerId: number) => { const comment = shiComments[answerId] || "" try { setIsUpdatingComment(answerId) const result = await updateSHICommentAction({ answerId, shiComment: comment, }) if (result.ok) { toast({ title: "SHI 코멘트 저장 완료", description: "SHI 코멘트가 저장되었습니다.", }) // 페이지 새로고침 router.refresh() } else { toast({ title: "저장 실패", description: result.error || "SHI 코멘트 저장 중 오류가 발생했습니다.", variant: "destructive" }) } } catch (error) { console.error("SHI 코멘트 저장 오류:", error) toast({ title: "저장 실패", description: "SHI 코멘트 저장 중 오류가 발생했습니다.", variant: "destructive" }) } finally { setIsUpdatingComment(null) } } // // 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()) { toast({ title: "거부 사유 필요", description: "거부 사유를 입력해주세요.", variant: "destructive" }) return } try { setIsRejecting(true) const result = await rejectPQAction({ pqSubmissionId: pqSubmission.id, vendorId: vendorId, rejectReason: rejectReason }) if (result.ok) { toast({ title: "PQ 거부 완료", description: "PQ가 거부되었습니다.", }) // 페이지 새로고침 router.refresh() } else { toast({ title: "거부 실패", description: result.error || "PQ 거부 중 오류가 발생했습니다.", variant: "destructive" }) } } catch (error) { console.error("PQ 거부 오류:", error) toast({ title: "거부 실패", description: "PQ 거부 중 오류가 발생했습니다.", variant: "destructive" }) } finally { setIsRejecting(false) setShowRejectDialog(false) } } return (
{/* 그룹별 PQ 항목 표시 */} {pqData.map((group) => (

{group.groupName}

{sortByCode(group.items).map((item) => (
{item.code} - {item.checkPoint} {item.description && ( {item.description} )} {item.remarks && (

Remark:

{item.remarks}

)}
{/* 항목 상태 표시 */} {!!item.answer || item.attachments.length > 0 ? ( 답변 있음 ) : ( 답변 없음 )}
{/* 프로젝트별 추가 정보 */} {pqSubmission.projectId && item.contractInfo && (

계약 정보

{item.contractInfo}
)} {pqSubmission.projectId && item.additionalRequirement && (

추가 요구사항

{item.additionalRequirement}
)} {/* 벤더 답변 - 입력 형식에 따라 다르게 표시 */}

벤더 답변 {item.inputFormat && ( {item.inputFormat === "TEXT" && "텍스트"} {item.inputFormat === "EMAIL" && "이메일"} {item.inputFormat === "PHONE" && "전화번호"} {item.inputFormat === "NUMBER" && "숫자"} {item.inputFormat === "FILE" && "파일"} {item.inputFormat === "TEXT_FILE" && "텍스트+파일"} )}

{(() => { const inputFormat = item.inputFormat || "TEXT"; switch (inputFormat) { case "EMAIL": return (
이메일 주소:
{item.answer || 답변 없음}
); case "PHONE": return (
전화번호:
{item.answer || 답변 없음}
); case "NUMBER": return (
숫자 값:
{item.answer || 답변 없음}
); case "FILE": return (
파일 업로드 항목:
{item.attachments.length > 0 ? "파일이 업로드되었습니다." : "파일이 업로드되지 않았습니다."}
); case "TEXT_FILE": return (
텍스트 답변:
{item.answer || 텍스트 답변 없음}
파일 업로드:
{item.attachments.length > 0 ? "파일이 업로드되었습니다." : "파일이 업로드되지 않았습니다."}
); default: // TEXT return (
{item.answer || 답변 없음}
); } })()}
{/* SHI 코멘트 필드 (편집 가능) */}

SHI 코멘트