diff --git a/src/common/common.js b/src/common/common.js index 76632014..6ecc08fc 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -222,6 +222,9 @@ export const SAVE_KEY = [ 'skeletonLines', 'skeleton', 'viewportTransform', + 'outerLineFix', + 'adjustRoofLines', + 'northModuleYn', ] export const OBJECT_PROTOTYPE = [fabric.Line.prototype, fabric.Polygon.prototype, fabric.Triangle.prototype, fabric.Group.prototype] diff --git a/src/components/Main.jsx b/src/components/Main.jsx index 9afadca7..ad7dd3d3 100644 --- a/src/components/Main.jsx +++ b/src/components/Main.jsx @@ -18,8 +18,9 @@ import Config from '@/config/config.export' export default function MainPage() { const [sessionState, setSessionState] = useRecoilState(sessionStore) - const [chagePasswordPopOpen, setChagePasswordPopOpen] = useState(false) - + const [changePasswordPopOpen, setChangePasswordPopOpen] = useState(false) + // 데이터 확인 완료 여부 상태 추가 + const [isSessionLoaded, setIsSessionLoaded] = useState(false) const router = useRouter() const { getMessage } = useMessage() @@ -52,6 +53,14 @@ export default function MainPage() { } } + useEffect(() => { + if (isObjectNotEmpty(sessionState)) { + if (sessionState?.pwdInitYn !== 'Y') { + setChangePasswordPopOpen(true) + } + } + }, [sessionState]) + // 라디오 변경 이벤트 const handleOnChangeRadio = (e) => { setSearchRadioType(e.target.value) @@ -77,7 +86,7 @@ export default function MainPage() { useEffect(() => { if (isObjectNotEmpty(sessionState)) { if (sessionState?.pwdInitYn !== 'Y') { - setChagePasswordPopOpen(true) + setChangePasswordPopOpen(true) } } }, [sessionState]) @@ -86,10 +95,25 @@ export default function MainPage() { const [open, setOpen] = useState(false) const [modalNoticeNo, setModalNoticeNo] = useState('') + useEffect(() => { + if (isObjectNotEmpty(sessionState)) { + if (sessionState?.pwdInitYn !== 'Y') { + setChangePasswordPopOpen(true) + } else { + // pwdInitYn이 'Y'라면 팝업을 닫음 (false) + setChangePasswordPopOpen(false) + } + } + }, [sessionState]) + + //if (!isSessionLoaded) return null + return ( <> {open && } - {(!chagePasswordPopOpen && ( + {changePasswordPopOpen ? ( + + ) : ( <>
@@ -131,11 +155,8 @@ export default function MainPage() {
- )) || ( - <> - - )} + ) } diff --git a/src/components/common/input/CalcInput.jsx b/src/components/common/input/CalcInput.jsx index 5cb2bb42..9cfddfd4 100644 --- a/src/components/common/input/CalcInput.jsx +++ b/src/components/common/input/CalcInput.jsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect, forwardRef } from 'react' +import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react' import { createCalculator } from '@/util/calc-utils' import '@/styles/calc.scss' @@ -23,8 +23,23 @@ export const CalculatorInput = forwardRef( }, [ref]) // Sync displayValue with value prop + // useEffect(() => { + // setDisplayValue(value || '0') + // }, [value]) + useEffect(() => { - setDisplayValue(value || '0') + const newValue = value || '0' + setDisplayValue(newValue) + + // 외부에서 value가 변경될 때 계산기 내부 상태도 동기화 + const calculator = calculatorRef.current + if (calculator) { + // 연산 중이 아닐 때 외부에서 값이 들어오면 현재 피연산자로 설정 + calculator.currentOperand = newValue.toString() + calculator.previousOperand = '' + calculator.operation = undefined + setHasOperation(false) + } }, [value]) // 클릭 외부 감지 @@ -294,9 +309,11 @@ export const CalculatorInput = forwardRef( } else { calculator.currentOperand = filteredValue setHasOperation(false) + // 연산자가 없는 순수 숫자일 때만 부모 컴포넌트의 onChange 호출 + onChange(filteredValue) } - onChange(filteredValue) + //onChange(filteredValue) } } @@ -323,13 +340,19 @@ export const CalculatorInput = forwardRef( // Tab 키는 계산기 숨기고 기본 동작 허용 if (e.key === 'Tab') { + if (hasOperation) { + handleCompute(true) // 계산 수행 + } setShowKeypad(false) return } // 모든 방향키는 기본 동작 허용 if (e.key === 'ArrowLeft' || e.key === 'ArrowRight' || e.key === 'ArrowUp' || e.key === 'ArrowDown') { - setShowKeypad(true) + if (hasOperation) { + handleCompute(true) // 계산 수행 + } + setShowKeypad(false) return } @@ -343,6 +366,12 @@ export const CalculatorInput = forwardRef( return } + // --- 여기서부터는 브라우저의 기본 입력을 막고 계산기 로직만 적용함 --- + if (e.key !== 'Process') { // 한글 입력 등 특수 상황 방지 (필요시) + // e.preventDefault() 호출 위치를 확인하세요. + } + + e.preventDefault() const calculator = calculatorRef.current const { allowDecimal } = options diff --git a/src/components/common/select/QSelectBox.jsx b/src/components/common/select/QSelectBox.jsx index dbb3c285..0e77ccca 100644 --- a/src/components/common/select/QSelectBox.jsx +++ b/src/components/common/select/QSelectBox.jsx @@ -96,7 +96,7 @@ export default function QSelectBox({ title={tagTitle} >

