diff --git a/src/components/common/select/QSelectBox.jsx b/src/components/common/select/QSelectBox.jsx index 495f3ae6..dbb3c285 100644 --- a/src/components/common/select/QSelectBox.jsx +++ b/src/components/common/select/QSelectBox.jsx @@ -14,6 +14,7 @@ import { useMessage } from '@/hooks/useMessage' * @param {string} targetKey - value에 있는 키 * @param {string} showKey - options 있는 키중 보여줄 키 * @param {object} params - 추가 파라미터 + * @param {boolean} showFirstOptionWhenEmpty - value가 빈값일 때 첫 번째 옵션을 보여줄지 여부 * @returns */ export default function QSelectBox({ @@ -27,6 +28,7 @@ export default function QSelectBox({ showKey = '', params = {}, tagTitle = '', + showFirstOptionWhenEmpty = false, }) { const { getMessage } = useMessage() @@ -39,7 +41,9 @@ export default function QSelectBox({ if (options.length === 0) return title !== '' ? title : getMessage('selectbox.title') if (showKey !== '' && !value) { //value가 없으면 showKey가 있으면 우선 보여준다 - // return options[0][showKey] + if (showFirstOptionWhenEmpty && options.length > 0) { + return options[0][showKey] + } return title !== '' ? title : getMessage('selectbox.title') } else if (showKey !== '' && value) { //value가 있으면 sourceKey와 targetKey를 비교하여 보여준다 @@ -48,12 +52,18 @@ export default function QSelectBox({ return option[sourceKey] === value[targetKey] }) if (!option) { + if (showFirstOptionWhenEmpty && options.length > 0) { + return options[0][showKey] + } return title !== '' ? title : getMessage('selectbox.title') } else { return option[showKey] } } else { //일치하는 조건이 없으면 기본값을 보여준다. + if (showFirstOptionWhenEmpty && options.length > 0) { + return showKey !== '' ? options[0][showKey] : options[0].name + } return title !== '' ? title : getMessage('selectbox.title') } } @@ -74,7 +84,7 @@ export default function QSelectBox({ useEffect(() => { // value && handleClickSelectOption(value) setSelected(handleInitState()) - }, [options, value, sourceKey, targetKey, showKey]) + }, [options, value, sourceKey, targetKey, showKey, showFirstOptionWhenEmpty]) useOnClickOutside(ref, handleClose) diff --git a/src/components/estimate/Estimate.jsx b/src/components/estimate/Estimate.jsx index 4a3337db..f6517960 100644 --- a/src/components/estimate/Estimate.jsx +++ b/src/components/estimate/Estimate.jsx @@ -23,6 +23,7 @@ import { usePopup } from '@/hooks/usePopup' import { useSwal } from '@/hooks/useSwal' import { QcastContext } from '@/app/QcastProvider' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' +import {normalizeDigits, normalizeDecimal} from '@/util/input-utils' export default function Estimate({}) { const [uniqueData, setUniqueData] = useState([]) const [handlePricingFlag, setHandlePricingFlag] = useState(false) @@ -645,11 +646,14 @@ export default function Estimate({}) { newValue = parts[0] + '.' + parts[1].substring(0, 2) } - let pkgAsp = newValue || '0' + let pkgAsp = normalizeDecimal(newValue || '0') //현재 PKG용량값 가져오기 let totVolKw = estimateContextState.totVolKw * 1000 - let pkgTotPrice = parseFloat(pkgAsp?.replaceAll(',', '')) * totVolKw * 1000 + // let pkgTotPrice = parseFloat(pkgAsp?.replaceAll(',', '')) * totVolKw * 1000 + + const pkgAspNumber = Number(normalizeDecimal(pkgAsp)) + const pkgTotPrice = pkgAspNumber * totVolKw * 1000 setEstimateContextState({ pkgAsp: pkgAsp, @@ -663,7 +667,7 @@ export default function Estimate({}) { // 수량 변경 const onChangeAmount = (value, dispOrder, index) => { //itemChangeFlg = 1, partAdd = 0 셋팅 - let amount = Number(value.replace(/[^0-9]/g, '').replaceAll(',', '')) + let amount = Number(normalizeDigits(value)) if (isNaN(amount)) { amount = '0' @@ -701,7 +705,8 @@ export default function Estimate({}) { // 단가 변경 const onChangeSalePrice = (value, dispOrder, index) => { //itemChangeFlg, partAdd 받아온 그대로 - let salePrice = Number(value.replace(/[^0-9]/g, '').replaceAll(',', '')) + let salePrice = Number(normalizeDecimal(value)) + if (isNaN(salePrice)) { salePrice = 0 } else { @@ -940,7 +945,7 @@ export default function Estimate({}) { delete item.showSalePrice delete item.showSaleTotPrice if (item.delFlg === '0') { - let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0 + let amount = Number(normalizeDigits(item.amount)) || 0 let price if (amount === 0) { price = 0 @@ -975,7 +980,7 @@ export default function Estimate({}) { makeUniqueSpecialNoteCd(itemList) itemList.forEach((item) => { if (item.delFlg === '0') { - let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0 + let amount = Number(normalizeDigits(item.amount)) || 0 let salePrice if (item.moduleFlg === '1') { const volKw = (item.pnowW * amount) / 1000 @@ -1009,8 +1014,8 @@ export default function Estimate({}) { } } }) - let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0 - + //let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0 + const pkgAsp = Number(normalizeDecimal(estimateContextState.pkgAsp)) totals.pkgTotPrice = pkgAsp * totals.totVolKw * 1000 totals.supplyPrice = totals.addSupplyPrice + totals.pkgTotPrice totals.vatPrice = totals.supplyPrice * 0.1 @@ -1069,7 +1074,7 @@ export default function Estimate({}) { let dispCableFlgCnt = 0 estimateContextState.itemList.forEach((item) => { if (item.delFlg === '0') { - let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0 + let amount = Number(normalizeDigits(item.amount)) || 0 let salePrice if (item.moduleFlg === '1') { const volKw = (item.pnowW * amount) / 1000 @@ -1102,7 +1107,7 @@ export default function Estimate({}) { item.showSaleTotPrice = '0' } - if (item.dispCableFlg === '1' ) { + if (item.dispCableFlg === '1') { dispCableFlgCnt++ if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') { setCableDbItem(item.itemId) @@ -1118,9 +1123,10 @@ export default function Estimate({}) { setCableDbItem('100037') } - let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0 - + // let pkgAsp = estimateContextState.pkgAsp ? Number(estimateContextState.pkgAsp.replaceAll(',', '')) : 0 + const pkgAsp = Number(normalizeDecimal(estimateContextState.pkgAsp)) totals.pkgTotPrice = pkgAsp * totals.totVolKw * 1000 + totals.supplyPrice = totals.addSupplyPrice + totals.pkgTotPrice totals.vatPrice = totals.supplyPrice * 0.1 totals.totPrice = totals.supplyPrice + totals.vatPrice @@ -1150,7 +1156,7 @@ export default function Estimate({}) { delete item.showSalePrice delete item.showSaleTotPrice if (item.delFlg === '0') { - let amount = Number(item.amount?.replace(/[^0-9]/g, '').replaceAll(',', '')) || 0 + let amount = Number(normalizeDigits(item.amount)) || 0 let price if (amount === 0) { price = 0 @@ -1176,10 +1182,6 @@ export default function Estimate({}) { if (item.dispCableFlg === '1') { dispCableFlgCnt++ - } - - if (item.dispCableFlg === '1'){ - if(item.itemTpCd === 'M12' || item.itemTpCd === 'S13') { setCableDbItem(item.itemId) }else{ diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index af9dd7a4..7881712e 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -1,6 +1,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { getDirectionByPoint } from '@/util/canvas-util' +import { calcLinePlaneSize } from '@/util/qpolygon-utils' export const QLine = fabric.util.createClass(fabric.Line, { type: 'QLine', @@ -17,7 +18,7 @@ export const QLine = fabric.util.createClass(fabric.Line, { initialize: function (points, options, length = 0) { // 소수점 전부 제거 - points = points.map((point) => Number(point?.toFixed(1))) + points = points.map((point) => Number(Number(point)?.toFixed(1))) this.callSuper('initialize', points, { ...options, selectable: options.selectable ?? true }) if (options.id) { @@ -31,14 +32,16 @@ export const QLine = fabric.util.createClass(fabric.Line, { this.direction = options.direction ?? getDirectionByPoint({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 }) this.textMode = options.textMode ?? 'plane' // plane:복시도, actual:실측, none:표시안함 this.textVisible = options.textVisible ?? true - if (length !== 0) { - this.length = length - } else { - this.setLength() - } this.startPoint = { x: this.x1, y: this.y1 } this.endPoint = { x: this.x2, y: this.y2 } + try { + this.setLength() + } catch (e) { + setTimeout(() => { + this.setLength() + }, 100) + } }, init: function () { @@ -66,23 +69,7 @@ export const QLine = fabric.util.createClass(fabric.Line, { }, setLength() { - if (this.attributes?.actualSize !== undefined && this.attributes?.planeSize !== undefined) { - if (this.textMode === 'plane') { - this.length = this.attributes.planeSize / 10 - } else if (this.textMode === 'actual') { - this.length = this.attributes.actualSize / 10 - } - } else { - const scaleX = this.scaleX - const scaleY = this.scaleY - const x1 = this.left - const y1 = this.top - const x2 = this.left + this.width * scaleX - const y2 = this.top + this.height * scaleY - const dx = x2 - x1 - const dy = y2 - y1 - this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) - } + this.length = calcLinePlaneSize(this) / 10 }, addLengthText() { diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx index f10322ca..938b5244 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx @@ -7,6 +7,7 @@ import { useState } from 'react' import { currentObjectState } from '@/store/canvasAtom' import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing' import { useSwal } from '@/hooks/useSwal' +import { normalizeDigits } from '@/util/input-utils' export default function AuxiliaryEdit(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -40,15 +41,15 @@ export default function AuxiliaryEdit(props) { if (currentObject) { copy( currentObject, - arrow2 ? (arrow2 === '←' ? Number(+horizonSize / 10) * -1 : Number(+horizonSize / 10)) : 0, - arrow1 ? (arrow1 === '↑' ? Number(+verticalSize / 10) * -1 : Number(+verticalSize / 10)) : 0, + arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0, + arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0, ) } } else { move( currentObject, - arrow2 ? (arrow2 === '←' ? Number(+horizonSize / 10) * -1 : Number(+horizonSize / 10)) : 0, - arrow1 ? (arrow1 === '↑' ? Number(+verticalSize / 10) * -1 : Number(+verticalSize / 10)) : 0, + arrow2 ? (arrow2 === '←' ? (Number(normalizeDigits(horizonSize)) / 10) * -1 : Number(normalizeDigits(horizonSize)) / 10) : 0, + arrow1 ? (arrow1 === '↑' ? (Number(normalizeDigits(verticalSize)) / 10) * -1 : Number(normalizeDigits(verticalSize)) / 10) : 0, ) } @@ -65,7 +66,7 @@ export default function AuxiliaryEdit(props) {

{getMessage('length')}

- setVerticalSize(e.target.value)} /> + setVerticalSize(normalizeDigits(e.target.value))} />
mm
@@ -87,7 +88,7 @@ export default function AuxiliaryEdit(props) {
- setHorizonSize(e.target.value)} /> + setHorizonSize(normalizeDigits(e.target.value))} />
mm
diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx index 3bb94a4f..5a9cde6f 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx @@ -7,6 +7,7 @@ import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useEffect, useState } from 'react' import Big from 'big.js' import { calcLineActualSize, calcLinePlaneSize } from '@/util/qpolygon-utils' +import { normalizeDigits } from '@/util/input-utils' export default function AuxiliarySize(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -42,7 +43,8 @@ export default function AuxiliarySize(props) { if (checkedRadio === 2) setValue2(value) setSize(0) } else { - value = Big(value.replace(/[^0-9]/g, '')) + //value = Big(value.replace(/[^0-9]/g, '')) + value = Big(normalizeDigits(value)) if (checkedRadio === 1) setValue1(value.toNumber()) if (checkedRadio === 2) setValue2(value.toNumber()) setSize(value.div(10).toNumber()) diff --git a/src/components/floor-plan/modal/basic/step/Module.jsx b/src/components/floor-plan/modal/basic/step/Module.jsx index 8ab6f394..2402b3e0 100644 --- a/src/components/floor-plan/modal/basic/step/Module.jsx +++ b/src/components/floor-plan/modal/basic/step/Module.jsx @@ -10,6 +10,7 @@ import { useDebounceValue } from 'usehooks-ts' import { moduleSelectionDataState } from '@/store/selectedModuleOptions' import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController' import { isObjectNotEmpty } from '@/util/common-utils' +import { normalizeDecimal} from '@/util/input-utils' export default function Module({ setTabNum }) { const { getMessage } = useMessage() @@ -188,7 +189,7 @@ export default function Module({ setTabNum }) { type="text" className="input-origin block" value={inputInstallHeight} - onChange={(e) => setInputInstallHeight(e.target.value)} + onChange={(e) => setInputInstallHeight(normalizeDecimal(e.target.value))} />
m @@ -225,7 +226,7 @@ export default function Module({ setTabNum }) { type="text" className="input-origin block" value={inputVerticalSnowCover} - onChange={(e) => setInputVerticalSnowCover(e.target.value)} + onChange={(e) => setInputVerticalSnowCover(normalizeDecimal(e.target.value))} />
cm diff --git a/src/components/floor-plan/modal/basic/step/Orientation.jsx b/src/components/floor-plan/modal/basic/step/Orientation.jsx index 0b603aed..14618a20 100644 --- a/src/components/floor-plan/modal/basic/step/Orientation.jsx +++ b/src/components/floor-plan/modal/basic/step/Orientation.jsx @@ -8,6 +8,7 @@ import QSelectBox from '@/components/common/select/QSelectBox' import { roofsState } from '@/store/roofAtom' import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' import Swal from 'sweetalert2' +import { normalizeDecimal} from '@/util/input-utils' export const Orientation = forwardRef((props, ref) => { const { getMessage } = useMessage() @@ -177,9 +178,10 @@ export const Orientation = forwardRef((props, ref) => { setInputCompasDeg('-0') return } - if (Number(e) >= -180 && Number(e) <= 180) { - if (numberCheck(Number(e))) { - setInputCompasDeg(Number(e)) + const n = Number(normalizeDecimal(e)) + if (n >= -180 && n <= 180) { + if (numberCheck(n)) { + setInputCompasDeg(n) } } else { setInputCompasDeg(compasDeg) @@ -398,7 +400,7 @@ export const Orientation = forwardRef((props, ref) => {
{getMessage('modal.module.basic.setting.module.placement.area')}
- setInputMargin(e.target.value)} /> + setInputMargin(normalizeDecimal(e.target.value))} />
m
@@ -427,10 +429,10 @@ export const Orientation = forwardRef((props, ref) => { {getMessage('modal.module.basic.setting.module.fitting.height')}
handleChangeInstallHeight(e.target.value)} + onChange={(e) => handleChangeInstallHeight(normalizeDecimal(e.target.value))} />
m @@ -455,10 +457,10 @@ export const Orientation = forwardRef((props, ref) => { {getMessage('modal.module.basic.setting.module.standard.snowfall.amount')}
handleChangeVerticalSnowCover(e.target.value)} + onChange={(e) => handleChangeVerticalSnowCover(normalizeDecimal(e.target.value))} />
cm diff --git a/src/components/floor-plan/modal/basic/step/Placement.jsx b/src/components/floor-plan/modal/basic/step/Placement.jsx index 497d1307..e96cf5c0 100644 --- a/src/components/floor-plan/modal/basic/step/Placement.jsx +++ b/src/components/floor-plan/modal/basic/step/Placement.jsx @@ -12,6 +12,7 @@ import { import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' import { isObjectNotEmpty } from '@/util/common-utils' +import { normalizeDigits } from '@/util/input-utils' import Image from 'next/image' const Placement = forwardRef((props, refs) => { @@ -155,7 +156,7 @@ const Placement = forwardRef((props, refs) => { newLayoutSetup[index] = { ...newLayoutSetup[index], moduleId: itemId, - [e.target.name]: Number(e.target.value), + [e.target.name]: Number(normalizeDigits(e.target.value)), } props.setLayoutSetup(newLayoutSetup) } diff --git a/src/components/floor-plan/modal/basic/step/Trestle.jsx b/src/components/floor-plan/modal/basic/step/Trestle.jsx index e2e7bb78..c8b40ef3 100644 --- a/src/components/floor-plan/modal/basic/step/Trestle.jsx +++ b/src/components/floor-plan/modal/basic/step/Trestle.jsx @@ -9,6 +9,7 @@ import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedM import { forwardRef, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' import Swal from 'sweetalert2' +import { normalizeDigits } from '@/util/input-utils' const Trestle = forwardRef((props, ref) => { const { tabNum, setTabNum, trestleTrigger, roofs, setRoofs, moduleSelectionData, setModuleSelectionData, setRoofsStore } = props @@ -58,12 +59,21 @@ const Trestle = forwardRef((props, ref) => { const { restoreModuleInstArea } = useModuleBasicSetting() const [flag, setFlag] = useState(false) const tempModuleSelectionData = useRef(null) + const [autoSelectStep, setAutoSelectStep] = useState(null) // 'raftBase', 'trestle', 'constMthd', 'roofBase', 'construction' + useEffect(() => { if (roofs && !selectedRoof) { + console.log("roofs:::::", roofs.length) + setLengthBase(roofs[0].length); setSelectedRoof(roofs[0]) } - + if (selectedRoof && selectedRoof.lenAuth === "C") { + onChangeLength(selectedRoof.length); + } + if (selectedRoof && ["C", "R"].includes(selectedRoof.raftAuth)) { + onChangeRaftBase(roofs[0]); + } //모듈 설치 영역 복구 restoreModuleInstArea() }, [roofs]) @@ -100,41 +110,86 @@ const Trestle = forwardRef((props, ref) => { useEffect(() => { if (trestleList.length > 0) { - setSelectedTrestle(trestleList.find((trestle) => trestle.trestleMkrCd === trestleState?.trestleMkrCd) ?? null) + const existingTrestle = trestleList.find( + (trestle) => trestle.trestleMkrCd === trestleState?.trestleMkrCd + ); + if (existingTrestle) { + setSelectedTrestle(existingTrestle) + } else if (autoSelectStep === 'trestle') { + // 자동 선택: 첫 번째 가대메이커 선택 + console.log('Auto selecting first trestle:', trestleList[0]) + const firstTrestle = trestleList[0] + onChangeTrestleMaker(firstTrestle) + // setAutoSelectStep은 onChangeTrestleMaker 내부에서 처리됨 + } } else { setSelectedTrestle(null) } - }, [trestleList]) - - useEffect(() => { - if (roofBaseList.length > 0) { - setSelectedRoofBase(roofBaseList.find((roofBase) => roofBase.roofBaseCd === trestleState?.roofBaseCd) ?? null) - } else { - setSelectedRoofBase(null) - } - }, [roofBaseList]) + }, [trestleList, autoSelectStep]) useEffect(() => { if (constMthdList.length > 0) { - setSelectedConstMthd(constMthdList.find((constMthd) => constMthd.constMthdCd === trestleState?.constMthdCd) ?? null) + const existingConstMthd = constMthdList.find((constMthd) => constMthd.constMthdCd === trestleState?.constMthdCd) + if (existingConstMthd) { + setSelectedConstMthd(existingConstMthd) + } else if (autoSelectStep === 'constMthd') { + // 자동 선택: 첫 번째 공법 선택 + const firstConstMthd = constMthdList[0] + onChangeConstMthd(firstConstMthd) + setAutoSelectStep('roofBase') // 다음 단계로 설정 + } } else { setSelectedConstMthd(null) } - }, [constMthdList]) + }, [constMthdList, autoSelectStep]) + + useEffect(() => { + if (roofBaseList.length > 0) { + const existingRoofBase = roofBaseList.find((roofBase) => roofBase.roofBaseCd === trestleState?.roofBaseCd) + if (existingRoofBase) { + setSelectedRoofBase(existingRoofBase) + } else if (autoSelectStep === 'roofBase') { + // 자동 선택: 첫 번째 지붕밑바탕 선택 + const firstRoofBase = roofBaseList[0] + onChangeRoofBase(firstRoofBase) + setAutoSelectStep('construction') // 다음 단계로 설정 + } + } else { + setSelectedRoofBase(null) + } + }, [roofBaseList, autoSelectStep]) useEffect(() => { if (constructionList.length > 0) { - setSelectedConstruction(constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp) ?? null) + const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState?.construction?.constTp) + if (existingConstruction) { + setSelectedConstruction(existingConstruction) + } else if (autoSelectStep === 'construction') { + // 자동 선택: 첫 번째 가능한 construction 선택 + const availableConstructions = constructionList.filter((construction) => construction.constPossYn === 'Y') + if (availableConstructions.length > 0) { + const firstConstruction = availableConstructions[0] + const firstIndex = constructionList.findIndex((construction) => construction.constTp === firstConstruction.constTp) + handleConstruction(firstIndex) + setAutoSelectStep(null) // 자동 선택 완료 + } else { + Swal.fire({ + title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]), + icon: 'warning', + }) + } + } + if (constructionList.filter((construction) => construction.constPossYn === 'Y').length === 0) { Swal.fire({ - title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]), // 시공법법을 선택해주세요. + title: getMessage('modal.module.basic.settting.module.error4', [selectedRoof?.nameJp]), icon: 'warning', }) } } else { setSelectedConstruction(null) } - }, [constructionList]) + }, [constructionList, autoSelectStep]) const getConstructionState = (index) => { if (constructionList && constructionList.length > 0) { @@ -151,6 +206,13 @@ const Trestle = forwardRef((props, ref) => { const onChangeLength = (e) => { setLengthBase(e) + // 다음 단계들 초기화 + setSelectedRaftBase(null) + setSelectedTrestle(null) + setSelectedConstMthd(null) + setSelectedRoofBase(null) + setSelectedConstruction(null) + dispatch({ type: 'SET_LENGTH', roof: { @@ -160,10 +222,24 @@ const Trestle = forwardRef((props, ref) => { raft: selectedRaftBase?.clCode, }, }) + + // 자동으로 첫 번째 서까래 간격 선택 + if (raftBaseList.length > 0) { + + const inx = raftBaseList.findIndex((raft) => raft.clCode === selectedRoof?.raft) ?? 0 + const firstRaftBase = raftBaseList[inx] + onChangeRaftBase(firstRaftBase) + } } const onChangeRaftBase = (e) => { setSelectedRaftBase(e) + // 다음 단계들 초기화 + setSelectedTrestle(null) + setSelectedConstMthd(null) + setSelectedRoofBase(null) + setSelectedConstruction(null) + dispatch({ type: 'SET_RAFT_BASE', roof: { @@ -172,10 +248,20 @@ const Trestle = forwardRef((props, ref) => { raft: e.clCode, }, }) + + // 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행 + setTimeout(() => { + setAutoSelectStep('trestle') + }, 500) // API 호출 완료를 위한 더 긴 지연 } const onChangeHajebichi = (e) => { setHajebichi(e) + // 다음 단계들 초기화 + setSelectedTrestle(null) + setSelectedConstMthd(null) + setSelectedRoofBase(null) + setSelectedConstruction(null) // roofs 배열에서 selectedRoof.index와 같은 인덱스의 지붕 객체 업데이트 if (selectedRoof && selectedRoof.index !== undefined) { @@ -192,10 +278,20 @@ const Trestle = forwardRef((props, ref) => { hajebichi: e, }, }) + + // 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행 + setTimeout(() => { + setAutoSelectStep('trestle') + }, 500) } const onChangeTrestleMaker = (e) => { setSelectedTrestle(e) + // 다음 단계들 초기화 + setSelectedConstMthd(null) + setSelectedRoofBase(null) + setSelectedConstruction(null) + dispatch({ type: 'SET_TRESTLE_MAKER', roof: { @@ -205,32 +301,48 @@ const Trestle = forwardRef((props, ref) => { trestleMkrCd: e.trestleMkrCd, }, }) + + // API 호출 완료 후 다음 단계(공법) 자동 선택 설정 + setTimeout(() => { + setAutoSelectStep('constMthd') + }, 300) } const onChangeConstMthd = (e) => { setSelectedConstMthd(e) + // 다음 단계 초기화 + setSelectedRoofBase(null) + setSelectedConstruction(null) + dispatch({ type: 'SET_CONST_MTHD', roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: selectedRaftBase?.clCode, - trestleMkrCd: selectedTrestle.trestleMkrCd, + trestleMkrCd: selectedTrestle?.trestleMkrCd, constMthdCd: e.constMthdCd, }, }) + + // API 호출 완료 후 다음 단계(지붕밑바탕) 자동 선택 설정 + setTimeout(() => { + setAutoSelectStep('roofBase') + }, 300) } const onChangeRoofBase = (e) => { setSelectedRoofBase(e) + setSelectedConstruction(null) + dispatch({ type: 'SET_ROOF_BASE', roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: selectedRaftBase?.clCode, - trestleMkrCd: selectedTrestle.trestleMkrCd, - constMthdCd: selectedConstMthd.constMthdCd, + trestleMkrCd: selectedTrestle?.trestleMkrCd, + constMthdCd: selectedConstMthd?.constMthdCd, roofBaseCd: e.roofBaseCd, illuminationTp: managementState?.surfaceTypeValue ?? '', instHt: managementState?.installHeight ?? '', @@ -240,6 +352,11 @@ const Trestle = forwardRef((props, ref) => { roofPitch: Math.round(hajebichi ?? 0), }, }) + + // API 호출 완료 후 다음 단계(construction) 자동 선택 설정 + setTimeout(() => { + setAutoSelectStep('construction') + }, 300) } const handleConstruction = (index) => { @@ -558,7 +675,19 @@ const Trestle = forwardRef((props, ref) => { type="text" className="input-origin block" value={lengthBase} - onChange={(e) => onChangeLength(e.target.value)} + onChange={(e) => { + const v = e.target.value + if (v === '') { + onChangeLength('') + return + } + const n = Number(normalizeDigits(v)) + if (Number.isNaN(n)) { + onChangeLength('') + } else { + onChangeLength(n) + } + }} disabled={selectedRoof.lenAuth === 'R'} />
@@ -581,6 +710,8 @@ const Trestle = forwardRef((props, ref) => { showKey={'clCodeNm'} disabled={selectedRoof.raftAuth === 'R'} onChange={(e) => onChangeRaftBase(e)} + showFirstOptionWhenEmpty={true} + /> )} @@ -598,7 +729,19 @@ const Trestle = forwardRef((props, ref) => { type="text" className="input-origin block" disabled={selectedRoof.roofPchAuth === 'R'} - onChange={(e) => onChangeHajebichi(e.target.value)} + onChange={(e) => { + const v = e.target.value + if (v === '') { + onChangeHajebichi('') + return + } + const n = Number(normalizeDigits(v)) + if (Number.isNaN(n)) { + onChangeHajebichi('') + } else { + onChangeHajebichi(n) + } + }} value={hajebichi} /> @@ -619,6 +762,7 @@ const Trestle = forwardRef((props, ref) => { targetKey={'trestleMkrCd'} showKey={'trestleMkrCdJp'} onChange={(e) => onChangeTrestleMaker(e)} + showFirstOptionWhenEmpty={true} /> )} @@ -637,6 +781,7 @@ const Trestle = forwardRef((props, ref) => { targetKey={'constMthdCd'} showKey={'constMthdCdJp'} onChange={(e) => onChangeConstMthd(e)} + showFirstOptionWhenEmpty={true} /> )} @@ -655,6 +800,7 @@ const Trestle = forwardRef((props, ref) => { showKey={'roofBaseCdJp'} value={selectedRoofBase} onChange={(e) => onChangeRoofBase(e)} + showFirstOptionWhenEmpty={true} /> )} diff --git a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx index 44deb1a0..4c09868a 100644 --- a/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/CircuitTrestleSetting.jsx @@ -120,7 +120,44 @@ export default function CircuitTrestleSetting({ id }) { const beforeCapture = (type) => { setCanvasZoom(100) canvas.set({ zoom: 1 }) - canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + + // roof 객체들을 찾아서 중앙점 계산 + const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') + + if (roofs.length > 0) { + // 모든 roof의 x, y 좌표를 수집 + const allPoints = [] + roofs.forEach((roof) => { + if (roof.getCurrentPoints()) { + roof.getCurrentPoints().forEach((point) => { + allPoints.push({ x: point.x, y: point.y }) + }) + } + }) + + if (allPoints.length > 0) { + // 모든 점들의 중앙값 계산 + const minX = Math.min(...allPoints.map((p) => p.x)) + const maxX = Math.max(...allPoints.map((p) => p.x)) + const minY = Math.min(...allPoints.map((p) => p.y)) + const maxY = Math.max(...allPoints.map((p) => p.y)) + + const centerX = (minX + maxX) / 2 + const centerY = (minY + maxY) / 2 + + // 캔버스 중앙으로 이동하기 위한 오프셋 계산 + const canvasWidth = canvas.getWidth() + const canvasHeight = canvas.getHeight() + const offsetX = canvasWidth / 2 - centerX + const offsetY = canvasHeight / 2 - centerY + + canvas.viewportTransform = [1, 0, 0, 1, offsetX, offsetY] + } else { + canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + } + } else { + canvas.viewportTransform = [1, 0, 0, 1, 0, 0] + } const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) const circuitNumberTexts = canvas.getObjects().filter((obj) => obj.name === 'circuitNumber') @@ -139,40 +176,9 @@ export default function CircuitTrestleSetting({ id }) { // roof polygon들의 중간점 계산 const roofPolygons = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) let x, y - x = 0 //canvas.width / 2 - y = 1000 //canvas.height / 2 - - /*if (roofPolygons.length > 0) { - let minX = Infinity, - minY = Infinity, - maxX = -Infinity, - maxY = -Infinity - - roofPolygons.forEach((obj) => { - const boundingRect = obj.getBoundingRect() - minX = Math.min(minX, boundingRect.left) - minY = Math.min(minY, boundingRect.top) - maxX = Math.max(maxX, boundingRect.left + boundingRect.width) - maxY = Math.max(maxY, boundingRect.top + boundingRect.height) - }) - - x = (minX + maxX) / 2 - y = (minY + maxY) / 2 - } else { - // roof polygon이 없으면 기본 중앙점 사용 - x = canvas.width / 2 - y = canvas.height / 2 - } - - if (x > 1600) { - x = 0 - y = 0 - } - if (y > 1600) { - x = 0 - y = 0 - }*/ - + x = canvas.width / 2 + y = canvas.height / 2 + canvas.zoomToPoint(new fabric.Point(x, y), 0.4) changeFontSize('lengthText', '28') changeFontSize('circuitNumber', '28') diff --git a/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx b/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx index cf79634f..2009e63f 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/PowerConditionalSelect.jsx @@ -74,11 +74,9 @@ export default function PowerConditionalSelect(props) { ] useEffect(() => { - if (makers.length === 0) { - getPcsMakerList().then((res) => { - setMakers(res.data) - }) - } + getPcsMakerList().then((res) => { + setMakers(res.data) + }) }, []) const onCheckSeries = (data) => { 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 281ae5a8..8b880336 100644 --- a/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx +++ b/src/components/floor-plan/modal/circuitTrestle/step/type/PassivityCircuitAllocation.jsx @@ -12,6 +12,7 @@ import { selectedModuleState } from '@/store/selectedModuleOptions' import { circuitNumDisplaySelector } from '@/store/settingAtom' import { useContext, useEffect, useState } from 'react' import { useRecoilState, useRecoilValue } from 'recoil' +import { normalizeDigits } from '@/util/input-utils' export default function PassivityCircuitAllocation(props) { const { @@ -580,7 +581,20 @@ export default function PassivityCircuitAllocation(props) { value={circuitNumber} min={1} max={99} - onChange={(e) => setCircuitNumber(e.target.value)} + onChange={(e) => { + const v = e.target.value + if (v === '') { + setCircuitNumber('') + return + } + const n = Number(normalizeDigits(v)) + if (Number.isNaN(n)) { + setCircuitNumber('') + } else { + const clamped = Math.max(1, Math.min(99, n)) + setCircuitNumber(clamped) + } + }} />