diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx index daafa0ed0..c550551ba 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx @@ -15,6 +15,7 @@ import { import { pwrkCnsltnDocCrtRsltSaveRequestSchema, pwrkCnsltnDocCrtRsltService, + type PwrkCnsltnDocCrtRsltResponseDto, } from "@/entities/pwrk-cnsltn-doc-crt-rslt"; import { usePwrkCnsltnDocPjtcoAjmtMngPage } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng"; import { useElaprBranchesByDocNo, useElaprDocPrgrsByDocNo } from "@/entities/elapr"; @@ -167,7 +168,7 @@ function PwrkCnsltnDocMngDialog({ const [uploadedFiles, setUploadedFiles] = useState([]); const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false); const [isEvlDialogOpen, setIsEvlDialogOpen] = useState(false); - const [selectedVerSn, setSelectedVerSn] = useState(null); + const [selectedHistoryKey, setSelectedHistoryKey] = useState(""); // 전자결재 브랜치 목록 직접 조회 (Single Source of Truth) const { data: elaprBranchList = [] } = useElaprBranchesByDocNo( @@ -315,7 +316,7 @@ function PwrkCnsltnDocMngDialog({ useEffect(() => { if (open) { - setSelectedVerSn(null); + setSelectedHistoryKey(""); setCurrentBranchSeCd( resolveDefaultBranchSeCd({ branchSeCd: effectiveBranchSeCd, @@ -365,21 +366,11 @@ function PwrkCnsltnDocMngDialog({ } = useQuery({ queryKey: [ "pwrk-cnsltn-doc-crt-rslt", - selectedVerSn !== null ? "open-version" : "open-latest", + "open-latest", crtDmndUuid, targetBranchForApi, - selectedVerSn, ], - queryFn: () => { - if (selectedVerSn !== null) { - return pwrkCnsltnDocCrtRsltService.openWebHwpByVer( - crtDmndUuid!, - selectedVerSn, - targetBranchForApi, - ); - } - return pwrkCnsltnDocCrtRsltService.openWebHwpLatest(crtDmndUuid!, targetBranchForApi); - }, + queryFn: () => pwrkCnsltnDocCrtRsltService.openWebHwpLatest(crtDmndUuid!, targetBranchForApi), select: (response) => response.data, enabled: open && Boolean(crtDmndUuid), staleTime: Infinity, @@ -394,8 +385,7 @@ function PwrkCnsltnDocMngDialog({ ? "_시설" : "_종합" : ""; - const verSuffix = selectedVerSn !== null ? `_v${selectedVerSn}` : ""; - const documentFileName = `실무협의서${branchLabel}${verSuffix}_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`; + const documentFileName = `실무협의서${branchLabel}_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`; const isLoading = isPwrkCnsltnLoading || isDocumentLoading; const isError = isPwrkCnsltnError || isDocumentError; // AI 자동화 이력 조회 (COST_VRFC 완료 여부 확인) @@ -471,7 +461,7 @@ function PwrkCnsltnDocMngDialog({ setAtchFileGroupId(""); setUploadedFiles([]); setIsEvlDialogOpen(false); - setSelectedVerSn(null); + setSelectedHistoryKey(""); return; } @@ -523,20 +513,35 @@ function PwrkCnsltnDocMngDialog({ if (currentBranchSeCd !== nextBranch) { if (!checkDirtyAndConfirm(branchName)) return; setCurrentBranchSeCd(nextBranch); + setSelectedHistoryKey(""); } - setSelectedVerSn(null); } - async function handleOpenBranchInNewWindow(targetBranch: "SP" | "SF") { + async function openDocumentPopupWindow({ + popupName, + pageTitle, + headerTitle, + badgeText, + badgeColor = "#2563eb", + fileName, + fetchBlob, + }: { + popupName: string; + pageTitle: string; + headerTitle: string; + badgeText?: string; + badgeColor?: string; + fileName: string; + fetchBlob: () => Promise<{ data: Blob }>; + }) { if (!completedDraft) { alert("열람할 실무협의서 초안 정보가 없습니다."); return; } - const branchName = targetBranch === "SP" ? "관로 (SP)" : "시설 (SF)"; const popup = window.open( "", - `pwrk_viewer_${targetBranch}`, + popupName, "width=1200,height=900,menubar=no,toolbar=no,location=no,status=no,resizable=yes", ); if (!popup) { @@ -551,14 +556,17 @@ function PwrkCnsltnDocMngDialog({ - 실무협의서 [${branchName}] 참조 뷰어 + ${pageTitle} +

- 실무협의서 ${branchName} 참조 문서 - 읽기 전용 참조 모드 + ${headerTitle} + ${badgeText ? `${badgeText}` : ""} + 읽기 전용 참조 모드

+
-
웹한글 기안기 및 ${branchName} 초안 문서를 불러오는 중입니다...
+
웹한글 기안기 및 문서를 불러오는 중입니다...
@@ -587,12 +630,8 @@ function PwrkCnsltnDocMngDialog({ popup.document.close(); try { - const response = await pwrkCnsltnDocCrtRsltService.openWebHwpLatest( - completedDraft.crtDmndUuid, - targetBranch, - ); + const response = await fetchBlob(); const blob = response.data; - const fileName = `실무협의서_${targetBranch === "SP" ? "관로" : "시설"}.hwpx`; const documentFile = new File([blob], fileName, { type: "application/haansofthwpx" }); // 다운로드 fallback 핸들러 바인딩 @@ -633,7 +672,11 @@ function PwrkCnsltnDocMngDialog({ window.clearInterval(checkTimer); const loadingEl = popup.document.getElementById("loading"); if (loadingEl) loadingEl.style.display = "none"; - ctrl.Open(documentFile, "HWPX", "", () => {}); + ctrl.Open(documentFile, "HWPX", "", () => { + try { + (ctrl as Record).EditMode = 0; + } catch {} + }); } } catch { window.clearInterval(checkTimer); @@ -645,13 +688,71 @@ function PwrkCnsltnDocMngDialog({ console.error("새 창 문서 로드 실패:", err); const loadingEl = popup.document.getElementById("loading"); if (loadingEl) { - loadingEl.textContent = `${branchName} 문서를 불러오지 못했습니다.`; + loadingEl.textContent = "문서를 불러오지 못했습니다."; loadingEl.style.color = "#ef4444"; } - alert(`${branchName} 문서를 불러오지 못했습니다.`); + alert("문서를 불러오지 못했습니다."); } } + async function handleOpenBranchInNewWindow(targetBranch: "SP" | "SF") { + if (!completedDraft) { + alert("열람할 실무협의서 초안 정보가 없습니다."); + return; + } + + const branchName = targetBranch === "SP" ? "관로 (SP)" : "시설 (SF)"; + await openDocumentPopupWindow({ + popupName: `pwrk_viewer_${targetBranch}`, + pageTitle: `실무협의서 [${branchName}] 참조 뷰어`, + headerTitle: `실무협의서 ${branchName} 참조 문서`, + badgeText: targetBranch === "SP" ? "관로 초안" : "시설 초안", + badgeColor: "#2563eb", + fileName: `실무협의서_${targetBranch === "SP" ? "관로" : "시설"}.hwpx`, + fetchBlob: () => + pwrkCnsltnDocCrtRsltService.openWebHwpLatest( + completedDraft.crtDmndUuid, + targetBranch, + ), + }); + } + + async function handleOpenVersionInNewWindow(item: PwrkCnsltnDocCrtRsltResponseDto) { + if (!completedDraft) { + alert("열람할 실무협의서 초안 정보가 없습니다."); + return; + } + + const branch = item.branchSeCd || "MAIN"; + const branchName = + branch === "SP" + ? "관로 (SP)" + : branch === "SF" + ? "시설 (SF)" + : "종합 실무협의서 (MAIN)"; + const branchShort = + branch === "SP" + ? "관로" + : branch === "SF" + ? "시설" + : "종합"; + + await openDocumentPopupWindow({ + popupName: `pwrk_viewer_${completedDraft.crtDmndUuid}_${branch}_v${item.verSn}`, + pageTitle: `실무협의서 [${branchShort}] 버전 ${item.verSn} 참조 뷰어`, + headerTitle: `실무협의서 ${branchName} (버전 ${item.verSn})`, + badgeText: `버전 ${item.verSn}`, + badgeColor: "#059669", + fileName: `실무협의서_${branchShort}_v${item.verSn}.hwpx`, + fetchBlob: () => + pwrkCnsltnDocCrtRsltService.openWebHwpByVer( + completedDraft.crtDmndUuid, + item.verSn, + branch, + ), + }); + } + function handleOpenChange(nextOpen: boolean) { if (!nextOpen) { if (currentBranchSeCd === "MAIN") { @@ -704,7 +805,6 @@ function PwrkCnsltnDocMngDialog({ queryKey: ["pwrk-cnsltn-doc-crt-rslt", "history", completedDraft.crtDmndUuid], }), ]); - setSelectedVerSn(null); alert(`실무협의서를 저장했습니다. (버전 ${savedDocument.verSn})`); if (savedDocument.verSn <= AI_DRAFT_EVL_MAX_VER_SN) { @@ -715,17 +815,10 @@ function PwrkCnsltnDocMngDialog({ async function handleDownload() { if (!completedDraft) throw new Error("다운로드할 실무협의서 초안이 없습니다."); - const response = - selectedVerSn !== null - ? await pwrkCnsltnDocCrtRsltService.download( - completedDraft.crtDmndUuid, - selectedVerSn, - targetBranchForApi, - ) - : await pwrkCnsltnDocCrtRsltService.downloadLatest( - completedDraft.crtDmndUuid, - targetBranchForApi, - ); + const response = await pwrkCnsltnDocCrtRsltService.downloadLatest( + completedDraft.crtDmndUuid, + targetBranchForApi, + ); const objectUrl = window.URL.createObjectURL(response.data); const anchor = document.createElement("a"); anchor.href = objectUrl; @@ -859,47 +952,37 @@ function PwrkCnsltnDocMngDialog({ supplementBranches, ]); - // 현재 활성화된 탭(브랜치)의 최신(제일 마지막) verSn 계산 - const latestVerSnForCurrentBranch = useMemo(() => { - const branchItems = historyList.filter( - (item) => (item.branchSeCd || "MAIN") === (currentBranchSeCd || "MAIN"), - ); - if (!branchItems.length) return null; - return Math.max(...branchItems.map((item) => item.verSn)); - }, [historyList, currentBranchSeCd]); + // 저장 버튼은 해당 부서 작성 권한이 있는 경우 표시 (메인 기안기는 항상 최신 버전 유지) + const canSaveInCurrentView = canSaveCurrentBranch; - // 최신 버전을 열람 중인지 여부 (null이면 최신 모드이거나, 선택된 버전이 해당 탭의 최댓값과 일치) - const isCurrentBranchLatest = - selectedVerSn === null || selectedVerSn === latestVerSnForCurrentBranch; - - // 저장 버튼은 해당 탭의 제일 마지막(최신) 버전이 아니면 자동 숨김 처리 - const canSaveInCurrentView = canSaveCurrentBranch && isCurrentBranchLatest; - - // 변경이력 콤보 박스 컴포넌트 + // 변경이력 콤보 박스 컴포넌트 (선택 시 해당 버전 새 창으로 열람 및 선택 항목 유지) const versionHistorySelect = (
-
); @@ -1060,7 +1171,7 @@ function PwrkCnsltnDocMngDialog({