From 29fd4cbd592752961242337bf5b991296e491992 Mon Sep 17 00:00:00 2001 From: sangwook yoo Date: Thu, 11 Jun 2026 10:05:40 +0900 Subject: [PATCH] =?UTF-8?q?fix(pdf-import):=20=EB=B6=84=EC=84=9D=20?= =?UTF-8?q?=EC=A4=91=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EC=9D=B4=ED=83=88=20?= =?UTF-8?q?=EC=8B=9C=20=EB=8A=A6=EC=9D=80=20=EA=B2=B0=EA=B3=BC=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=C2=B7=ED=94=8C=EB=9E=9C=20=EB=8D=AE=EC=96=B4=EC=93=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=B0=A8=EB=8B=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [작업내용] : - 진행 중인 PDF 분석 요청을 pdfRunRef 로 추적, 모달 언마운트 시 AbortController.abort() 호출 - fetch 완료 후·저신뢰 confirm 후 두 지점에서 컨트롤러 동일성 가드 — 언마운트로 무효화된 늦은 결과가 비워진 canvas 에 적용되고 saveCanvas 가 이전 플랜 DB 를 outline-only 로 덮어쓰던 데이터 손실 경로 차단 Co-Authored-By: Claude Fable 5 --- .../placementShape/PlacementShapeSetting.jsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 3022c5a1..a7d8fd4b 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -107,6 +107,16 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla const [pdfAnalyzing, setPdfAnalyzing] = useState(false) const { setIsGlobalLoading } = useContext(QcastContext) const pdfInputRef = useRef(null) + // 진행 중인 PDF 분석 요청 추적 — 언마운트(라우팅 이탈) 시 abort 하고, 늦게 도착한 결과가 + // 이미 비워진 canvas 에 적용되거나 이전 플랜으로 저장되는 것을 차단한다. + const pdfRunRef = useRef(null) + + useEffect(() => { + return () => { + pdfRunRef.current?.controller?.abort() + pdfRunRef.current = null + } + }, []) /** * 치수 입력방법(복시도입력/실측값입력/도면 파일 업로드) */ @@ -165,6 +175,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setPdfAnalyzing(true) setIsGlobalLoading(true) const controller = new AbortController() + pdfRunRef.current = { controller } const timeoutId = setTimeout(() => controller.abort(), PDF_ANALYZE_TIMEOUT_MS) let response = null @@ -188,6 +199,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setIsGlobalLoading(false) } + // 언마운트로 abort 됐거나 다른 실행으로 대체된 경우 — 늦은 결과를 적용하거나 알림을 띄우지 않는다. + if (pdfRunRef.current?.controller !== controller) { + return false + } + // alert/confirm 은 스피너 해제 후 표시한다(스피너 z-index 가 alert 보다 높아 가려지는 것 방지). if (errorKey) { swalFire({ text: getMessage(errorKey), type: 'alert' }) @@ -216,6 +232,11 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla if (!proceed) return false } + // 저신뢰 confirm 대기 중 언마운트된 경우까지 커버하는 최종 가드 + if (pdfRunRef.current?.controller !== controller) { + return false + } + const applied = await applyOuterline(payload.outerline, { unit: payload.unit || 'mm' }) if (!applied) { swalFire({ text: getMessage('pdf.import.error.apply'), type: 'alert' })