diff --git a/src/hooks/roofcover/useOuterLineWall.js b/src/hooks/roofcover/useOuterLineWall.js index 0c49fccb..82472227 100644 --- a/src/hooks/roofcover/useOuterLineWall.js +++ b/src/hooks/roofcover/useOuterLineWall.js @@ -6,9 +6,11 @@ import { adsorptionPointModeState, adsorptionRangeState, canvasState, + currentMenuState, dotLineIntervalSelector, verticalHorizontalModeState, } from '@/store/canvasAtom' +import { MENU } from '@/common/common' import { useEvent } from '@/hooks/useEvent' import { useMouse } from '@/hooks/useMouse' import { useLine } from '@/hooks/useLine' @@ -88,6 +90,7 @@ export function useOuterLineWall(id, propertiesId) { const { addPopup, closePopup } = usePopup() const setOuterLineFix = useSetRecoilState(outerLineFixState) + const setCurrentMenu = useSetRecoilState(currentMenuState) const outerLineDiagonalLengthRef = useRef(null) @@ -257,6 +260,8 @@ export function useOuterLineWall(id, propertiesId) { canvas.outerLineFix = true closePopup(id) ccwCheck() + // 외벽선 완료 → 지붕형상 설정 메뉴로 전환 + setCurrentMenu(MENU.ROOF_COVERING.ROOF_SHAPE_SETTINGS) addPopup(propertiesId, 1, ) } diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index e7c8e260..9a1a18c6 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { useMessage } from '@/hooks/useMessage' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState, pitchTextSelector } from '@/store/canvasAtom' -import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' +import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common' import { usePolygon } from '@/hooks/usePolygon' import { useMode } from '@/hooks/useMode' import { useLine } from '@/hooks/useLine' @@ -21,6 +21,8 @@ import { import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' import { useUndoRedo } from '@/hooks/useUndoRedo' +import { pendingPopupState } from '@/store/popupAtom' +import RoofShapeSetting from '@/components/floor-plan/modal/roofShape/RoofShapeSetting' // 지붕형상 설정 export function useRoofShapeSetting(id) { @@ -71,6 +73,7 @@ export function useRoofShapeSetting(id) { const { addLine, removeLine } = useLine() const { saveSnapshot } = useUndoRedo() + const [pendingPopup, setPendingPopup] = useRecoilState(pendingPopupState) useEffect(() => { pitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? pitch : getChonByDegree(pitch) @@ -82,6 +85,18 @@ export function useRoofShapeSetting(id) { jerkinHeadPitchRef.current = currentAngleType === ANGLE_TYPE.SLOPE ? jerkinHeadPitch : getChonByDegree(jerkinHeadPitch) }, [jerkinHeadPitch]) + // undo/redo 로 pendingPopup 이 복원되면 해당 팝업을 다시 연다 + useEffect(() => { + if (!pendingPopup) return + if (pendingPopup.type === 'RoofShapeSetting') { + addPopup(pendingPopup.id, 1, ) + setPendingPopup(null) + } else if (pendingPopup.type === 'RoofAllocation') { + addPopup(pendingPopup.id, 1, ) + setPendingPopup(null) + } + }, [pendingPopup]) + useEffect(() => { const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') if (!canvas.outerLineFix || outerLines.length === 0) { @@ -184,7 +199,8 @@ export function useRoofShapeSetting(id) { ] const handleSave = () => { - saveSnapshot() + // undo 시 지붕형상 설정 팝업이 다시 열리도록 pendingPopup 을 atomOverrides 로 직접 전달 + saveSnapshot({ pendingPopup: { type: 'RoofShapeSetting', id, pos: { x: 50, y: 230 } } }) let outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') let direction @@ -485,6 +501,8 @@ export function useRoofShapeSetting(id) { canvas?.renderAll() roof.drawHelpLine(settingModalFirstOptions) isFixRef.current = true + // 지붕형상 설정 완료 → 지붕면 할당 메뉴로 전환 + setCurrentMenu(MENU.ROOF_COVERING.ROOF_SHAPE_ALLOC) addPopup(id, 1, ) } diff --git a/src/hooks/useUndoRedo.js b/src/hooks/useUndoRedo.js index 1a93ff5e..21bfc500 100644 --- a/src/hooks/useUndoRedo.js +++ b/src/hooks/useUndoRedo.js @@ -15,7 +15,7 @@ import { } from '@/store/canvasAtom' import { outerLinePointsState } from '@/store/outerLineAtom' import { selectedMenuState } from '@/store/menuAtom' -import { popupState } from '@/store/popupAtom' +import { pendingPopupState, popupState } from '@/store/popupAtom' import { POLYGON_TYPE, SAVE_KEY } from '@/common/common' const MAX_HISTORY = 3 @@ -103,6 +103,7 @@ export function useUndoRedo() { const [currentMenu, setCurrentMenu] = useRecoilState(currentMenuState) const [, setCanvasZoom] = useRecoilState(canvasZoomState) const setPopup = useSetRecoilState(popupState) + const [pendingPopup, setPendingPopup] = useRecoilState(pendingPopupState) // 최신 스택 값을 ref로 유지하여 클로저 stale 문제 방지 const undoStackRef = useRef(undoStack) @@ -118,8 +119,9 @@ export function useUndoRedo() { outerLinePoints: safeClone(outerLinePoints || []), selectedMenu, currentMenu, + pendingPopup: pendingPopup ? safeClone(pendingPopup) : null, } - }, [moduleIsSetup, checkedModule, moduleRowColArray, outerLinePoints, selectedMenu, currentMenu]) + }, [moduleIsSetup, checkedModule, moduleRowColArray, outerLinePoints, selectedMenu, currentMenu, pendingPopup]) // loadFromJSON 후 오브젝트 간 라이브 참조 관계를 재구축 const rebuildObjectGraph = useCallback(() => { @@ -266,6 +268,8 @@ export function useUndoRedo() { setOuterLinePoints(atomSnapshot.outerLinePoints || []) if (atomSnapshot.selectedMenu !== undefined) setSelectedMenu(atomSnapshot.selectedMenu) if (atomSnapshot.currentMenu !== undefined) setCurrentMenu(atomSnapshot.currentMenu) + // undo/redo 시 복원해야 할 팝업 정보 + setPendingPopup(atomSnapshot.pendingPopup ?? null) }, []) // canvas.toJSON(SAVE_KEY)의 결과를 순환 참조 없이 안전하게 직렬화 @@ -281,7 +285,7 @@ export function useUndoRedo() { } }, [canvas]) - const saveSnapshot = useCallback(() => { + const saveSnapshot = useCallback((atomOverrides = null) => { if (!canvas || _isRestoring) return // snapshot 전에 mouseLine 제거 @@ -293,6 +297,11 @@ export function useUndoRedo() { const atomSnapshot = getAtomSnapshot() + // atomOverrides 로 비동기 state 반영이 안 된 값을 즉시 덮어쓸 수 있다 + if (atomOverrides) { + Object.assign(atomSnapshot, atomOverrides) + } + const snapshot = { canvasJSON, atomSnapshot, @@ -329,9 +338,8 @@ export function useUndoRedo() { const newUndoStack = [...currentUndoStack] const snapshot = newUndoStack.pop() - if (snapshot.atomSnapshot?.currentMenu !== currentMenu) { - closeAllPopups() - } + // undo/redo 시 항상 팝업을 닫고 복원한다 + closeAllPopups() setUndoStack(newUndoStack) setRedoStack((prev) => [...prev, currentSnapshot]) @@ -355,9 +363,8 @@ export function useUndoRedo() { const newRedoStack = [...currentRedoStack] const snapshot = newRedoStack.pop() - if (snapshot.atomSnapshot?.currentMenu !== currentMenu) { - closeAllPopups() - } + // undo/redo 시 항상 팝업을 닫고 복원한다 + closeAllPopups() setRedoStack(newRedoStack) setUndoStack((prev) => [...prev, currentSnapshot]) diff --git a/src/store/popupAtom.js b/src/store/popupAtom.js index 72acaee5..fd430afd 100644 --- a/src/store/popupAtom.js +++ b/src/store/popupAtom.js @@ -39,3 +39,9 @@ export const promisePopupState = atom({ key: 'promisePopupStore', default: false, }) + +/** undo/redo 시 복원해야 할 팝업 정보 (JSX 직렬화 불가 → 타입+파라미터만 저장) */ +export const pendingPopupState = atom({ + key: 'pendingPopupState', + default: null, // { type: 'RoofAllocation', id, pos } 등 +})