From 256189fe409fbe0a7d57b817c9f31b9d73ac2238 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 10 Feb 2026 11:44:34 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=EB=B0=B0=EC=B9=98=EB=A9=B4=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=20=EC=84=A4=EC=A0=95=20=ED=9B=84=20=EA=B8=B8=EC=9D=B4?= =?UTF-8?q?=20=EA=B3=84=EC=82=B0=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= =?UTF-8?q?,=20=EB=B0=B0=EC=B9=98=EB=A9=B4=20=EB=B3=B5=EC=82=AC=20?= =?UTF-8?q?=EC=8B=9C=20=EA=B8=B8=EC=9D=B4=20=EC=9D=B4=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../placementShape/PlacementShapeSetting.jsx | 89 ++++++++++++------- src/hooks/common/useCommonUtils.js | 44 ++++----- src/hooks/option/useCanvasSetting.js | 39 ++++---- 3 files changed, 101 insertions(+), 71 deletions(-) diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 95442b8a..a537b065 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -8,7 +8,7 @@ import MaterialGuide from '@/components/floor-plan/modal/placementShape/Material import WithDraggable from '@/components/common/draggable/WithDraggable' import { useCanvasSetting } from '@/hooks/option/useCanvasSetting' -import { useRecoilState, useRecoilValue } from 'recoil' +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { addedRoofsState, roofDisplaySelector, roofMaterialsAtom } from '@/store/settingAtom' import { useCommonCode } from '@/hooks/common/useCommonCode' import QSelectBox from '@/components/common/select/QSelectBox' @@ -16,11 +16,12 @@ import { globalLocaleStore } from '@/store/localeAtom' import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { usePolygon } from '@/hooks/usePolygon' -import { canvasState } from '@/store/canvasAtom' +import { canvasState, currentMenuState } from '@/store/canvasAtom' +import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' +import { MENU, POLYGON_TYPE } from '@/common/common' import { useRoofFn } from '@/hooks/common/useRoofFn' import { usePlan } from '@/hooks/usePlan' -import { normalizeDecimal, normalizeDigits } from '@/util/input-utils' -import { logger } from '@/util/logger' +import { normalizeDecimal } from '@/util/input-utils' import { CalculatorInput } from '@/components/common/input/CalcInput' /** @@ -41,14 +42,21 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla const { basicSetting, setBasicSettings, fetchBasicSettings, basicSettingSave } = useCanvasSetting() const [addedRoofs, setAddedRoofs] = useRecoilState(addedRoofsState) const { findCommonCode } = useCommonCode() - const [raftCodes, setRaftCodes] = useState([]) /** 서까래 정보 */ - const [currentRoof, setCurrentRoof] = useState(null) /** 현재 선택된 지붕재 정보 */ - const { closePopup } = usePopup() /** usePopup에서 closePopup 함수 가져오기 */ + const [raftCodes, setRaftCodes] = useState([]) + /** 서까래 정보 */ + const [currentRoof, setCurrentRoof] = useState(null) + /** 현재 선택된 지붕재 정보 */ + const { closePopup } = usePopup() + /** usePopup에서 closePopup 함수 가져오기 */ const { drawDirectionArrow } = usePolygon() const { setSurfaceShapePattern } = useRoofFn() const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) + const { setPolygonLinesActualSize } = usePolygon() + const { setSelectedMenu } = useCanvasMenu() + const setCurrentMenu = useSetRecoilState(currentMenuState) + const roofRef = { roofCd: useRef(null), width: useRef(null), @@ -121,7 +129,12 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla if (addedRoofs.length > 0) { const raftCodeList = findCommonCode('203800') setRaftCodes(raftCodeList) - setCurrentRoof({ ...addedRoofs[0], planNo: planNo, roofSizeSet: String(basicSetting.roofSizeSet), roofAngleSet: basicSetting.roofAngleSet }) + setCurrentRoof({ + ...addedRoofs[0], + planNo: planNo, + roofSizeSet: String(basicSetting.roofSizeSet), + roofAngleSet: basicSetting.roofAngleSet, + }) } else { /** 데이터 설정 확인 후 데이터가 없으면 기본 데이터 설정 */ setCurrentRoof({ ...DEFAULT_ROOF_SETTINGS }) @@ -209,7 +222,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla * 배치면초기설정 저장 버튼 클릭 */ const handleSaveBtn = async () => { - const roofInfo = { ...currentRoof, planNo: basicSetting.planNo, @@ -227,7 +239,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla newAddedRoofs[0] = { ...roofInfo } setAddedRoofs(newAddedRoofs) - logger.debug('save Info', { + console.log('save Info', { ...basicSetting, selectedRoofMaterial: { ...newAddedRoofs[0], @@ -235,7 +247,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla }) /** - * 배치면초기설정 저장 + * 배치면초기설정 저장 (메뉴 변경/useEffect 트리거 없이) */ basicSettingSave({ ...basicSetting, @@ -245,7 +257,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla selectedRoofMaterial: { ...newAddedRoofs[0], }, - }) + }, { skipSideEffects: true }) const roofs = canvas.getObjects().filter((obj) => obj.roofMaterial?.index === 0) @@ -254,7 +266,19 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setSurfaceShapePattern(roof, roofDisplay.column, false, { ...roofInfo }) roof.roofMaterial = { ...roofInfo } drawDirectionArrow(roof) + setPolygonLinesActualSize(roof, true) }) + canvas.renderAll() + + /** 지붕면 존재 여부에 따라 메뉴 설정 */ + const hasRoofs = canvas.getObjects().some((obj) => obj.name === POLYGON_TYPE.ROOF) + if (hasRoofs) { + setSelectedMenu('surface') + setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING) + } else { + setSelectedMenu('outline') + setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE) + } /* 저장 후 화면 닫기 */ closePopup(id) @@ -353,26 +377,26 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla value={index === 0 ? (currentRoof?.pitch ?? basicSetting?.inclBase ?? '0') : (currentRoof?.angle ?? '0')} onChange={(value) => { if (index === 0) { - const pitch = value === '' ? '' : Number(value); - const angle = pitch === '' ? '' : getDegreeByChon(pitch); - setCurrentRoof(prev => ({ + const pitch = value === '' ? '' : Number(value) + const angle = pitch === '' ? '' : getDegreeByChon(pitch) + setCurrentRoof((prev) => ({ ...prev, pitch, - angle - })); + angle, + })) } else { - const angle = value === '' ? '' : Number(value); - const pitch = angle === '' ? '' : getChonByDegree(angle); - setCurrentRoof(prev => ({ + const angle = value === '' ? '' : Number(value) + const pitch = angle === '' ? '' : getChonByDegree(angle) + setCurrentRoof((prev) => ({ ...prev, pitch, - angle - })); + angle, + })) } }} options={{ allowNegative: false, - allowDecimal: true + allowDecimal: true, }} /> @@ -432,7 +456,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" ref={roofRef.width} - value={currentRoof?.width||0} + value={currentRoof?.width || 0} onChange={(value) => { setCurrentRoof({ ...currentRoof, value }) }} @@ -440,7 +464,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> @@ -467,7 +491,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" ref={roofRef.length} - value={currentRoof?.length||0} + value={currentRoof?.length || 0} onChange={(value) => { setCurrentRoof({ ...currentRoof, value }) }} @@ -475,7 +499,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> @@ -522,20 +546,19 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla ref={roofRef.hajebichi} value={currentRoof?.hajebichi ?? basicSetting?.roofPchBase ?? '0'} onChange={(value) => { - const hajebichi = value === '' ? '' : Number(value); - setCurrentRoof(prev => ({ + const hajebichi = value === '' ? '' : Number(value) + setCurrentRoof((prev) => ({ ...prev, - hajebichi - })); + hajebichi, + })) }} readOnly={currentRoof?.roofPchAuth === 'R'} disabled={currentRoof?.roofSizeSet === '3'} options={{ allowNegative: false, - allowDecimal: false //(index !== 0), + allowDecimal: false, //(index !== 0), }} /> - )} diff --git a/src/hooks/common/useCommonUtils.js b/src/hooks/common/useCommonUtils.js index 0b028f32..fc4c907b 100644 --- a/src/hooks/common/useCommonUtils.js +++ b/src/hooks/common/useCommonUtils.js @@ -633,9 +633,7 @@ export function useCommonUtils() { // 원본 roof의 자식 오브젝트들 찾기 (개구, 그림자, 도머 등) const childObjectTypes = [BATCH_TYPE.OPENING, BATCH_TYPE.SHADOW, BATCH_TYPE.TRIANGLE_DORMER, BATCH_TYPE.PENTAGON_DORMER] - const childObjects = canvas.getObjects().filter( - (o) => o.parentId === obj.id && childObjectTypes.includes(o.name) - ) + const childObjects = canvas.getObjects().filter((o) => o.parentId === obj.id && childObjectTypes.includes(o.name)) // 원본 roof 중심점 계산 const originalPoints = obj.getCurrentPoints() @@ -669,24 +667,28 @@ export function useCommonUtils() { y: p.y + offsetY, })) - clonedObj = new QPolygon(newPoints, { - fill: obj.fill || 'transparent', - stroke: obj.stroke || 'black', - strokeWidth: obj.strokeWidth || 1, - fontSize: 0, // 이동 중에는 lengthText 생성하지 않음 (fontSize=0이면 addLengthText가 스킵됨) - selectable: true, - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - name: 'clonedObj', - originX: 'center', - originY: 'center', - pitch: obj.pitch, - surfaceId: obj.surfaceId, - sort: false, - }, canvas) + clonedObj = new QPolygon( + newPoints, + { + fill: obj.fill || 'transparent', + stroke: obj.stroke || 'black', + strokeWidth: obj.strokeWidth || 1, + fontSize: 0, // 이동 중에는 lengthText 생성하지 않음 (fontSize=0이면 addLengthText가 스킵됨) + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + name: 'clonedObj', + originX: 'center', + originY: 'center', + pitch: obj.pitch, + surfaceId: obj.surfaceId, + // sort: false, + }, + canvas, + ) canvas.add(clonedObj) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 88c32d5f..be906baa 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -440,15 +440,17 @@ export function useCanvasSetting(executeEffect = true) { }) setAddedRoofs(addRoofs) - setCanvasSetting({ - ...basicSetting, - roofMaterials: addRoofs[0], - planNo: roofsRow[0].planNo, - roofSizeSet: roofsRow[0].roofSizeSet, - roofAngleSet: roofsRow[0].roofAngleSet, - roofsData: roofsArray, - selectedRoofMaterial: addRoofs.find((roof) => roof.selected), - }) + if (openPoint !== 'basicSettingSave') { + setCanvasSetting({ + ...basicSetting, + roofMaterials: addRoofs[0], + planNo: roofsRow[0].planNo, + roofSizeSet: roofsRow[0].roofSizeSet, + roofAngleSet: roofsRow[0].roofAngleSet, + roofsData: roofsArray, + selectedRoofMaterial: addRoofs.find((roof) => roof.selected), + }) + } } }) } catch (error) { @@ -474,7 +476,8 @@ export function useCanvasSetting(executeEffect = true) { /** * 기본설정(PlacementShapeSetting) 저장 */ - const basicSettingSave = async (params) => { + const basicSettingSave = async (params, options = {}) => { + const { skipSideEffects = false } = options try { const patternData = { objectNo: correntObjectNo, @@ -527,14 +530,16 @@ export function useCanvasSetting(executeEffect = true) { setBasicSettings({ ...params }) }) - /** CanvasSetting Recoil 설정 - roofSizeSet을 문자열로 변환 */ - setCanvasSetting({ - ...basicSetting, - roofSizeSet: String(params.roofSizeSet), - }) + if (!skipSideEffects) { + /** CanvasSetting Recoil 설정 - roofSizeSet을 문자열로 변환 */ + setCanvasSetting({ + ...basicSetting, + roofSizeSet: String(params.roofSizeSet), + }) - /** 메뉴 설정 */ - setMenuByRoofSize(params.roofSizeSet) + /** 메뉴 설정 */ + setMenuByRoofSize(params.roofSizeSet) + } /** 배치면초기설정 조회 */ fetchBasicSettings(params.planNo, 'basicSettingSave') From b4baaa06ddf6d14119dbcb67c4ef7b24c2934d14 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 10 Feb 2026 17:55:31 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=EC=A0=84=ED=99=94=EB=B2=88=ED=98=B8=2000?= =?UTF-8?q?00-000-000=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/auth/Join.jsx | 15 ++++++++++----- src/locales/ja.json | 4 ++-- src/locales/ko.json | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/auth/Join.jsx b/src/components/auth/Join.jsx index 94f7fa3e..a4c17e33 100644 --- a/src/components/auth/Join.jsx +++ b/src/components/auth/Join.jsx @@ -35,7 +35,12 @@ export default function Join() { const joinValidation = (formData) => { // 전화번호/FAX 정규식 (일본 형식: 0으로 시작, 하이픈 포함) - const telRegex = /^0\d{1,4}-\d{1,4}-\d{4}$/ + const telRegex = /^0\d{1,4}-\d{1,4}-\d{3,4}$/ + const isValidTel = (value) => { + if (!telRegex.test(value)) return false + const digitCount = value.replace(/-/g, '').length + return digitCount >= 10 && digitCount <= 11 + } // 판매대리점 정보 - 판매대리점명 @@ -77,7 +82,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) telNoRef.current.focus() return false - } else if (!telRegex.test(telNo)) { + } else if (!isValidTel(telNo)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) telNoRef.current.focus() return false @@ -98,7 +103,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.fax')])) faxRef.current.focus() return false - }else if (!telRegex.test(fax)) { + }else if (!isValidTel(fax)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.fax')])) faxRef.current.focus() return false @@ -173,7 +178,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub1.telNo')])) userTelNoRef.current.focus() return false - } else if (!telRegex.test(userTelNo)) { + } else if (!isValidTel(userTelNo)) { alert(getMessage('join.validation.check1', [getMessage('join.sub1.telNo')])) userTelNoRef.current.focus() return false @@ -185,7 +190,7 @@ export default function Join() { alert(getMessage('common.message.required.data', [getMessage('join.sub2.fax')])) userFaxRef.current.focus() return false - } else if (!telRegex.test(userFax)) { + } else if (!isValidTel(userFax)) { alert(getMessage('join.validation.check1', [getMessage('join.sub2.fax')])) userFaxRef.current.focus() return false diff --git a/src/locales/ja.json b/src/locales/ja.json index 0c589e1e..2953688e 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -683,7 +683,7 @@ "join.sub1.addr": "住所", "join.sub1.addr_placeholder": "全角50文字以内", "join.sub1.telNo": "電話番号", - "join.sub1.telNo_placeholder": "00-0000-0000", + "join.sub1.telNo_placeholder": "000-0000-0000 / 0000-000-000", "join.sub1.fax": "FAX番号", "join.sub1.fax_placeholder": "00-0000-0000", "join.sub1.bizNo": "法人番号", @@ -702,7 +702,7 @@ "join.complete.title": "HANASYS設計ログインID発行申請完了", "join.complete.contents": "※申請したIDが承認されると、担当者情報に入力したEメールアドレスにログイン関連案内メールが送信されます。", "join.complete.email_comment": "担当者のメールアドレス", - "join.validation.check1": "{0}形式を確認してください。", + "join.validation.check1": "{0}形式(000-0000-0000 / 0000-000-000)を確認してください。", "join.complete.save.confirm": "ID申請を完了後は申請情報の修正が出来ません。申請しますか?", "stuff.gridHeader.lastEditDatetime": "更新日時", "stuff.gridHeader.objectNo": "物件番号", diff --git a/src/locales/ko.json b/src/locales/ko.json index 6ada9943..7e871e60 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -683,7 +683,7 @@ "join.sub1.addr": "주소", "join.sub1.addr_placeholder": "전각50자이내", "join.sub1.telNo": "전화번호", - "join.sub1.telNo_placeholder": "000-0000-0000", + "join.sub1.telNo_placeholder": "000-0000-0000 또는 0000-000-000", "join.sub1.fax": "FAX 번호", "join.sub1.fax_placeholder": "00 0000 0000", "join.sub1.bizNo": "법인번호", @@ -702,7 +702,7 @@ "join.complete.title": "Q.CAST3 로그인ID 발행신청 완료", "join.complete.contents": "※ 신청한 ID가 승인되면 담당자 정보에 입력한 이메일 주소로 로그인 관련 안내 메일이 전송됩니다.", "join.complete.email_comment": "담당자 이메일 주소", - "join.validation.check1": "{0} 형식을 확인해주세요.", + "join.validation.check1": "{0} 형식(000-0000-0000 또는 0000-000-000)을 확인해주세요.", "join.complete.save.confirm": "Hanwha Japan 담당자에게 ID승인이 요청되면 더 이상 정보를 수정할 수 없습니다. 정말로 요청하시겠습니까?", "stuff.gridHeader.lastEditDatetime": "갱신일시", "stuff.gridHeader.objectNo": "물건번호", From a92f335a8342fbaa45613be0943566fc2b273bb4 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 11 Feb 2026 15:23:58 +0900 Subject: [PATCH 03/14] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EC=8B=9C=20=EA=B8=B8=EC=9D=B4=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EA=B0=95=EC=A0=9C=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useRoofFn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/common/useRoofFn.js b/src/hooks/common/useRoofFn.js index 1148eed3..7c32505e 100644 --- a/src/hooks/common/useRoofFn.js +++ b/src/hooks/common/useRoofFn.js @@ -179,7 +179,7 @@ export function useRoofFn() { polygon.set('fill', null) polygon.set('fill', pattern) polygon.roofMaterial = roofMaterial - setPolygonLinesActualSize(polygon) + setPolygonLinesActualSize(polygon, true) changeCorridorDimensionText() polygon.canvas?.renderAll() } catch (e) { From 0f59fa2b3682f28ee3b33846cdaf6ac38cbc9f78 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Wed, 11 Feb 2026 15:26:46 +0900 Subject: [PATCH 04/14] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=A9=B4=20=ED=9A=8C?= =?UTF-8?q?=EC=A0=84=20=EC=8B=9C=20=EA=B8=B8=EC=9D=B4=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EC=88=98=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/surface/useSurfaceShapeBatch.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index de5534a5..d7df9b6d 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -1573,13 +1573,13 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { currentObject.fire('modified') currentObject.fire('polygonMoved') // 화살표와 선 다시 그리기 + setPolygonLinesActualSize(currentObject, true) drawDirectionArrow(currentObject) setTimeout(() => { - setPolygonLinesActualSize(currentObject) changeSurfaceLineType(currentObject) currentObject.dirty = true currentObject.setCoords() - canvas.requestRenderAll() + canvas.renderAll() setCurrentObject(currentObject) }, 500) } From 4b635493fe0a312faf6858e5ad5d9bffdd603606 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 12 Feb 2026 14:22:41 +0900 Subject: [PATCH 05/14] =?UTF-8?q?=EA=B0=84=EA=B2=A9=EC=9D=B4=20=EB=8B=A4?= =?UTF-8?q?=EB=A5=BC=20=EB=95=8C=20=EC=8A=A4=EC=BC=88=EB=A0=88=ED=86=A4?= =?UTF-8?q?=EC=9D=B4=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 93 ++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 14 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index a8a4063e..aeb63ffa 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -401,26 +401,70 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { console.log('baseLinePoints:', orderedBaseLinePoints) // baseLinePoint에서 roofLinePoint 방향으로 45도 대각선 확장 후 가장 가까운 접점 계산 - let roofLineContactPoints = orderedBaseLinePoints.map((point, index) => { + // 각 포인트의 dx, dy, sign 정보 수집 + const contactData = orderedBaseLinePoints.map((point, index) => { const roofPoint = roofLinePoints[index] - if (!roofPoint) return point + if (!roofPoint) return { dx: 0, dy: 0, signDx: 0, signDy: 0 } const dx = roofPoint.x - point.x const dy = roofPoint.y - point.y - const absDx = Math.abs(dx) - const absDy = Math.abs(dy) + return { dx, dy, signDx: Math.sign(dx), signDy: Math.sign(dy) } + }) + + // maxStep: 모든 포인트 중 가장 큰 45도 step + const maxStep = contactData.reduce((max, data) => { + return Math.max(max, Math.min(Math.abs(data.dx), Math.abs(data.dy))) + }, 0) + + // baseLine 폴리곤의 중심 계산 (확장 방향 결정용) + const centroid = orderedBaseLinePoints.reduce((acc, p) => { + acc.x += p.x / orderedBaseLinePoints.length + acc.y += p.y / orderedBaseLinePoints.length + return acc + }, { x: 0, y: 0 }) + + // 모든 포인트를 동일한 maxStep으로 45도 확장 + let roofLineContactPoints = orderedBaseLinePoints.map((point, index) => { + const data = contactData[index] + const { dx, dy } = data + + // dx 또는 dy가 0인 경우 중심에서 바깥쪽 방향으로 확장 + let signDx = data.signDx + let signDy = data.signDy + if (signDx === 0) { + signDx = point.x >= centroid.x ? 1 : -1 + } + if (signDy === 0) { + signDy = point.y >= centroid.y ? 1 : -1 + } + + return { + x: point.x + signDx * maxStep, + y: point.y + signDy * maxStep + } + }) + + const maxContactDistance = Math.hypot(maxStep, maxStep) + + let changRoofLinePoints = orderedBaseLinePoints.map((point, index) => { + const contactPoint = roofLineContactPoints[index] + if (!contactPoint) return point + + const dx = contactPoint.x - point.x + const dy = contactPoint.y - point.y + const len = Math.hypot(dx, dy) // 거리가 0이면 이미 같은 위치 - if (absDx === 0 && absDy === 0) return point + if (len === 0) return point - const step = Math.min(absDx, absDy) + const step = maxContactDistance if (step === 0) return point - const nextX = point.x + Math.sign(dx) * step - const nextY = point.y + Math.sign(dy) * step + const nextX = point.x + (dx / len) * step + const nextY = point.y + (dy / len) * step return { - x: Number(nextX.toFixed(1)), - y: Number(nextY.toFixed(1)) + x: nextX, + y: nextY } }) @@ -429,8 +473,11 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { roofLineContactPoints = movingLineFromSkeleton(roofId, canvas) } - console.log('points:', roofLineContactPoints) - const geoJSONPolygon = toGeoJSON(roofLineContactPoints) + // changRoofLinePoints 좌표를 roof.skeletonPoints에 저장 (원본 roof.points는 유지) + roof.skeletonPoints = changRoofLinePoints.map(p => ({ x: p.x, y: p.y })) + + console.log('points:', changRoofLinePoints) + const geoJSONPolygon = toGeoJSON(changRoofLinePoints) try { // SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거 @@ -440,7 +487,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { // 스켈레톤 데이터를 기반으로 내부선 생성 roof.innerLines = roof.innerLines || [] roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode) - + //console.log("roofInnerLines:::", roof.innerLines); // 캔버스에 스켈레톤 상태 저장 if (!canvas.skeletonStates) { canvas.skeletonStates = {} @@ -1722,10 +1769,28 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { } let eavesLines = [] + // 확장된 외곽선 판별용 + const skPts = roof.skeletonPoints || [] + const isSkeletonOuterEdge = (p1, p2, tolerance = 0.5) => { + for (let si = 0; si < skPts.length; si++) { + const sp1 = skPts[si] + const sp2 = skPts[(si + 1) % skPts.length] + if ((Math.abs(p1.x - sp1.x) < tolerance && Math.abs(p1.y - sp1.y) < tolerance && + Math.abs(p2.x - sp2.x) < tolerance && Math.abs(p2.y - sp2.y) < tolerance) || + (Math.abs(p1.x - sp2.x) < tolerance && Math.abs(p1.y - sp2.y) < tolerance && + Math.abs(p2.x - sp1.x) < tolerance && Math.abs(p2.y - sp1.y) < tolerance)) { + return true + } + } + return false + } + for (let i = 0; i < polygonPoints.length; i++) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; + // 확장된 외곽선에 해당하는 edge는 스킵 + if (skPts.length > 0 && isSkeletonOuterEdge(p1, p2)) continue // 지붕 경계선과 교차 확인 및 클리핑 const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine); @@ -1900,7 +1965,7 @@ function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, is }; skeletonLines.push(newLine); - console.log('skeletonLines', skeletonLines); + //console.log('skeletonLines', skeletonLines); } /** From cf84e51f24be18a1792bd239b279de9a6f486a31 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 12 Feb 2026 15:02:55 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[1455]=EC=B2=98=EB=A7=88=EC=BB=A4?= =?UTF-8?q?=EB=B2=84=20=ED=91=9C=EC=8B=9C=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/basic/step/ModuleTabContents.jsx | 4 ++-- .../floor-plan/modal/basic/step/Placement.jsx | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/floor-plan/modal/basic/step/ModuleTabContents.jsx b/src/components/floor-plan/modal/basic/step/ModuleTabContents.jsx index cda87230..9fd7da9a 100644 --- a/src/components/floor-plan/modal/basic/step/ModuleTabContents.jsx +++ b/src/components/floor-plan/modal/basic/step/ModuleTabContents.jsx @@ -211,8 +211,8 @@ export default function ModuleTabContents({ tabIndex, addRoof, setAddedRoofs, ro diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 091129a8..2dced068 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -194,7 +194,7 @@ const Placement = forwardRef((props, refs) => { type="checkbox" id={item.itemId} name={item.itemId} - checked={selectedItems[item.itemId]} + checked={selectedItems[item.itemId] || false} onChange={(e) => handleSelectedItem(e, item.itemId)} /> @@ -217,8 +217,7 @@ const Placement = forwardRef((props, refs) => { type="text" className="input-origin block" name="row" - value={props.layoutSetup[index]?.row ?? 1} - //defaultValue={0} + value={props.layoutSetup[index]?.row || 1} onChange={(e) => handleLayoutSetup(e, item.itemId, index)} /> @@ -229,8 +228,7 @@ const Placement = forwardRef((props, refs) => { type="text" className="input-origin block" name="col" - value={props.layoutSetup[index]?.col ?? 1} - //defaultValue={0} + value={props.layoutSetup[index]?.col || 1} onChange={(e) => handleLayoutSetup(e, item.itemId, index)} /> @@ -268,7 +266,7 @@ const Placement = forwardRef((props, refs) => { type="radio" name="radio02" id="ra03" - checked={moduleSetupOption.isChidori} + checked={moduleSetupOption.isChidori === true} disabled={isChidoriNotAble} value={'true'} onChange={(e) => handleChangeChidori(e)} @@ -280,7 +278,7 @@ const Placement = forwardRef((props, refs) => { type="radio" name="radio02" id="ra04" - checked={!moduleSetupOption.isChidori} + checked={moduleSetupOption.isChidori === false} value={'false'} onChange={(e) => handleChangeChidori(e)} /> From 55e201db9c9eecb93c8f9d6f88a9c997aa04a78a Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 12 Feb 2026 15:50:27 +0900 Subject: [PATCH 07/14] =?UTF-8?q?=EB=A9=94=EB=89=B4=EB=A5=BC=20=EC=B0=BE?= =?UTF-8?q?=EC=95=84=EA=B0=80=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EB=B0=9C=EC=83=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/option/useCanvasSetting.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index be906baa..edd74c22 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -538,8 +538,10 @@ export function useCanvasSetting(executeEffect = true) { }) /** 메뉴 설정 */ - setMenuByRoofSize(params.roofSizeSet) + //setMenuByRoofSize(params.roofSizeSet) } + //메뉴를 찾아가지 못하는 오류 발생으로 변경 + setMenuByRoofSize(params.roofSizeSet) /** 배치면초기설정 조회 */ fetchBasicSettings(params.planNo, 'basicSettingSave') From 14dc84502c85f4b1e2295d1c5ad13542bb06470b Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 13 Feb 2026 08:26:25 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[1386]T01=20=EA=B3=84=EC=A0=95=EC=9D=B4?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EC=95=88=EA=B1=B4=20=EC=A4=91?= =?UTF-8?q?=20=ED=95=B4=EB=8B=B9=20=ED=8C=90=EB=A7=A4=EC=A0=90=20ID?= =?UTF-8?q?=EA=B0=80=20=EC=97=B4=EB=9E=8C=20=EA=B0=80=EB=8A=A5=ED=95=9C=20?= =?UTF-8?q?=EA=B2=83=EC=97=90=20=ED=95=9C=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/management/StuffDetail.jsx | 12 ++++++++++-- src/components/management/StuffSubHeader.jsx | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index 75db2623..98eec095 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -357,7 +357,11 @@ export default function StuffDetail() { if (res?.data?.createSaleStoreId === 'T01') { if (session?.storeId !== 'T01') { - setShowButton('none') + //T01 계정이 작성한 안건 중 해당 판매점 ID가 열람 가능한 것에 한합니다. + if(session?.storeId !== managementState?.saleStoreId){ + return false + } + } } if (isObjectNotEmpty(res.data)) { @@ -1658,7 +1662,11 @@ export default function StuffDetail() { const getCellDoubleClicked = (params) => { if (managementState?.createSaleStoreId === 'T01') { if (session?.storeId !== 'T01') { - return false + //T01 계정이 작성한 안건 중 해당 판매점 ID가 열람 가능한 것에 한합니다. + if(session?.storeId !== managementState?.saleStoreId){ + return false + } + } } diff --git a/src/components/management/StuffSubHeader.jsx b/src/components/management/StuffSubHeader.jsx index 6cbfd862..b5df5411 100644 --- a/src/components/management/StuffSubHeader.jsx +++ b/src/components/management/StuffSubHeader.jsx @@ -39,7 +39,11 @@ export default function StuffSubHeader({ type }) { if (isObjectNotEmpty(managementState)) { if (managementState?.createSaleStoreId === 'T01') { if (session?.storeId !== 'T01') { - setButtonStyle('none') + //T01 계정이 작성한 안건 중 해당 판매점 ID가 열람 가능한 것에 한합니다. + if(session?.storeId !== managementState?.saleStoreId){ + setButtonStyle('none') + } + } } } From c2b4f5c7df790c3332344ca0f4077224226f434e Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 13 Feb 2026 13:09:08 +0900 Subject: [PATCH 09/14] =?UTF-8?q?=EC=B9=98=EC=88=98=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/CanvasFrame.jsx | 15 +++++++++-- src/components/floor-plan/CanvasMenu.jsx | 2 +- src/components/floor-plan/FloorPlan.jsx | 2 +- .../placementShape/PlacementShapeSetting.jsx | 25 ++++++++++--------- src/hooks/option/useCanvasSetting.js | 22 +++++++++------- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index 9441dc7c..20d6a348 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -33,6 +33,7 @@ import { compasDegAtom } from '@/store/orientationAtom' import { hotkeyStore } from '@/store/hotkeyAtom' import { usePopup } from '@/hooks/usePopup' import { outerLinePointsState } from '@/store/outerLineAtom' +import { canvasSettingState } from '@/store/canvasAtom' export default function CanvasFrame() { const canvasRef = useRef(null) @@ -59,6 +60,7 @@ export default function CanvasFrame() { const { basicSetting, fetchBasicSettings } = useCanvasSetting() const { selectedMenu, setSelectedMenu } = useCanvasMenu() const { initEvent } = useEvent() + const canvasSetting = useRecoilValue(canvasSettingState) const loadCanvas = () => { if (!canvas) return @@ -82,7 +84,12 @@ export default function CanvasFrame() { setSelectedMenu('module') }, 500) } else if (canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL).length > 0) { - setSelectedMenu('outline') + // canvasSetting.roofSizeSet에 따라 메뉴 결정 + if (canvasSetting?.roofSizeSet === '2') { + setSelectedMenu('surface') // 실측값입력 → surface + } else { + setSelectedMenu('outline') // 복시도입력 → outline + } } else { setTimeout(() => { setSelectedMenu('surface') @@ -121,7 +128,11 @@ export default function CanvasFrame() { if (currentCanvasPlan.planNo) { /* 약간의 지연을 줘서 roofMaterials가 로드될 시간을 확보 */ setTimeout(() => { - fetchBasicSettings(Number(currentCanvasPlan.planNo), null) + // 메뉴 이동 시 canvasSetting이 덮어쓰이는 것을 방지 + // 이미 canvasSetting에 roofSizeSet이 있으면 API 호출 건너뛰기 + if (!canvasSetting?.roofSizeSet) { + fetchBasicSettings(Number(currentCanvasPlan.planNo), null) + } }, 100) } }, [currentCanvasPlan, canvas]) diff --git a/src/components/floor-plan/CanvasMenu.jsx b/src/components/floor-plan/CanvasMenu.jsx index 97d878a0..be42c977 100644 --- a/src/components/floor-plan/CanvasMenu.jsx +++ b/src/components/floor-plan/CanvasMenu.jsx @@ -426,7 +426,7 @@ export default function CanvasMenu(props) { return ( (['2', '3'].includes(canvasSetting?.roofSizeSet) && menu.type === 'outline') || (selectedMenu === 'module' && ['placement', 'outline'].includes(menu.type)) || - (isExistModule() && ['placement', 'outline'].some((num) => num === menu.type)) || + (isExistModule() && canvasSetting?.roofSizeSet !== '1' && ['placement', 'outline'].some((num) => num === menu.type)) || (['estimate', 'simulation'].includes(selectedMenu) && ['placement', 'outline', 'surface'].includes(menu.type)) ) } diff --git a/src/components/floor-plan/FloorPlan.jsx b/src/components/floor-plan/FloorPlan.jsx index 417cc559..6098aec3 100644 --- a/src/components/floor-plan/FloorPlan.jsx +++ b/src/components/floor-plan/FloorPlan.jsx @@ -41,7 +41,7 @@ export default function FloorPlan({ children }) { promiseGet({ url: `/api/object/${objectNo}/detail` }).then((res) => { if (res.status === 200) { const { data } = res - console.log(data) + //console.log(data) let surfaceTypeValue if (res.data.surfaceType === 'Ⅲ・Ⅳ') { diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index a537b065..14ff26e1 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -16,7 +16,7 @@ import { globalLocaleStore } from '@/store/localeAtom' import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { usePolygon } from '@/hooks/usePolygon' -import { canvasState, currentMenuState } from '@/store/canvasAtom' +import { canvasState, canvasSettingState, currentMenuState } from '@/store/canvasAtom' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { MENU, POLYGON_TYPE } from '@/common/common' import { useRoofFn } from '@/hooks/common/useRoofFn' @@ -51,6 +51,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla const { drawDirectionArrow } = usePolygon() const { setSurfaceShapePattern } = useRoofFn() const canvas = useRecoilValue(canvasState) + const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) const roofDisplay = useRecoilValue(roofDisplaySelector) const { setPolygonLinesActualSize } = usePolygon() @@ -167,10 +168,6 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla }) }, [currentRoof]) - const handleRoofSizeSetChange = (value) => { - setCurrentRoof({ ...currentRoof, roofSizeSet: value }) - } - const handleRoofAngleSetChange = (value) => { setCurrentRoof({ ...currentRoof, roofAngleSet: value }) } @@ -239,12 +236,8 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla newAddedRoofs[0] = { ...roofInfo } setAddedRoofs(newAddedRoofs) - console.log('save Info', { - ...basicSetting, - selectedRoofMaterial: { - ...newAddedRoofs[0], - }, - }) + // currentRoof의 roofSizeSet을 canvasSetting에 반영 + setCanvasSetting({ ...canvasSetting, roofSizeSet: currentRoof?.roofSizeSet }) /** * 배치면초기설정 저장 (메뉴 변경/useEffect 트리거 없이) @@ -272,7 +265,15 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla /** 지붕면 존재 여부에 따라 메뉴 설정 */ const hasRoofs = canvas.getObjects().some((obj) => obj.name === POLYGON_TYPE.ROOF) - if (hasRoofs) { + + // roofSizeSet에 따라 메뉴 설정 + if (currentRoof?.roofSizeSet === '2') { + setSelectedMenu('surface') + setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING) + } else if (currentRoof?.roofSizeSet === '1') { + setSelectedMenu('outline') + setCurrentMenu(MENU.ROOF_COVERING.EXTERIOR_WALL_LINE) + } else if (hasRoofs) { setSelectedMenu('surface') setCurrentMenu(MENU.BATCH_CANVAS.BATCH_DRAWING) } else { diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index edd74c22..01d49937 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -441,15 +441,19 @@ export function useCanvasSetting(executeEffect = true) { setAddedRoofs(addRoofs) if (openPoint !== 'basicSettingSave') { - setCanvasSetting({ - ...basicSetting, - roofMaterials: addRoofs[0], - planNo: roofsRow[0].planNo, - roofSizeSet: roofsRow[0].roofSizeSet, - roofAngleSet: roofsRow[0].roofAngleSet, - roofsData: roofsArray, - selectedRoofMaterial: addRoofs.find((roof) => roof.selected), - }) + // canvasSetting은 현재 값을 유지하고 basicSetting만 업데이트 + // 새로고침 시 canvasSetting이 바뀌는 문제 방지 + if (!canvasSetting?.roofSizeSet) { + setCanvasSetting({ + ...basicSetting, + roofMaterials: addRoofs[0], + planNo: roofsRow[0].planNo, + roofSizeSet: roofsRow[0].roofSizeSet, + roofAngleSet: roofsRow[0].roofAngleSet, + roofsData: roofsArray, + selectedRoofMaterial: addRoofs.find((roof) => roof.selected), + }) + } } } }) From df4b2a889b32b4971e9de8d865ff2ca490a408c2 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Fri, 20 Feb 2026 11:41:19 +0900 Subject: [PATCH 10/14] =?UTF-8?q?rack=EC=A0=95=EB=B3=B4=20=EC=A0=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EB=AA=BB=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../circuitTrestle/CircuitTrestleSetting.jsx | 124 ++++++++++-------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index 02e9d171..c98b75f0 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -63,6 +63,7 @@ export default function CircuitTrestleSetting({ id }) { const originCanvasViewPortTransform = useRef([]) const [isFold, setIsFold] = useState(false) + const [showHiddenBasicSetting, setShowHiddenBasicSetting] = useState(true) const { makers, @@ -108,6 +109,14 @@ export default function CircuitTrestleSetting({ id }) { } }, []) + // 모듈/가대설정 팝업을 순간적으로 열었다가 닫아 초기화 로직 실행 + useEffect(() => { + const timer = setTimeout(() => { + setShowHiddenBasicSetting(false) + }, 300) + return () => clearTimeout(timer) + }, []) + // 모듈이 설치된 경우 rack설치 여부에 따라 설치 된 모듈 아래에 작은 모듈이 설치되어 있을 경우는 모듈 설치 메뉴 reopen useEffect(() => { const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) @@ -998,63 +1007,70 @@ export default function CircuitTrestleSetting({ id }) { } return ( - - handleClose()} - isFold={isFold} - onFold={() => setIsFold(!isFold)} - /> - -
-
-
-
{getMessage('modal.circuit.trestle.setting.power.conditional.select')}
- -
- {getMessage('modal.circuit.trestle.setting.circuit.allocation')}({getMessage('modal.circuit.trestle.setting.step.up.allocation')}) + <> + {showHiddenBasicSetting && ( +
+ +
+ )} + + handleClose()} + isFold={isFold} + onFold={() => setIsFold(!isFold)} + /> + +
+
+
+
{getMessage('modal.circuit.trestle.setting.power.conditional.select')}
+ +
+ {getMessage('modal.circuit.trestle.setting.circuit.allocation')}({getMessage('modal.circuit.trestle.setting.step.up.allocation')}) +
+ {tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && } + {tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && ( + + )} + {tabNum === 2 && }
- {tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && } - {tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && ( - - )} - {tabNum === 2 && } -
- {tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && ( -
- - -
- )} - {tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && ( -
- - -
- )} - {tabNum === 2 && ( -
- - {/* -
- )} - - + {tabNum === 1 && allocationType === ALLOCATION_TYPE.AUTO && ( +
+ + +
+ )} + {tabNum === 1 && allocationType === ALLOCATION_TYPE.PASSIVITY && ( +
+ + +
+ )} + {tabNum === 2 && ( +
+ + {/* +
+ )} + + + ) } From 4ce66733dd887a579ccb341f89680f5a7db32580 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 23 Feb 2026 10:40:37 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[1490]=EB=A9=94=EB=89=B4=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=20=EB=B3=80=EA=B2=BD=20=EC=9A=94=EC=B2=AD=20(?= =?UTF-8?q?=EC=8B=A4=EC=B8=A1=EA=B0=92=EC=9E=85=EB=A0=A5->=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=EB=A9=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locales/ja.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/ja.json b/src/locales/ja.json index 2953688e..feb9945f 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -76,7 +76,7 @@ "common.setting.rollback": "前に戻る", "modal.cover.outline.remove": "外壁の取り外し", "modal.cover.outline.select.move": "外壁選択の移動", - "plan.menu.placement.surface": "実測値入力", + "plan.menu.placement.surface": "配置面", "plan.menu.placement.surface.slope.setting": "傾斜設定", "plan.menu.placement.surface.drawing": "配置面の描画", "modal.placement.surface.drawing.straight.line": "直線", From 4bf90e7dfbd6be44e0729dd2a550ad471c9a236e Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 23 Feb 2026 11:40:34 +0900 Subject: [PATCH 12/14] =?UTF-8?q?=EC=9D=B4=EB=8F=99=EC=8B=9C=20spare=200.5?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/module/useModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/module/useModule.js b/src/hooks/module/useModule.js index 8e59a1e7..c81c54cb 100644 --- a/src/hooks/module/useModule.js +++ b/src/hooks/module/useModule.js @@ -1059,7 +1059,7 @@ export function useModule() { } const isOutsideSurface = (module, moduleSetupSurface) => { - return !checkModuleDisjointSurface(polygonToTurfPolygon(module, true), polygonToTurfPolygon(moduleSetupSurface, true)) + return !checkModuleDisjointSurface(polygonToTurfPolygon(module, true), polygonToTurfPolygon(moduleSetupSurface, true), 0.5) } const getRowModules = (target) => { From 9ada34c020856b76f0a0d91eeebb9c5ad1b42bcc Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 23 Feb 2026 16:48:16 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[1480]=20=EC=84=A4=EC=A0=95=EA=B0=92=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20(=EC=9D=B4=EC=A0=84=ED=94=8C=EB=9E=9C?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20->=20=ED=98=84=ED=94=8C=EB=9E=9C=EC=A0=80?= =?UTF-8?q?=EC=9E=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../placementShape/PlacementShapeSetting.jsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 14ff26e1..87ad753c 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -16,7 +16,7 @@ import { globalLocaleStore } from '@/store/localeAtom' import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { usePolygon } from '@/hooks/usePolygon' -import { canvasState, canvasSettingState, currentMenuState } from '@/store/canvasAtom' +import { canvasState, canvasSettingState, currentCanvasPlanState, currentMenuState } from '@/store/canvasAtom' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { MENU, POLYGON_TYPE } from '@/common/common' import { useRoofFn } from '@/hooks/common/useRoofFn' @@ -52,6 +52,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla const { setSurfaceShapePattern } = useRoofFn() const canvas = useRecoilValue(canvasState) const [canvasSetting, setCanvasSetting] = useRecoilState(canvasSettingState) + const currentCanvasPlan = useRecoilValue(currentCanvasPlanState) const roofDisplay = useRecoilValue(roofDisplaySelector) const { setPolygonLinesActualSize } = usePolygon() @@ -123,6 +124,15 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla if (openPoint && openPoint === 'canvasMenus') fetchBasicSettings(planNo, openPoint) }, []) + /** + * 현재 활성 플랜이 변경될 때 currentRoof.planNo 업데이트 + */ + useEffect(() => { + if (currentCanvasPlan?.planNo && currentRoof) { + setCurrentRoof(prev => ({ ...prev, planNo: currentCanvasPlan.planNo })) + } + }, [currentCanvasPlan?.planNo]) + /** * 배치면초기설정 데이터 조회 후 화면 오픈 */ @@ -132,7 +142,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setRaftCodes(raftCodeList) setCurrentRoof({ ...addedRoofs[0], - planNo: planNo, + planNo: currentCanvasPlan?.planNo || planNo, roofSizeSet: String(basicSetting.roofSizeSet), roofAngleSet: basicSetting.roofAngleSet, }) @@ -221,7 +231,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla const handleSaveBtn = async () => { const roofInfo = { ...currentRoof, - planNo: basicSetting.planNo, + planNo: currentCanvasPlan?.planNo || basicSetting.planNo, roofCd: roofRef.roofCd.current?.value, width: roofRef.width.current?.value, length: roofRef.length.current?.value, @@ -244,6 +254,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla */ basicSettingSave({ ...basicSetting, + planNo: currentCanvasPlan?.planNo || basicSetting.planNo, /** * 선택된 지붕재 정보 */ From 188e6ee2f2a0e8ddc1d405ae1a63cb27be2f6101 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 23 Feb 2026 17:29:30 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[1475]=20=ED=94=8C=EB=9E=9C=EB=B3=B5?= =?UTF-8?q?=EC=82=AC(=EB=8B=A4=EC=A4=91=EC=A7=80=EB=B6=95=20=EB=B3=B5?= =?UTF-8?q?=EC=82=AC=EC=95=88=EB=90=A8=20-=20=EB=8B=A8=EC=9D=BC=EC=A7=80?= =?UTF-8?q?=EB=B6=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/option/useCanvasSetting.js | 54 ++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 01d49937..fc80fe70 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -570,12 +570,44 @@ export function useCanvasSetting(executeEffect = true) { */ const basicSettingCopySave = async (params) => { try { - const patternData = { - objectNo: correntObjectNo, - planNo: Number(params.planNo), - roofSizeSet: Number(params.roofSizeSet), - roofAngleSet: params.roofAngleSet, - roofMaterialsAddList: params.roofsData.map((item) => ({ + // roofsData가 단일 항목인 경우, 모든 추가된 지붕재(addedRoofs)를 사용하여 다중 항목으로 확장 + let roofMaterialsList = [] + + if (params.roofsData && params.roofsData.length === 1) { + // 단일 항목인 경우 addedRoofs의 모든 항목을 사용 + if (addedRoofs && addedRoofs.length > 0) { + roofMaterialsList = addedRoofs.map((roof, index) => ({ + planNo: Number(params.planNo), + roofApply: roof.selected || index === 0, // 첫 번째 또는 선택된 항목 + roofSeq: index, + roofMatlCd: roof.roofMatlCd, + roofWidth: roof.width || roof.roofWidth, + roofHeight: roof.length || roof.roofHeight, + roofHajebichi: roof.hajebichi || 0, + roofGap: roof.raft || roof.roofGap, + roofLayout: roof.layout || 'P', + roofPitch: roof.pitch || 0, + roofAngle: roof.angle || 0, + })) + } else { + // addedRoofs가 비어있을 경우 원래 단일 항목 사용 + roofMaterialsList = params.roofsData.map((item) => ({ + planNo: Number(item.planNo), + roofApply: item.roofApply, + roofSeq: item.roofSeq, + roofMatlCd: item.roofMatlCd, + roofWidth: item.roofWidth, + roofHeight: item.roofHeight, + roofHajebichi: item.roofHajebichi, + roofGap: item.roofGap, + roofLayout: item.roofLayout, + roofPitch: item.roofPitch, + roofAngle: item.roofAngle, + })) + } + } else { + // 다중 항목인 경우 기존 방식 사용 + roofMaterialsList = params.roofsData.map((item) => ({ planNo: Number(item.planNo), roofApply: item.roofApply, roofSeq: item.roofSeq, @@ -587,7 +619,15 @@ export function useCanvasSetting(executeEffect = true) { roofLayout: item.roofLayout, roofPitch: item.roofPitch, roofAngle: item.roofAngle, - })), + })) + } + + const patternData = { + objectNo: correntObjectNo, + planNo: Number(params.planNo), + roofSizeSet: Number(params.roofSizeSet), + roofAngleSet: params.roofAngleSet, + roofMaterialsAddList: roofMaterialsList, } await post({ url: `/api/canvas-management/canvas-basic-settings`, data: patternData }).then((res) => {