import { useRecoilState, useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil' import { canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState } from '@/store/canvasAtom' import { useContext, useEffect, useRef, useState } from 'react' import { useAxios } from '@/hooks/useAxios' import { useSwal } from '@/hooks/useSwal' import { usePolygon } from '@/hooks/usePolygon' import { correntObjectNoState, addedRoofsState, basicSettingState, roofDisplaySelector, roofMaterialsSelector, selectedRoofMaterialSelector, } from '@/store/settingAtom' import { usePopup } from '@/hooks/usePopup' import { POLYGON_TYPE } from '@/common/common' import { v4 as uuidv4 } from 'uuid' import ActualSizeSetting from '@/components/floor-plan/modal/roofAllocation/ActualSizeSetting' import { useMessage } from '@/hooks/useMessage' import useMenu from '@/hooks/common/useMenu' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useRoofFn } from '@/hooks/common/useRoofFn' import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' import { globalLocaleStore } from '@/store/localeAtom' import { getChonByDegree, getDegreeByChon } from '@/util/canvas-util' import { moduleSelectionDataState } from '@/store/selectedModuleOptions' import { useCanvasPopupStatusController } from '@/hooks/common/useCanvasPopupStatusController' import { outerLinePointsState } from '@/store/outerLineAtom' import { QcastContext } from '@/app/QcastProvider' import { usePlan } from '@/hooks/usePlan' export function useRoofAllocationSetting(id) { const canvas = useRecoilValue(canvasState) const [correntObjectNo, setCorrentObjectNo] = useRecoilState(correntObjectNoState) const roofDisplay = useRecoilValue(roofDisplaySelector) const { drawDirectionArrow, addLengthText, splitPolygonWithLines, splitPolygonWithSeparate } = usePolygon() const [popupId, setPopupId] = useState(uuidv4()) const { addPopup, closePopup, closeAll } = usePopup() const currentObject = useRecoilValue(currentObjectState) const { setSelectedMenu } = useCanvasMenu() const roofMaterials = useRecoilValue(roofMaterialsSelector) const selectedRoofMaterial = useRecoilValue(selectedRoofMaterialSelector) const [basicSetting, setBasicSetting] = useRecoilState(basicSettingState) const [currentRoofMaterial, setCurrentRoofMaterial] = useState(roofMaterials[0]) /** 팝업 내 기준 지붕재 */ const [roofList, setRoofList] = useRecoilState(addedRoofsState) /** 배치면 초기설정에서 선택한 지붕재 배열 */ const [editingLines, setEditingLines] = useState([]) const [currentRoofList, setCurrentRoofList] = useState([]) const currentAngleType = useRecoilValue(currentAngleTypeSelector) const globalLocaleState = useRecoilValue(globalLocaleStore) const [basicInfo, setBasicInfo] = useState(null) const { get, post } = useAxios(globalLocaleState) const { getMessage } = useMessage() const { swalFire } = useSwal() const { setIsGlobalLoading } = useContext(QcastContext) const { setSurfaceShapePattern } = useRoofFn() const { saveCanvas } = usePlan() const [moduleSelectionData, setModuleSelectionData] = useRecoilState(moduleSelectionDataState) const resetPoints = useResetRecoilState(outerLinePointsState) useEffect(() => { /** 배치면 초기설정에서 선택한 지붕재 배열 설정 */ setCurrentRoofList(roofList) }, []) useEffect(() => { /** 지붕면 조회 */ const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) /** roofPolygon.innerLines */ roofBases.forEach((roof) => { roof.innerLines.forEach((line) => { /** 실측값이 없는 경우 라인 두께 4로 설정 */ if (!line.attributes.actualSize || line.attributes?.actualSize === 0) { line.set({ strokeWidth: 4, stroke: 'black', selectable: true }) } /** 현재 선택된 라인인 경우 라인 두께 2로 설정 */ if (editingLines.includes(line)) { line.set({ strokeWidth: 2, stroke: 'black', selectable: true }) } }) }) /** 현재 선택된 객체가 보조라인, 피라미드, 힙인 경우 두께 4로 설정 */ if (currentObject && currentObject.name && ['auxiliaryLine', 'ridge', 'hip'].includes(currentObject.name)) { currentObject.set({ strokeWidth: 4, stroke: '#EA10AC' }) } }, [currentObject]) useEffect(() => { /** 현재 선택된 객체가 보조라인, 피라미드, 힙인 경우 두께 4로 설정 */ const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) if (roofBases.length === 0) { swalFire({ text: getMessage('roofAllocation.not.found'), icon: 'warning' }) closePopup(id) } /** 배치면 초기설정 조회 */ fetchBasicSettings(basicSetting.planNo) }, []) /** * 배치면 초기설정 조회 */ 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 { 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, }, ] } /** * 데이터 설정 */ const selectRoofs = [] for (let i = 0; i < roofsArray.length; i++) { roofMaterials?.map((material) => { if (material.roofMatlCd === roofsArray[i].roofMatlCd) { selectRoofs.push({ ...material, selected: roofsArray[i].roofApply, index: roofsArray[i].roofSeq, id: roofsArray[i].roofMatlCd, width: roofsArray[i].roofWidth, length: roofsArray[i].roofHeight, hajebichi: roofsArray[i].roofHajebichi, raft: roofsArray[i].roofGap, layout: roofsArray[i].roofLayout, pitch: roofsArray[i].roofPitch, angle: roofsArray[i].roofAngle, }) } }) } setBasicSetting({ ...basicSetting, planNo: res[0].planNo, roofSizeSet: res[0].roofSizeSet, roofAngleSet: res[0].roofAngleSet, roofsData: roofsArray, selectedRoofMaterial: selectRoofs.find((roof) => roof.selected), }) setBasicInfo({ planNo: '' + res[0].planNo, roofSizeSet: '' + res[0].roofSizeSet, roofAngleSet: '' + res[0].roofAngleSet }) }) } catch (error) { console.error('Data fetching error:', error) } } /** * 지붕면 할당 저장 */ const basicSettingSave = async () => { try { setIsGlobalLoading(true) const patternData = { objectNo: correntObjectNo, planNo: Number(basicSetting.planNo), roofSizeSet: Number(basicSetting.roofSizeSet), roofAngleSet: basicSetting.roofAngleSet, roofAllocationList: currentRoofList.map((item, index) => ({ planNo: Number(basicSetting.planNo), roofApply: item.selected, roofSeq: index, roofMatlCd: item.roofMatlCd === null || item.roofMatlCd === undefined ? 'ROOF_ID_WA_53A' : item.roofMatlCd, roofWidth: item.width === null || item.width === undefined ? 0 : Number(item.width), roofHeight: item.length === null || item.length === undefined ? 0 : Number(item.length), roofHajebichi: item.hajebichi === null || item.hajebichi === undefined ? 0 : Number(item.hajebichi), roofGap: !item.raft ? item.raftBaseCd : item.raft, roofLayout: item.layout === null || item.layout === undefined ? 'P' : item.layout, roofPitch: item.pitch === null || item.pitch === undefined ? 4 : Number(item.pitch), roofAngle: item.angle === null || item.angle === undefined ? 21.8 : Number(item.angle), })), } await post({ url: `/api/canvas-management/roof-allocation-settings`, data: patternData }).then((res) => { swalFire({ text: getMessage(res.returnMessage) }) setIsGlobalLoading(false) saveCanvas(false) }) //Recoil 설정 //setCanvasSetting({ ...basicSetting }) /** 배치면 초기설정 조회 */ fetchBasicSettings(basicSetting.planNo) } catch (error) { swalFire({ text: error.message, icon: 'error' }) } } /** * 지붕재 추가 */ const onAddRoofMaterial = () => { if (currentRoofList.length >= 4) { swalFire({ type: 'alert', icon: 'error', text: getMessage('roof.exceed.count') }) return } const originCurrentRoofList = currentRoofList.map((roof) => { return { ...roof, selected: false } }) originCurrentRoofList.push({ ...currentRoofMaterial, selected: true, id: currentRoofMaterial.roofMatlCd, name: currentRoofMaterial.roofMatlNm, index: currentRoofList.length, }) setCurrentRoofList(originCurrentRoofList) } /** * 지붕재 삭제 */ const onDeleteRoofMaterial = (idx) => { const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) for (let i = 0; i < roofs.length; i++) { if (roofs[i].roofMaterial?.index === idx) { swalFire({ type: 'alert', icon: 'error', text: getMessage('roof.material.can.not.delete') }) return } } const isSelected = currentRoofList[idx].selected const newRoofList = JSON.parse(JSON.stringify(currentRoofList)).filter((_, index) => index !== idx) if (isSelected) { newRoofList[0].selected = true } setCurrentRoofList(newRoofList) } /** * 선택한 지붕재로 할당 */ const handleSave = () => { /** * 모두 actualSize 있으면 바로 적용 없으면 actualSize 설정 */ if (checkInnerLines()) { addPopup(popupId, 1, ) } else { apply() resetPoints() basicSettingSave() } } /** * 지붕재 오른쪽 마우스 클릭 후 단일로 지붕재 변경 필요한 경우 */ const handleSaveContext = () => { const newRoofList = currentRoofList.map((roof, idx) => { if (roof.index !== idx) { // 기존 저장된 지붕재의 index 수정 const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && obj.roofMaterial?.index === roof.index) roofs.forEach((roof) => { setSurfaceShapePattern(roof, roofDisplay.column, false, { ...roof, index: idx }, true) }) } return { ...roof, index: idx, raft: roof.raft ? roof.raft : roof.raftBaseCd, raftBaseCd: roof.raft ? roof.raft : roof.raftBaseCd } }) setBasicSetting((prev) => { return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) } }) setRoofList(newRoofList) setRoofMaterials(newRoofList) const selectedRoofMaterial = newRoofList.find((roof) => roof.selected) setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true) drawDirectionArrow(currentObject) setModuleSelectionData({ ...moduleSelectionData, roofConstructions: newRoofList }) // modifyModuleSelectionData() closeAll() basicSettingSave() } /** * 기존 세팅된 지붕에 지붕재 내용을 바뀐 내용으로 수정 * @param newRoofMaterials */ const setRoofMaterials = (newRoofMaterials) => { const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) newRoofMaterials.forEach((roofMaterial) => { const index = roofMaterial.index const tempRoofs = roofs.filter((roof) => roof.roofMaterial?.index === index) tempRoofs.forEach((roof) => { setSurfaceShapePattern(roof, roofDisplay.column, false, roofMaterial) }) }) } /** * 지붕면 할당 */ const handleAlloc = () => { if (!checkInnerLines()) { apply() } else { swalFire({ type: 'alert', icon: 'error', text: getMessage('실제치수를 입력해 주세요.') }) } } /** * 실측값 없는 경우 체크 */ const checkInnerLines = () => { const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // roofPolygon.innerLines let result = false roofBases.forEach((roof) => { if (roof.separatePolygon.length === 0) { roof.innerLines.forEach((line) => { if (!line.attributes.actualSize || line.attributes?.actualSize === 0) { line.set({ strokeWidth: 4, stroke: 'black', selectable: true }) result = true } }) } }) if (result) canvas?.renderAll() return result } /** * 지붕면 할당 */ const apply = () => { const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && !obj.roofMaterial) const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { if (roofBase.separatePolygon.length > 0) { splitPolygonWithSeparate(roofBase.separatePolygon) } else { splitPolygonWithLines(roofBase) } } catch (e) { console.log(e) return } /** 라인 삭제 */ roofBase.innerLines.forEach((line) => { canvas.remove(line) }) canvas.remove(roofBase) }) /** 벽면 삭제 */ wallLines.forEach((wallLine) => { canvas.remove(wallLine) }) /** 데이터 설정 */ const newRoofList = currentRoofList.map((roof, idx) => { return { ...roof, index: idx, ...basicInfo, raft: roof.raft ? roof.raft : roof.raftBaseCd } }) setBasicSetting((prev) => { return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) } }) setRoofList(newRoofList) const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof) => { if (roof.isFixed) return roof.set({ isFixed: true }) /** 모양 패턴 설정 */ setSurfaceShapePattern( roof, roofDisplay.column, false, currentRoofList.find((roof) => roof.selected), ) drawDirectionArrow(roof) }) setRoofMaterials(newRoofList) /** 외곽선 삭제 */ const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'outerLinePoint' || obj.name === 'outerLine') removeTargets.forEach((obj) => { canvas.remove(obj) }) setEditingLines([]) closeAll() setSelectedMenu('surface') /** 모듈 선택 데이터 초기화 */ // modifyModuleSelectionData() setModuleSelectionData({ ...moduleSelectionData, roofConstructions: newRoofList }) } /** * 라인 사이즈 설정 */ const setLineSize = (id, size) => { const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) roofBases.forEach((roof) => { roof.innerLines.forEach((line) => { if (id === line.id) { setEditingLines([...editingLines.filter((editLine) => editLine.id !== line.id), line]) line.attributes.actualSize = size line.set({ strokeWidth: 2, stroke: 'black' }) } }) }) canvas?.renderAll() } /** * 지붕재 변경 */ const handleChangeRoofMaterial = (value, index) => { const selectedRoofMaterial = roofMaterials.find((roof) => roof.roofMatlCd === value.id) const newRoofList = currentRoofList.map((roof, idx) => { if (idx === index) { return { ...selectedRoofMaterial, selected: roof.selected } } return roof }) setCurrentRoofList(newRoofList) } /** * 기본 지붕재 radio값 변경 */ const handleDefaultRoofMaterial = (index) => { const newRoofList = currentRoofList.map((roof, idx) => { return { ...roof, selected: idx === index } }) setCurrentRoofList(newRoofList) } /** * 서까래 변경 */ const handleChangeRaft = (e, index) => { const raftValue = e.clCode const newRoofList = currentRoofList.map((roof, idx) => { if (idx === index) { return { ...roof, raft: raftValue } } return roof }) setCurrentRoofList(newRoofList) } /** * 레이아웃 변경 */ const handleChangeLayout = (layoutValue, index) => { const newRoofList = currentRoofList.map((roof, idx) => { if (idx === index) { return { ...roof, layout: layoutValue } } return roof }) setCurrentRoofList(newRoofList) } /** * 치수 입력방법(복시도입력/실측값입력/육지붕) */ const handleChangeInput = (e, type = '', index) => { const value = e.target.value const newRoofList = currentRoofList.map((roof, idx) => { if (idx === index) { return { ...roof, [type]: value } } return roof }) setCurrentRoofList(newRoofList) } /** * 피치 변경 */ const handleChangePitch = (e, index) => { let value = e.target.value const reg = /^[0-9]+(\.[0-9]{0,1})?$/ if (!reg.test(value)) { value = value.substring(0, value.length - 1) } const newRoofList = currentRoofList.map((roof, idx) => { if (idx === index) { const result = currentAngleType === 'slope' ? { pitch: value, angle: getDegreeByChon(value) } : { pitch: getChonByDegree(value), angle: value } return { ...roof, ...result } } return roof }) setCurrentRoofList(newRoofList) } /** * 모듈 선택에서 선택한 데이터 초기화 */ const modifyModuleSelectionData = () => { if (moduleSelectionData.roofConstructions.length > 0) { setModuleSelectionData({ ...moduleSelectionData, roofConstructions: [] }) moduleSelectedDataTrigger({ roofConstructions: [] }) } } /** * 모듈 선택 데이터 트리거 */ const { trigger: moduleSelectedDataTrigger } = useCanvasPopupStatusController(2) return { handleSave, onAddRoofMaterial, onDeleteRoofMaterial, handleAlloc, setLineSize, roofMaterials, selectedRoofMaterial, basicSetting, setBasicSetting, currentRoofMaterial, setCurrentRoofMaterial, handleDefaultRoofMaterial, handleChangeRoofMaterial, handleChangeRaft, handleChangeLayout, handleSaveContext, currentRoofList, handleChangeInput, handleChangePitch, } }