diff --git a/src/components/floor-plan/CanvasFrame.jsx b/src/components/floor-plan/CanvasFrame.jsx index 63dc523a..9441dc7c 100644 --- a/src/components/floor-plan/CanvasFrame.jsx +++ b/src/components/floor-plan/CanvasFrame.jsx @@ -32,6 +32,7 @@ import { useEvent } from '@/hooks/useEvent' import { compasDegAtom } from '@/store/orientationAtom' import { hotkeyStore } from '@/store/hotkeyAtom' import { usePopup } from '@/hooks/usePopup' +import { outerLinePointsState } from '@/store/outerLineAtom' export default function CanvasFrame() { const canvasRef = useRef(null) @@ -45,6 +46,7 @@ export default function CanvasFrame() { const totalDisplay = useRecoilValue(totalDisplaySelector) // 집계표 표시 여부 const { setIsGlobalLoading } = useContext(QcastContext) const resetModuleStatisticsState = useResetRecoilState(moduleStatisticsState) + const resetOuterLinePoints = useResetRecoilState(outerLinePointsState) const resetMakersState = useResetRecoilState(makersState) const resetSelectedMakerState = useResetRecoilState(selectedMakerState) const resetSeriesState = useResetRecoilState(seriesState) @@ -137,6 +139,7 @@ export default function CanvasFrame() { const resetRecoilData = () => { // resetModuleStatisticsState() + resetOuterLinePoints() resetMakersState() resetSelectedMakerState() resetSeriesState() diff --git a/src/components/floor-plan/modal/object/DormerOffset.jsx b/src/components/floor-plan/modal/object/DormerOffset.jsx index fd3eb70a..1881b18e 100644 --- a/src/components/floor-plan/modal/object/DormerOffset.jsx +++ b/src/components/floor-plan/modal/object/DormerOffset.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react' +import { useEffect, useRef, useState } from 'react' import { useMessage } from '@/hooks/useMessage' import WithDraggable from '@/components/common/draggable/WithDraggable' import { useRecoilValue } from 'recoil' @@ -15,8 +15,8 @@ export default function DormerOffset(props) { const { closePopup } = usePopup() const [arrow1, setArrow1] = useState(null) const [arrow2, setArrow2] = useState(null) - const arrow1LengthRef = useRef() - const arrow2LengthRef = useRef() + const arrow1LengthRef = useRef(0) + const arrow2LengthRef = useRef(0) const [arrow1Length, setArrow1Length] = useState(0) const [arrow2Length, setArrow2Length] = useState(0) @@ -59,12 +59,12 @@ export default function DormerOffset(props) { name="" label="" className="input-origin block" - value={arrow1LengthRef.current.value} + value={arrow1LengthRef.current.value ?? 0} ref={arrow1LengthRef} onChange={(value) => setArrow1Length(value)} options={{ allowNegative: false, - allowDecimal: false + allowDecimal: false, }} /> diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index 51c16c1a..e283fff0 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -170,8 +170,8 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla setCurrentRoof({ ...selectedRoofMaterial, - pitch: currentRoof?.pitch, - angle: currentRoof?.angle, + // pitch: currentRoof?.pitch, + // angle: currentRoof?.angle, index: 0, planNo: currentRoof.planNo, roofSizeSet: String(currentRoof.roofSizeSet), @@ -353,19 +353,21 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'} onChange={(value) => { if (index === 0) { - const num = value === '' ? '' : Number(value) + const pitch = value === '' ? '' : Number(value); + const angle = pitch === '' ? '' : getDegreeByChon(pitch); setCurrentRoof(prev => ({ ...prev, - pitch: num === '' ? '' : num, - angle: num === '' ? '' : getDegreeByChon(num), - })) + pitch, + angle + })); } else { - const num = value === '' ? '' : Number(value) - setCurrentRoof( prev => ({ + const angle = value === '' ? '' : Number(value); + const pitch = angle === '' ? '' : getChonByDegree(angle); + setCurrentRoof(prev => ({ ...prev, - pitch: num === '' ? '' : getChonByDegree(num), - angle: num === '' ? '' : num, - })) + pitch, + angle + })); } }} options={{ @@ -514,13 +516,17 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla {/*/>*/} { - setCurrentRoof({ ...currentRoof, value }) + const hajebichi = value === '' ? '' : Number(value); + setCurrentRoof(prev => ({ + ...prev, + hajebichi + })); }} readOnly={currentRoof?.roofPchAuth === 'R'} disabled={currentRoof?.roofSizeSet === '3'} diff --git a/src/components/simulator/Simulator.jsx b/src/components/simulator/Simulator.jsx index 9831d1b3..d873049f 100644 --- a/src/components/simulator/Simulator.jsx +++ b/src/components/simulator/Simulator.jsx @@ -264,7 +264,6 @@ export default function Simulator() { style={{ width: '30%' }} className="select-light" value={pwrGnrSimType} - defaultValue={`D`} onChange={(e) => { handleChartChangeData(e.target.value) setPwrGnrSimType(e.target.value) @@ -334,33 +333,31 @@ export default function Simulator() { - {moduleInfoList.length > 0 ? ( - moduleInfoList.map((moduleInfo) => { - return ( - <> - - {/* 지붕면 */} - {moduleInfo.roofSurface} - {/* 경사각 */} - - {convertNumberToPriceDecimal(moduleInfo.slopeAngle)} - {moduleInfo.classType == 0 ? '寸' : 'º'} - - {/* 방위각(도) */} - {convertNumberToPriceDecimal(moduleInfo.azimuth)} - {/* 태양전지모듈 */} - -
{moduleInfo.itemNo}
- - {/* 매수 */} - {convertNumberToPriceDecimal(moduleInfo.amount)} - - - ) - }) - ) : ( - - {getMessage('common.message.no.data')} + {moduleInfoList.length > 0 ? ( + moduleInfoList.map((moduleInfo) => { + return ( + + {/* 지붕면 */} + {moduleInfo.roofSurface} + {/* 경사각 */} + + {convertNumberToPriceDecimal(moduleInfo.slopeAngle)} + {moduleInfo.classType == 0 ? '寸' : 'º'} + + {/* 방위각(도) */} + {convertNumberToPriceDecimal(moduleInfo.azimuth)} + {/* 태양전지모듈 */} + +
{moduleInfo.itemNo}
+ + {/* 매수 */} + {convertNumberToPriceDecimal(moduleInfo.amount)} + + ) + }) + ) : ( + + {getMessage('common.message.no.data')} )} @@ -385,25 +382,23 @@ export default function Simulator() { - {pcsInfoList.length > 0 ? ( - pcsInfoList.map((pcsInfo) => { - return ( - <> - - {/* 파워컨디셔너 */} - -
{pcsInfo.itemNo}
- - {/* 대 */} - {convertNumberToPriceDecimal(pcsInfo.amount)} - - - ) - }) - ) : ( - - {getMessage('common.message.no.data')} - + {pcsInfoList.length > 0 ? ( + pcsInfoList.map((pcsInfo) => { + return ( + + {/* 파워컨디셔너 */} + +
{pcsInfo.itemNo}
+ + {/* 대 */} + {convertNumberToPriceDecimal(pcsInfo.amount)} + + ) + }) + ) : ( + + {getMessage('common.message.no.data')} + )} diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index af3cee04..072a2987 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -42,6 +42,7 @@ import { useEvent } from '@/hooks/useEvent' import { logger } from '@/util/logger' import { useText } from '@/hooks/useText' import { usePolygon } from '@/hooks/usePolygon' +import { getDegreeByChon } from '@/util/canvas-util' const defaultDotLineGridSetting = { INTERVAL: { @@ -177,8 +178,8 @@ export function useCanvasSetting(executeEffect = true) { raft: item.raftBase && parseInt(item.raftBase), layout: ['ROOF_ID_SLATE', 'ROOF_ID_SINGLE'].includes(item.roofMatlCd) ? ROOF_MATERIAL_LAYOUT.STAIRS : ROOF_MATERIAL_LAYOUT.PARALLEL, hajebichi: item.roofPchBase && parseInt(item.roofPchBase), - pitch: item.pitch ? parseInt(item.pitch) : 4, - angle: item.angle ? parseInt(item.angle) : 21.8, + pitch: item.inclBase ? parseInt(item.inclBase) : 4, + angle: getDegreeByChon(item.inclBase ? parseInt(item.inclBase): 4) //item.angle ? parseInt(item.angle) : 21.8, })) setRoofMaterials(roofLists) return roofLists diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 3ea1894c..f93e230d 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -114,46 +114,54 @@ export function useRoofAllocationSetting(id) { */ const fetchBasicSettings = async (planNo) => { try { - await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }).then((res) => { - let roofsArray = {} - - if (res.length > 0) { - roofsArray = res.map((item) => { - return { - planNo: 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 { - if (roofList.length > 0) { - roofsArray = roofList - } else { - roofsArray = [ - { - planNo: planNo, - roofApply: true, - roofSeq: 0, - roofMatlCd: 'ROOF_ID_WA_53A', - roofWidth: 265, - roofHeight: 235, - roofHajebichi: 0, - roofGap: 'HEI_455', - roofLayout: 'P', + const response = await get({ url: `/api/canvas-management/canvas-basic-settings/by-object/${correntObjectNo}/${planNo}` }); + + let roofsArray = []; + + // API에서 데이터를 성공적으로 가져온 경우 + if (response && response.length > 0) { + roofsArray = response.map((item, index) => ({ + planNo: item.planNo, + roofApply: item.roofApply, + roofSeq: item.roofSeq || index, + roofMatlCd: item.roofMatlCd, + roofWidth: item.roofWidth, + roofHeight: item.roofHeight, + roofHajebichi: item.roofHajebichi, + roofGap: item.roofGap, + roofLayout: item.roofLayout, + roofPitch: item.roofPitch, + roofAngle: item.roofAngle, + selected: index === 0, // 첫 번째 항목을 기본 선택으로 설정 + index: index + })); + } + // API에서 데이터가 없고 기존 roofList가 있는 경우 + else if (roofList && roofList.length > 0) { + roofsArray = roofList.map((roof, index) => ({ + ...roof, + selected: index === 0 // 첫 번째 항목을 기본 선택으로 설정 + })); + } + // 둘 다 없는 경우 기본값 설정 + else { + roofsArray = [ + { + planNo: planNo, + roofApply: true, + roofSeq: 0, + roofMatlCd: 'ROOF_ID_WA_53A', + roofWidth: 265, + roofHeight: 235, + roofHajebichi: 0, + roofGap: 'HEI_455', + roofLayout: 'P', roofPitch: 4, roofAngle: 21.8, }, ] } - } + /** * 데이터 설정 @@ -205,7 +213,7 @@ export function useRoofAllocationSetting(id) { angle: roof.angle ?? '', })) setCurrentRoofList(normalizedRoofs) - }) + } catch (error) { console.error('Data fetching error:', error) } @@ -374,11 +382,18 @@ export function useRoofAllocationSetting(id) { setBasicSetting((prev) => { return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) } }) + const selectedRoofMaterial = newRoofList.find((roof) => roof.selected) + const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && obj.roofMaterial?.index === selectedRoofMaterial.index) + + roofs.forEach((roof) => { + setSurfaceShapePattern(roof, roofDisplay.column, false, { ...selectedRoofMaterial }, true) + drawDirectionArrow(roof) + }) setRoofList(newRoofList) setRoofMaterials(newRoofList) setRoofsStore(newRoofList) - const selectedRoofMaterial = newRoofList.find((roof) => roof.selected) + setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true) drawDirectionArrow(currentObject) modifyModuleSelectionData() diff --git a/src/util/fabric-extensions.js b/src/util/fabric-extensions.js index 9410f764..acccc601 100644 --- a/src/util/fabric-extensions.js +++ b/src/util/fabric-extensions.js @@ -29,22 +29,39 @@ fabric.Rect.prototype.getCurrentPoints = function () { /** * fabric.Group에 getCurrentPoints 메서드를 추가 (도머 그룹용) - * 그룹의 groupPoints를 다시 계산하여 반환 + * 그룹 내 객체들의 점들을 수집하여 현재 월드 좌표를 반환 */ fabric.Group.prototype.getCurrentPoints = function () { - // groupPoints를 다시 계산 + // 그룹 내 객체들로부터 실시간으로 점들을 계산 + if (this._objects && this._objects.length > 0) { + let allPoints = [] - // 그룹에 groupPoints가 있으면 해당 점들을 사용 (도머의 경우) - if (this.groupPoints && Array.isArray(this.groupPoints)) { - const matrix = this.calcTransformMatrix() - console.log('this.groupPoints', this.groupPoints) - return this.groupPoints.map(function (p) { - const point = new fabric.Point(p.x, p.y) - return fabric.util.transformPoint(point, matrix) + // 그룹 내 모든 객체의 점들을 수집 + this._objects.forEach(function (obj) { + if (obj.getCurrentPoints && typeof obj.getCurrentPoints === 'function') { + const objPoints = obj.getCurrentPoints() + allPoints = allPoints.concat(objPoints) + } else if (obj.points && Array.isArray(obj.points)) { + const pathOffset = obj.pathOffset || { x: 0, y: 0 } + const matrix = obj.calcTransformMatrix() + const transformedPoints = obj.points + .map(function (p) { + return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y) + }) + .map(function (p) { + return fabric.util.transformPoint(p, matrix) + }) + allPoints = allPoints.concat(transformedPoints) + } }) + + if (allPoints.length > 0) { + // Convex Hull 알고리즘을 사용하여 외곽 점들만 반환 + return this.getConvexHull(allPoints) + } } - // groupPoints가 없으면 바운딩 박스를 사용 + // 객체가 없으면 바운딩 박스를 사용 const bounds = this.getBoundingRect() const points = [ { x: bounds.left, y: bounds.top },