{selected}

-
    +
      {options?.length > 0 && options?.map((option, index) => (
    • handleClickSelectOption(option)}> diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index a8f5f148..ee2fab4b 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -1465,19 +1465,19 @@ export default function Estimate({}) { : 'none', }} > - { - //주문분류 - setHandlePricingFlag(true) - setEstimateContextState({ estimateType: e.target.value, setEstimateContextState }) - }} - /> - + {/* {*/} + {/* //주문분류*/} + {/* setHandlePricingFlag(true)*/} + {/* setEstimateContextState({ estimateType: e.target.value, setEstimateContextState })*/} + {/* }}*/} + {/*/>*/} + {/**/}
      obj.name === 'lengthText' && obj.parentId === this.id) - if (this.textMode === 'none') { - if (thisText) { - this.canvas.remove(thisText) + if (thisText) { + if (this.attributes?.actualSize) { + thisText.set({ actualSize: this.attributes.actualSize }) + } + if (this.attributes?.planeSize) { + thisText.set({ planeSize: this.attributes.planeSize }) } } else { this.setLength() @@ -99,11 +106,6 @@ export const QLine = fabric.util.createClass(fabric.Line, { const x2 = this.left + this.width * scaleX const y2 = this.top + this.height * scaleY - if (thisText) { - thisText.set({ text: this.getLength().toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 }) - this.text = thisText - return - } let left, top if (this.direction === 'left' || this.direction === 'right') { left = (x1 + x2) / 2 @@ -120,6 +122,8 @@ export const QLine = fabric.util.createClass(fabric.Line, { const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI const text = new fabric.Textbox(this.getLength().toString(), { + actualSize: this.attributes?.actualSize, + planeSize: this.attributes?.planeSize, left: left, top: top, fontSize: this.fontSize, diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 8d173bc6..dbe6a9e3 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -36,6 +36,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.separatePolygon = [] this.toFixed = options.toFixed ?? 1 this.baseLines = [] + this.adjustRoofLines = [] // this.colorLines = [] // 소수점 전부 제거 @@ -134,7 +135,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.setCoords() }) - this.on('modified', (e) => { + this.on('modified', () => { this.initLines() this.addLengthText() this.setCoords() @@ -180,8 +181,27 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { return fabric.util.transformPoint(p, matrix) }) this.points = transformedPoints - const { left, top } = this.calcOriginCoords() - this.set('pathOffset', { x: left, y: top }) + + // 바운딩 박스 재계산 (width, height 업데이트 - fill 영역 수정) + const calcDim = this._calcDimensions({}) + this.width = calcDim.width + this.height = calcDim.height + + const newPathOffset = { + x: calcDim.left + this.width / 2, + y: calcDim.top + this.height / 2, + } + this.set('pathOffset', newPathOffset) + + // 변환을 points에 적용했으므로 left, top, angle, scale 모두 리셋 (이중 변환 방지) + this.set({ + left: newPathOffset.x, + top: newPathOffset.y, + angle: 0, + scaleX: 1, + scaleY: 1, + }) + this.setCoords() this.initLines() }) @@ -223,7 +243,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { calculateDegree() { const degrees = [] // polygon.lines를 순회하며 각도를 구해 출력 - this.lines.forEach((line, idx) => { + this.lines.forEach((line) => { const dx = line.x2 - line.x1 const dy = line.y2 - line.y1 const rad = Math.atan2(dy, dx) @@ -258,6 +278,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { ) .forEach((obj) => this.canvas.remove(obj)) this.innerLines = [] + this.adjustRoofLines = [] this.canvas.renderAll() let textMode = 'plane' @@ -339,18 +360,17 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) { // 용마루 -- straight-skeleton - console.log('용마루 지붕') - ///drawRidgeRoof(this.id, this.canvas, textMode) + // console.log('용마루 지붕') drawSkeletonRidgeRoof(this.id, this.canvas, textMode) } else if (isGableRoof(types)) { // A형, B형 박공 지붕 - console.log('패턴 지붕') + // console.log('패턴 지붕') drawGableRoof(this.id, this.canvas, textMode) } else if (isShedRoof(types, this.lines)) { - console.log('한쪽흐름 지붕') + // console.log('한쪽흐름 지붕') drawShedRoof(this.id, this.canvas, textMode) } else { - console.log('변별로 설정') + // console.log('변별로 설정') drawRoofByAttribute(this.id, this.canvas, textMode) } }, @@ -404,7 +424,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { const degree = Big(Math.atan2(dy.toNumber(), dx.toNumber())).times(180).div(Math.PI).toNumber() - // Create new text object if it doesn't exist + // Create a new text object if it doesn't exist const text = new fabric.Text(length.toString(), { left: midPoint.x, top: midPoint.y, diff --git a/src/components/floor-plan/modal/basic/step/Orientation.jsx b/src/components/floor-plan/modal/basic/step/Orientation.jsx index 4feeb41b..fe00c93b 100644 --- a/src/components/floor-plan/modal/basic/step/Orientation.jsx +++ b/src/components/floor-plan/modal/basic/step/Orientation.jsx @@ -452,7 +452,16 @@ export const Orientation = forwardRef((props, ref) => { className="input-origin block" value={inputCompasDeg} readOnly={!hasAnglePassivity} - onChange={(value) => setInputCompasDeg(value)} + onChange={(value) => { + // Convert to number and ensure it's within -180 to 180 range + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + const clampedValue = Math.min(180, Math.max(-180, numValue)); + setInputCompasDeg(String(clampedValue)); + } else { + setInputCompasDeg(value); + } + }} options={{ allowNegative: true, allowDecimal: false diff --git a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx index 1ee73892..b3c38c8e 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -122,21 +122,36 @@ export default function PassivityCircuitAllocation(props) { return } + // targetModule중 북면 설치 여부가 Y인 것과 N인 것이 혼합이면 안됨. + const targetModuleGroup = [ + ...new Set( + canvas + .getObjects() + .filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + .map((obj) => obj.moduleInfo.northModuleYn), + ), + ] + + if (targetModuleGroup.length > 1) { + swalFire({ + text: getMessage('module.circuit.fix.not.same.roof.error'), + type: 'alert', + icon: 'warning', + }) + return + } + switch (pcsTpCd) { case 'INDFCS': { const originHaveThisPcsModules = canvas .getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE && obj.pcs && obj.pcs.id === selectedPcs.id) - // 이미 해당 pcs로 설치된 모듈의 surface의 방향을 구한다. - const originSurfaceList = canvas - .getObjects() - .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && originHaveThisPcsModules.map((obj) => obj.surfaceId).includes(obj.id)) + // 1. 북면모듈, 북면외모듈 혼합 여부 체크 + const targetModuleInfos = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && targetModules.includes(obj.id)) + debugger + const newTargetModuleGroup = [...new Set(targetModuleInfos.concat(originHaveThisPcsModules).map((obj) => obj.moduleInfo.northModuleYn))] - originSurfaceList.concat(originSurfaceList).forEach((surface) => { - surfaceType[`${surface.direction}-${surface.roofMaterial.pitch}`] = surface - }) - - if (Object.keys(surfaceType).length > 1) { + if (newTargetModuleGroup.length > 1) { swalFire({ text: getMessage('module.circuit.fix.not.same.roof.error'), type: 'alert', @@ -229,6 +244,7 @@ export default function PassivityCircuitAllocation(props) { roofSurface: surface.direction, roofSurfaceIncl: +canvas.getObjects().filter((obj) => obj.id === surface.parentId)[0].pitch, roofSurfaceNorthYn: surface.direction === 'north' ? 'Y' : 'N', + roofSurfaceNorthModuleYn: surface.northModuleYn, moduleList: surface.modules.map((module) => { return { itemId: module.moduleInfo.itemId, diff --git a/src/components/floor-plan/modal/lineTypes/Angle.jsx b/src/components/floor-plan/modal/lineTypes/Angle.jsx index 0faad2a4..97bbe416 100644 --- a/src/components/floor-plan/modal/lineTypes/Angle.jsx +++ b/src/components/floor-plan/modal/lineTypes/Angle.jsx @@ -2,6 +2,7 @@ import Image from 'next/image' import { useMessage } from '@/hooks/useMessage' import { normalizeDecimalLimit, normalizeDigits } from '@/util/input-utils' import { CalculatorInput } from '@/components/common/input/CalcInput' +import { useEffect } from 'react' export default function Angle({ props }) { const { getMessage } = useMessage() @@ -31,11 +32,21 @@ export default function Angle({ props }) { className="input-origin block" value={angle1} ref={angle1Ref} - onChange={(value) => setAngle1(value)} + onChange={(value) => { + // Calculate the final value first + let finalValue = value; + const numValue = parseInt(value, 10); + if (!isNaN(numValue)) { + const clampedValue = Math.min(180, Math.max(-180, numValue)); + finalValue = String(clampedValue); + } + // Set state once with the final value + setAngle1(finalValue); + }} placeholder="45" onFocus={() => (angle1Ref.current.value = '')} options={{ - allowNegative: false, + allowNegative: true, allowDecimal: true }} /> diff --git a/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx b/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx index 12ed66be..4919462b 100644 --- a/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx +++ b/src/components/floor-plan/modal/lineTypes/DoublePitch.jsx @@ -26,17 +26,15 @@ export default function DoublePitch({ props }) { arrow2Ref, } = props - const getLength2 = () => { - const angle1Value = angle1Ref.current.value - const angle2Value = angle2Ref.current.value - const length1Value = length1Ref.current.value + const getLength2 = (angle1, angle2, length1) => { + const angle1Value = angle1 !== undefined ? angle1 : angle1Ref.current?.value + const angle2Value = angle2 !== undefined ? angle2 : angle2Ref.current?.value + const length1Value = length1 !== undefined ? length1 : length1Ref.current?.value const arrow1Value = arrow1Ref.current - const arrow2Value = arrow2Ref.current - if (angle1Value !== 0 && length1Value !== 0 && angle2Value !== 0 && arrow1Value !== '') { + if (!isNaN(Number(angle1Value)) && !isNaN(Number(length1Value)) && !isNaN(Number(angle2Value)) && arrow1Value) { const radian1 = (getDegreeByChon(angle1Value) * Math.PI) / 180 - const radian2 = (getDegreeByChon(angle2Value) * Math.PI) / 180 return Math.floor((Math.tan(radian1) * length1Value) / Math.tan(radian2)) } @@ -178,7 +176,7 @@ export default function DoublePitch({ props }) { ref={angle2Ref} onChange={(value) => { setAngle2(value) - setLength2(getLength2()) + setLength2(getLength2(angle1Ref.current?.value, value, length1Ref.current?.value)) }} placeholder="45" onFocus={() => (angle2Ref.current.value = '')} diff --git a/src/components/floor-plan/modal/lineTypes/RightAngle.jsx b/src/components/floor-plan/modal/lineTypes/RightAngle.jsx index ef2f00e2..a4356b4a 100644 --- a/src/components/floor-plan/modal/lineTypes/RightAngle.jsx +++ b/src/components/floor-plan/modal/lineTypes/RightAngle.jsx @@ -61,28 +61,40 @@ export default function RightAngle({ props }) {