diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 49c7814a..8379ab59 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useRef, useState } from 'react' import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { adsorptionPointModeState, @@ -119,6 +119,12 @@ export function useCanvasSetting(executeEffect = true) { const [basicSetting, setBasicSettings] = useRecoilState(basicSettingState) const { getRoofMaterialList, getModuleTypeItemList } = useMasterController() const [roofMaterials, setRoofMaterials] = useRecoilState(roofMaterialsAtom) + // [ROOFMAT-POLL-FIX 2026-06-29] fetchBasicSettings 의 폴링 루프가 stale closure 에 묶여 + // Header.jsx 가 채운 최신 roofMaterials 를 못 읽던 문제 → ref 미러로 최신값을 읽어 데이터 도착 즉시 종료. + const roofMaterialsRef = useRef(roofMaterials) + useEffect(() => { + roofMaterialsRef.current = roofMaterials + }, [roofMaterials]) const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) const setCurrentMenu = useSetRecoilState(currentMenuState) @@ -321,20 +327,22 @@ export function useCanvasSetting(executeEffect = true) { const targetObjectNo = objectNo || correntObjectNo // 지붕재 데이터가 없으면 Header.jsx에서 로드될 때까지 기다림 - let materials = roofMaterials + // [ROOFMAT-POLL-FIX 2026-06-29] roofMaterialsRef 로 최신값을 폴링 → 데이터가 들어오는 즉시 종료. + // (이전엔 stale closure 로 항상 풀카운트를 소모해 select 가 늦게 떴음) + let materials = roofMaterialsRef.current if (!materials || materials.length === 0) { logger.log('Waiting for roofMaterials to be loaded from Header.jsx...') - // 최대 2초간 기다림 (20번 × 100ms) + // 최대 1초간 기다림 (10번 × 100ms), 데이터 도착 시 즉시 break let waitCount = 0 - while ((!materials || materials.length === 0) && waitCount < 20) { - await new Promise(resolve => setTimeout(resolve, 100)) - materials = roofMaterials + while ((!materials || materials.length === 0) && waitCount < 10) { + await new Promise((resolve) => setTimeout(resolve, 100)) + materials = roofMaterialsRef.current waitCount++ } - + if (!materials || materials.length === 0) { - logger.log('roofMaterials still not loaded after 2 seconds, proceeding without them') - // 비상시에만 addRoofMaterials 호출 (fallback) + logger.log('roofMaterials still not loaded after 1 second, proceeding via fallback') + // 10회 안에 못 받으면 fallback 직접 호출 (getRoofMaterialList promise cache 로 중복 네트워크 방지) materials = await addRoofMaterials() logger.log('roofMaterials loaded via fallback:', materials) } else { @@ -485,6 +493,15 @@ export function useCanvasSetting(executeEffect = true) { }) } } + } else { + // [ROOFMAT-TOPMENU-DIAG 2026-06-29] 저장 roofMatlCd 가 마스터에 없거나 마스터 미로드로 매칭 0건 → + // top 메뉴 지붕재 select(CanvasMenu.jsx:648)가 렌더되지 않으므로 사용자에게 에러 안내. + logger.warn('[ROOFMAT-TOPMENU-DIAG] addRoofs 0건 — select 미표시', { + objectNo: targetObjectNo, + materialsCount: materials?.length ?? 0, + savedRoofCodes: roofsArray.map((r) => r.roofMatlCd), + }) + swalFire({ icon: 'error', text: getMessage('canvas.roof.material.not.found') }) } }) } catch (error) { diff --git a/src/locales/ja.json b/src/locales/ja.json index fecc3c3f..2ee9f131 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -60,6 +60,7 @@ "modal.placement.initial.setting.roof.angle": "角度", "modal.placement.initial.setting.roof.material": "屋根材選択(単位mm)", "modal.placement.initial.setting.roof.material.info": "対応可能な屋根材や足場は限定されますので、必ず事前マニュアルをご確認ください。", + "canvas.roof.material.not.found": "屋根材情報を読み込めませんでした。ページを再読み込みするか、屋根材設定をご確認ください。", "modal.placement.initial.setting.rafter": "垂木", "modal.roof.shape.setting": "屋根形状の設定", "modal.roof.shape.setting.ridge": "棟", diff --git a/src/locales/ko.json b/src/locales/ko.json index af972e11..1275a136 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -60,6 +60,7 @@ "modal.placement.initial.setting.roof.angle": "각도", "modal.placement.initial.setting.roof.material": "지붕재 선택(단위mm)", "modal.placement.initial.setting.roof.material.info": "대응 가능한 지붕재 및 발판은 한정되므로 반드시 사전 매뉴얼을 확인하십시오.", + "canvas.roof.material.not.found": "지붕재 정보를 불러오지 못했습니다. 페이지를 새로고침하거나 지붕재 설정을 확인해 주세요.", "modal.placement.initial.setting.rafter": "서까래", "modal.roof.shape.setting": "지붕형상 설정", "modal.roof.shape.setting.ridge": "용마루",