import { GlobalDataContext } from '@/app/GlobalDataProvider' import QSelectBox from '@/components/common/select/QSelectBox' import { useModuleBasicSetting } from '@/hooks/module/useModuleBasicSetting' import { useModuleTrestle } from '@/hooks/module/useModuleTrestle' import { useMessage } from '@/hooks/useMessage' import { currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { roofsState } from '@/store/roofAtom' import { moduleSelectionDataState, selectedModuleState } from '@/store/selectedModuleOptions' 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 const { getMessage } = useMessage() // const [selectedTrestle, setSelectedTrestle] = useState() const currentAngleType = useRecoilValue(currentAngleTypeSelector) const pitchText = useRecoilValue(pitchTextSelector) const [selectedRoof, setSelectedRoof] = useState(null) const [isAutoSelecting, setIsAutoSelecting] = useState(false) // 자동 선택 중인지 상태 const [autoSelectTimeout, setAutoSelectTimeout] = useState(null) // 타임아웃 참조 const autoSelectTimeoutRef = useRef(null) // 공통 타임아웃 설정 (밀리초) const AUTO_SELECT_TIMEOUT = 700 // API 호출 완료 대기 시간 const { trestleState, trestleDetail, dispatch, raftBaseList, trestleList, constMthdList, roofBaseList, constructionList, eavesMargin, ridgeMargin, kerabaMargin, setEavesMargin, setRidgeMargin, setKerabaMargin, lengthBase, setLengthBase, hajebichi, setHajebichi, cvrYn, cvrChecked, snowGdPossYn, snowGdChecked, setCvrYn, setCvrChecked, setSnowGdPossYn, setSnowGdChecked, } = useModuleTrestle({ selectedRoof, }) const selectedModules = useRecoilValue(selectedModuleState) //선택된 모듈 // const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState) const [selectedRaftBase, setSelectedRaftBase] = useState(null) const [selectedTrestle, setSelectedTrestle] = useState(null) const [selectedConstMthd, setSelectedConstMthd] = useState(null) const [selectedConstruction, setSelectedConstruction] = useState(null) const [selectedRoofBase, setSelectedRoofBase] = useState(null) const { managementState } = useContext(GlobalDataContext) 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 && roofs.length > 0 && !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) && roofs && roofs.length > 0) { onChangeRaftBase(roofs[0]); } //모듈 설치 영역 복구 restoreModuleInstArea() }, [roofs, selectedRoof]) // selectedRoof 추가 useEffect(() => { if (flag && moduleSelectionData) { if (JSON.stringify(tempModuleSelectionData.current) === JSON.stringify(moduleSelectionData)) { setTabNum(tabNum + 1) } } }, [flag, moduleSelectionData]) useEffect(() => { if (selectedRoof) { if (moduleSelectionData?.roofConstructions?.length >= selectedRoof.index + 1) { const { construction, trestle, trestleDetail } = moduleSelectionData?.roofConstructions[selectedRoof.index] dispatch({ type: 'SET_INITIALIZE', roof: { common: moduleSelectionData.common, module: moduleSelectionData.module, construction, trestle, trestleDetail, ...selectedRoof }, }) } else { dispatch({ type: 'SET_INITIALIZE', roof: { ...selectedRoof, common: moduleSelectionData.common, module: moduleSelectionData.module } }) } } }, [selectedRoof]) useEffect(() => { if (raftBaseList.length > 0) { setSelectedRaftBase(raftBaseList.find((raft) => raft.clCode === selectedRoof?.raft) ?? null) } else { setSelectedRaftBase(null) } }, [raftBaseList]) useEffect(() => { if (trestleList.length > 0) { 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, autoSelectStep]) useEffect(() => { if (constMthdList.length > 0) { 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, 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) { const existingConstruction = constructionList.find((construction) => construction.constTp === trestleState.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]), icon: 'warning', }) } } else { setSelectedConstruction(null) } }, [constructionList, autoSelectStep]) const getConstructionState = (index) => { if (constructionList && constructionList.length > 0) { if (constructionList[index].constPossYn === 'Y') { if (trestleState && trestleState.constTp === constructionList[index].constTp) { return 'blue' } return 'white' } return 'no-click' } return 'no-click' } const onChangeLength = (e) => { setLengthBase(e) // 다음 단계들 초기화 setSelectedRaftBase(null) setSelectedTrestle(null) setSelectedConstMthd(null) setSelectedRoofBase(null) setSelectedConstruction(null) dispatch({ type: 'SET_LENGTH', roof: { length: e, moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', 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: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: e.clCode, }, }) // 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행 setTimeout(() => { setAutoSelectStep('trestle') }, AUTO_SELECT_TIMEOUT) // API 호출 완료를 위한 더 긴 지연 } const onChangeHajebichi = (e) => { setHajebichi(e) // 다음 단계들 초기화 setSelectedTrestle(null) setSelectedConstMthd(null) setSelectedRoofBase(null) setSelectedConstruction(null) // roofs 배열에서 selectedRoof.index와 같은 인덱스의 지붕 객체 업데이트 if (selectedRoof && selectedRoof.index !== undefined) { const updatedRoofs = roofs.map((roof, index) => (index === selectedRoof.index ? { ...roof, hajebichi: Number(e) } : roof)) setRoofs(updatedRoofs) } dispatch({ type: 'SET_HAJEBICHI', roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: selectedRaftBase?.clCode, hajebichi: e, }, }) // 다음 단계(가대메이커) 자동 선택 설정 - 지연 실행 setTimeout(() => { setAutoSelectStep('trestle') }, AUTO_SELECT_TIMEOUT) } const onChangeTrestleMaker = (e) => { setSelectedTrestle(e) // 다음 단계들 초기화 setSelectedConstMthd(null) setSelectedRoofBase(null) setSelectedConstruction(null) dispatch({ type: 'SET_TRESTLE_MAKER', roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: selectedRaftBase?.clCode, trestleMkrCd: e.trestleMkrCd, }, }) // API 호출 완료 후 다음 단계(공법) 자동 선택 설정 setTimeout(() => { setAutoSelectStep('constMthd') }, AUTO_SELECT_TIMEOUT) } 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, constMthdCd: e.constMthdCd, }, }) // 기존 타임아웃 취소 if (autoSelectTimeoutRef.current) { clearTimeout(autoSelectTimeoutRef.current) } // 자동 선택 중 상태 활성화 setIsAutoSelecting(true) // API 호출 완료 후 다음 단계(지붕밑바탕) 자동 선택 설정 const timeoutId = setTimeout(() => { setAutoSelectStep('roofBase') setIsAutoSelecting(false) }, AUTO_SELECT_TIMEOUT) autoSelectTimeoutRef.current = timeoutId } 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, roofBaseCd: e.roofBaseCd, illuminationTp: managementState?.surfaceTypeValue ?? '', instHt: managementState?.installHeight ?? '', stdWindSpeed: managementState?.standardWindSpeedId ?? '', stdSnowLd: managementState?.verticalSnowCover ?? '', inclCd: selectedRoof?.pitch ?? 0, roofPitch: Math.round(hajebichi ?? 0), }, }) // API 호출 완료 후 다음 단계(construction) 자동 선택 설정 setTimeout(() => { setAutoSelectStep('construction') }, AUTO_SELECT_TIMEOUT) } const handleConstruction = (index) => { if (constructionList[index]?.constPossYn === 'Y') { dispatch({ type: 'SET_CONSTRUCTION', roof: { moduleTpCd: selectedModules.itemTp ?? '', roofMatlCd: selectedRoof?.roofMatlCd ?? '', raft: selectedRaftBase?.clCode, trestleMkrCd: selectedTrestle.trestleMkrCd, constMthdCd: selectedConstMthd.constMthdCd, roofBaseCd: selectedRoofBase.roofBaseCd, illuminationTp: managementState?.surfaceTypeValue ?? '', instHt: managementState?.installHeight ?? '', stdWindSpeed: managementState?.standardWindSpeedId ?? '', stdSnowLd: managementState?.verticalSnowCover ?? '', inclCd: selectedRoof?.pitch ?? 0, roofPitch: Math.round(hajebichi ?? 0), constTp: constructionList[index].constTp, snowGdPossYn: constructionList[index].snowGdPossYn, cvrYn: constructionList[index].cvrYn, mixMatlNo: selectedModules.mixMatlNo, // workingWidth: selectedRoof?.length?.toString() ?? '', workingWidth: lengthBase, }, }) setCvrYn(constructionList[index].cvrYn) setSnowGdPossYn(constructionList[index].snowGdPossYn) setCvrChecked(false) setSnowGdChecked(false) } } const handleChangeRoofMaterial = (index) => { const newAddedRoofs = roofs.map((roof, i) => { if (i === selectedRoof.index) { return { ...selectedRoof, hajebichi, length: lengthBase, eavesMargin, ridgeMargin, kerabaMargin, roofIndex: selectedRoof.index, raft: selectedRaftBase?.clCode, trestle: { hajebichi: hajebichi, length: lengthBase, ...selectedRaftBase, ...selectedTrestle, ...selectedConstMthd, ...selectedRoofBase, }, construction: { ...constructionList.find((data) => data.constTp === trestleState.constTp), cvrYn: cvrYn, snowGdPossYn: snowGdPossYn, cvrChecked: cvrChecked, snowGdChecked: snowGdChecked, setupCover: cvrChecked ?? false, setupSnowCover: snowGdChecked ?? false, }, trestleDetail: trestleDetail, } } return roof }) setRoofs(newAddedRoofs) setSelectedRoof(newAddedRoofs[index]) } const isComplete = async () => { const newAddedRoofs = roofs.map((roof, i) => { if (i === selectedRoof?.index) { return { ...selectedRoof, length: lengthBase, eavesMargin, ridgeMargin, kerabaMargin, roofIndex: roof.index, raft: selectedRaftBase?.clCode, hajebichi: hajebichi, trestle: { length: lengthBase, hajebichi: hajebichi, ...selectedRaftBase, ...selectedTrestle, ...selectedConstMthd, ...selectedRoofBase, }, construction: { ...constructionList.find((data) => newAddedRoofs[index].construction.constTp === data.constTp), cvrYn, snowGdPossYn, cvrChecked, snowGdChecked, setupCover: cvrChecked ?? false, setupSnowCover: snowGdChecked ?? false, }, trestleDetail: trestleDetail, } } return roof }) let result = true for (let i = 0; i < newAddedRoofs.length; i++) { const roof = newAddedRoofs[i] if (!roof.trestle?.trestleMkrCd) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error1', [roof.nameJp]), // 가대메이커를 선택해주세요. icon: 'warning', }) result = false return false } if (!roof.trestle?.constMthdCd) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error2', [roof.nameJp]), // 공법을 선택해주세요. icon: 'warning', }) result = false return false } if (!roof.trestle?.roofBaseCd) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error3', [roof.nameJp]), // 지붕밑바탕을 선택해주세요. icon: 'warning', }) result = false return false } if (!roof.construction?.constTp) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error12', [roof.nameJp]), // 시공법법을 선택해주세요. icon: 'warning', }) result = false return false } if (roof.lenAuth === 'C') { if (!roof.trestle?.length) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error5', [roof.nameJp]), // L 값을 입력해주세요. icon: 'warning', }) result = false return false } } if (['C', 'R'].includes(roof.raftAuth)) { if (!roof?.raft) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error6', [roof.nameJp]), // 서까래 간격을 입력해주세요. icon: 'warning', }) result = false return false } } if (['C', 'R'].includes(roof.roofPchAuth)) { if (!roof?.roofPchBase) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error7', [roof.nameJp]), // 하제비치를 입력해주세요. icon: 'warning', }) result = false return false } } if (!roof?.eavesMargin || !roof?.ridgeMargin || !roof?.kerabaMargin) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error8', [roof.nameJp]), // 모듈 배치 영영 값을 입력해주세요. icon: 'warning', }) result = false return false } if (roof.trestle.trestleMkrCd !== 'NO_DATA') { // 가매 없음이 아닐때는 가대 정보보다 작을 수 없음 if (roof.trestleDetail?.eaveIntvl > roof.eavesMargin) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error9', [roof.trestleDetail?.eaveIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다. icon: 'warning', }) result = false return false } if (roof.trestleDetail?.ridgeIntvl > roof.ridgeMargin) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error10', [roof.trestleDetail?.ridgeIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다. icon: 'warning', }) result = false return false } if (roof.trestleDetail?.kerabaIntvl > roof.kerabaMargin) { Swal.fire({ title: getMessage('modal.module.basic.settting.module.error11', [roof.trestleDetail?.kerabaIntvl, roof.nameJp]), // 모듈 배치 영역은 {0}mm 이상이어야 합니다. icon: 'warning', }) result = false return false } } } if (result) { const newRoofs = newAddedRoofs.map((roof) => { const { addRoof, construction, trestle, trestleDetail, roofConstructions, ...rest } = roof return rest }) setModuleSelectionData({ ...moduleSelectionData, roofConstructions: newAddedRoofs.map((roof, index) => ({ roofIndex: newRoofs[index].index, trestle: roof.trestle, addRoof: newRoofs[index], construction: roof.construction, trestleDetail: roof.trestleDetail, })), }) setFlag(true) tempModuleSelectionData.current = { ...moduleSelectionData, roofConstructions: newAddedRoofs.map((roof, index) => ({ roofIndex: newRoofs[index].index, trestle: roof.trestle, addRoof: newRoofs[index], construction: roof.construction, trestleDetail: roof.trestleDetail, })), } const updatePromises = [ // new Promise((resolve) => { // resolve() // }), new Promise((resolve) => { setRoofs(newRoofs) resolve() }), new Promise((resolve) => { const roofConstructions = newAddedRoofs.map((roof, index) => ({ roofIndex: newRoofs[index].index, addRoof: newRoofs[index], trestle: { ...roof.trestle, raft: roof.raftBaseCd, }, construction: { // ...constructionList.find((construction) => newAddedRoofs[index].construction.constTp === construction.constTp), ...roof.construction, roofIndex: roof.index, selectedIndex: roof.index, }, trestleDetail: roof.trestleDetail, })) trestleTrigger({ roofConstructions, }) setRoofsStore(roofConstructions) resolve() }), ] await Promise.all(updatePromises) return true } return false } useImperativeHandle(ref, () => ({ isComplete, })) return (
{roofs && roofs.map((roof, index) => ( ))}
{selectedRoof && selectedRoof.lenAuth === 'C' && ( <>
L
{ 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'} />
)} {selectedRoof && ['C', 'R'].includes(selectedRoof.raftAuth) && ( <>
{getMessage('modal.module.basic.setting.module.rafter.margin')}
{raftBaseList.length > 0 && ( onChangeRaftBase(e)} showFirstOptionWhenEmpty={true} /> )}
)} {selectedRoof && ['C', 'R'].includes(selectedRoof.roofPchAuth) && ( <>
{getMessage('modal.module.basic.setting.module.hajebichi')}
{ const v = e.target.value if (v === '') { onChangeHajebichi('') return } const n = Number(normalizeDigits(v)) if (Number.isNaN(n)) { onChangeHajebichi('') } else { onChangeHajebichi(n) } }} value={hajebichi} />
)}
{getMessage('modal.module.basic.setting.module.trestle.maker')}
{trestleList && ( onChangeTrestleMaker(e)} showFirstOptionWhenEmpty={true} /> )}
{getMessage('modal.module.basic.setting.module.construction.method')}
{constMthdList && ( onChangeConstMthd(e)} showFirstOptionWhenEmpty={true} /> )}
{getMessage('modal.module.basic.setting.module.under.roof')}
{roofBaseList && ( onChangeRoofBase(e)} showFirstOptionWhenEmpty={true} /> )}
dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, cvrChecked: !trestleState.cvrChecked } })} onChange={() => setCvrChecked(!cvrChecked)} />
dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, snowGdChecked: !trestleState.snowGdChecked } })} onChange={() => setSnowGdChecked(!snowGdChecked)} />
{getMessage('modal.module.basic.setting.module.placement.area')}
{getMessage('modal.module.basic.setting.module.placement.area.eaves')}
dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, eavesMargin: e.target.value } })} onChange={(e) => setEavesMargin(+e.target.value)} />
mm
{getMessage('modal.module.basic.setting.module.placement.area.ridge')}
dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, ridgeMargin: e.target.value } })} onChange={(e) => setRidgeMargin(+e.target.value)} />
mm
{getMessage('modal.module.basic.setting.module.placement.area.keraba')}
dispatch({ type: 'SET_TRESTLE_DETAIL', roof: { ...trestleState, kerabaMargin: e.target.value } })} onChange={(e) => setKerabaMargin(+e.target.value)} />
mm
{getMessage('modal.module.basic.setting.module.placement.margin')}
{getMessage('modal.module.basic.setting.module.placement.margin.horizontal')}
mm
{getMessage('modal.module.basic.setting.module.placement.margin.vertical')}
mm
{getMessage('modal.module.basic.setting.module.setting.info1')}
{getMessage('modal.module.basic.setting.module.setting.info2')}
) }) export default Trestle