diff --git a/src/common/common.js b/src/common/common.js index 9aa7f9f0..09f742f2 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -96,6 +96,10 @@ export const BATCH_TYPE = { OPENING_TEMP: 'openingTemp', SHADOW: 'shadow', SHADOW_TEMP: 'shadowTemp', + TRIANGLE_DORMER: 'triangleDormer', + TRIANGLE_DORMER_TEMP: 'triangleDormerTemp', + PENTAGON_DORMER: 'pentagonDormer', + PENTAGON_DORMER_TEMP: 'pentagonDormerTemp', } // 오브젝트 배치 > 프리입력, 치수입력 export const INPUT_TYPE = { diff --git a/src/components/floor-plan/MenuDepth01.jsx b/src/components/floor-plan/MenuDepth01.jsx index 21631434..8fcb9599 100644 --- a/src/components/floor-plan/MenuDepth01.jsx +++ b/src/components/floor-plan/MenuDepth01.jsx @@ -5,7 +5,7 @@ import { useEffect, useState } from 'react' import { MENU } from '@/common/common' import { currentMenuState } from '@/store/canvasAtom' import { useSetRecoilState } from 'recoil' - +import { useSurfaceShapeBatch } from '@/hooks/surface/useSurfaceShapeBatch' export default function MenuDepth01(props) { const { setShowOutlineModal, @@ -28,6 +28,9 @@ export default function MenuDepth01(props) { const { getMessage } = useMessage() const [activeMenu, setActiveMenu] = useState() const setCurrentMenu = useSetRecoilState(currentMenuState) + + const { deleteAllSurfacesAndObjects } = useSurfaceShapeBatch() + const onClickMenu = ({ id, menu, name }) => { setActiveMenu(menu) setShowOutlineModal(menu === MENU.ROOF_COVERING.EXTERIOR_WALL_LINE) @@ -60,6 +63,11 @@ export default function MenuDepth01(props) { setShowPlaceShapeDrawingModal(id === 1) setShowPlacementSurfaceSettingModal(id === 2) setShowObjectSettingModal(id === 3) + + //배치면 전체 삭제 + if (id === 4) { + deleteAllSurfacesAndObjects() + } } if (type === 'module') { diff --git a/src/components/floor-plan/modal/basic/BasicSetting.jsx b/src/components/floor-plan/modal/basic/BasicSetting.jsx index c6663cba..de2c28eb 100644 --- a/src/components/floor-plan/modal/basic/BasicSetting.jsx +++ b/src/components/floor-plan/modal/basic/BasicSetting.jsx @@ -1,5 +1,5 @@ import { useMessage } from '@/hooks/useMessage' -import WithDraggable from '@/components/common/draggable/withDraggable' +import WithDraggable from '@/components/common/draggable/WithDraggable' import { useState } from 'react' import Orientation from '@/components/floor-plan/modal/basic/step/Orientation' import Module from '@/components/floor-plan/modal/basic/step/Module' diff --git a/src/components/floor-plan/modal/object/ObjectSetting.jsx b/src/components/floor-plan/modal/object/ObjectSetting.jsx index de7e36c3..07a21f0c 100644 --- a/src/components/floor-plan/modal/object/ObjectSetting.jsx +++ b/src/components/floor-plan/modal/object/ObjectSetting.jsx @@ -3,12 +3,10 @@ import { useState, useRef } from 'react' import { useRecoilValue } from 'recoil' import { useMessage } from '@/hooks/useMessage' -import { INPUT_TYPE, BATCH_TYPE } from '@/common/common' import { useEvent } from '@/hooks/useEvent' import { canvasState } from '@/store/canvasAtom' import { useSwal } from '@/hooks/useSwal' -import { polygonToTurfPolygon, rectToPolygon, pointsToTurfPolygon } from '@/util/canvas-util' -import * as turf from '@turf/turf' +import { useObjectBatch } from '@/hooks/object/useObjectBatch' import WithDraggable from '@/components/common/draggable/WithDraggable' import OpenSpace from '@/components/floor-plan/modal/object/type/OpenSpace' @@ -20,9 +18,8 @@ export default function ObjectSetting({ setShowObjectSettingModal }) { const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) const [buttonAct, setButtonAct] = useState(1) - const [preObjects, setPreObjects] = useState([]) - const { addCanvasMouseEventListener, initEvent } = useEvent() const { swalFire } = useSwal() + const { applyOpeningAndShadow } = useObjectBatch() /** * 개구배치, 그림자배치 @@ -35,13 +32,8 @@ export default function ObjectSetting({ setShowObjectSettingModal }) { } const applyObject = () => { - setShowObjectSettingModal(false) - - let preObjectsArray = preObjects const surfaceShapePolygons = canvas?.getObjects().filter((obj) => obj.name === 'surfaceShapeBatch') - console.log(preObjectsArray) - if (surfaceShapePolygons.length === 0) { swalFire({ text: '지붕이 없어요 지붕부터 그리세요', icon: 'error' }) return @@ -49,121 +41,7 @@ export default function ObjectSetting({ setShowObjectSettingModal }) { //개구배치, 그림자배치 if (buttonAct === 1 || buttonAct === 2) { - const type = objectPlacement.typeRef.current.find((radio) => radio.checked).value - const isCrossChecked = objectPlacement.isCrossRef.current.checked - - let rect, isDown, origX, origY - let selectedSurface - - //프리입력 - if (type === INPUT_TYPE.FREE) { - addCanvasMouseEventListener('mouse:down', (e) => { - isDown = true - const pointer = canvas.getPointer(e.e) - - surfaceShapePolygons.forEach((surface) => { - if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { - selectedSurface = surface - } - }) - - if (!selectedSurface) { - swalFire({ text: '지붕안에 그려야해요', icon: 'error' }) - setShowObjectSettingModal(true) //메뉴보이고 - initEvent() //이벤트 초기화 - return - } - - origX = pointer.x - origY = pointer.y - - rect = new fabric.Rect({ - left: origX, - top: origY, - originX: 'left', - originY: 'top', - width: 0, - height: 0, - angle: 0, - stroke: 'black', - }) - - //개구냐 그림자냐에 따라 변경 - rect.set({ - fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.3)', - name: buttonAct === 1 ? BATCH_TYPE.OPENING_TEMP : BATCH_TYPE.SHADOW_TEMP, - }) - - canvas?.add(rect) - }) - - addCanvasMouseEventListener('mouse:move', (e) => { - if (!isDown) return - - if (selectedSurface) { - const pointer = canvas.getPointer(e.e) - const width = pointer.x - origX - const height = pointer.y - origY - - rect.set({ width: Math.abs(width), height: Math.abs(height) }) - - if (width < 0) { - rect.set({ left: Math.abs(pointer.x) }) - } - if (height < 0) { - rect.set({ top: Math.abs(pointer.y) }) - } - - canvas?.renderAll() - } - }) - - addCanvasMouseEventListener('mouse:up', (e) => { - if (rect) { - const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect)) - const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) - - //지붕 밖으로 그렸을때 - if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) { - swalFire({ text: '지붕안에 그리라고요...', icon: 'error' }) - //일단 지워 - const deleteTarget = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING_TEMP || obj.name === BATCH_TYPE.SHADOW_TEMP) - canvas?.remove(...deleteTarget) - setShowObjectSettingModal(true) //메뉴보이고 - initEvent() //이벤트 초기화 - return - } - - // if (!isCrossChecked) { - // const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(rectToPolygon(object), rectPolygon))) - - // console.log(isCross) - - // if (isCross) { - // swalFire({ text: '겹치기 불가요...', icon: 'error' }) - // const deleteTarget = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING_TEMP || obj.name === BATCH_TYPE.SHADOW_TEMP) - // canvas?.remove(...deleteTarget) - // setShowObjectSettingModal(true) //메뉴보이고 - // initEvent() //이벤트 초기화 - // return - // } - // } - - isDown = false - const complateName = buttonAct === 1 ? BATCH_TYPE.OPENING : BATCH_TYPE.SHADOW - rect.set({ name: complateName }) - rect.setCoords() - - preObjectsArray.push(rect) - - console.log('preObjectsArray', preObjectsArray) - setPreObjects(preObjectsArray) - - setShowObjectSettingModal(true) //메뉴보이고 - initEvent() - } - }) - } + applyOpeningAndShadow(objectPlacement, buttonAct, surfaceShapePolygons, setShowObjectSettingModal) } } diff --git a/src/components/floor-plan/modal/object/type/OpenSpace.jsx b/src/components/floor-plan/modal/object/type/OpenSpace.jsx index 2ffe9b26..ab4d287d 100644 --- a/src/components/floor-plan/modal/object/type/OpenSpace.jsx +++ b/src/components/floor-plan/modal/object/type/OpenSpace.jsx @@ -1,23 +1,45 @@ -import { forwardRef } from 'react' +import { forwardRef, useState, useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' import { INPUT_TYPE } from '@/common/common' const OpenSpace = forwardRef((props, refs) => { const { getMessage } = useMessage() + const [selectedType, setSelectedType] = useState(INPUT_TYPE.FREE) + + useEffect(() => { + if (selectedType === INPUT_TYPE.FREE) { + refs.widthRef.current.value = 0 + refs.heightRef.current.value = 0 + } + }, [selectedType]) //체크하면 값바꿈 - return (
- (refs.typeRef.current[0] = el)} /> + (refs.typeRef.current[0] = el)} + onClick={() => setSelectedType(INPUT_TYPE.FREE)} + />
- (refs.typeRef.current[1] = el)} /> + (refs.typeRef.current[1] = el)} + onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)} + />
@@ -29,7 +51,13 @@ const OpenSpace = forwardRef((props, refs) => {
- +
mm
@@ -40,7 +68,13 @@ const OpenSpace = forwardRef((props, refs) => {
- +
mm
diff --git a/src/components/floor-plan/modal/object/type/Shadow.jsx b/src/components/floor-plan/modal/object/type/Shadow.jsx index 71bfb6ed..0945d78c 100644 --- a/src/components/floor-plan/modal/object/type/Shadow.jsx +++ b/src/components/floor-plan/modal/object/type/Shadow.jsx @@ -1,20 +1,45 @@ -import { forwardRef } from 'react' +import { forwardRef, useState, useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' import { INPUT_TYPE } from '@/common/common' const Shadow = forwardRef((props, refs) => { const { getMessage } = useMessage() + + const [selectedType, setSelectedType] = useState(INPUT_TYPE.FREE) + + useEffect(() => { + if (selectedType === INPUT_TYPE.FREE) { + refs.widthRef.current.value = 0 + refs.heightRef.current.value = 0 + } + }, [selectedType]) + return (
- (refs.typeRef.current[0] = el)} /> + (refs.typeRef.current[0] = el)} + onClick={() => setSelectedType(INPUT_TYPE.FREE)} + />
- (refs.typeRef.current[1] = el)} /> + (refs.typeRef.current[1] = el)} + onClick={() => setSelectedType(INPUT_TYPE.DIMENSION)} + />
@@ -26,7 +51,13 @@ const Shadow = forwardRef((props, refs) => {
- +
mm
@@ -37,7 +68,13 @@ const Shadow = forwardRef((props, refs) => {
- +
mm
diff --git a/src/components/management/Stuff.jsx b/src/components/management/Stuff.jsx index bc0e6779..9a0a73c5 100644 --- a/src/components/management/Stuff.jsx +++ b/src/components/management/Stuff.jsx @@ -53,10 +53,8 @@ export default function Stuff() { const onDoubleClick = (e) => { let objectNo = e.target.innerText if (objectNo.substring(0, 1) === 'R') { - console.log('진짜') router.push(`${pathname}/detail?objectNo=${objectNo.toString()}`) } else { - console.log('임시') router.push(`${pathname}/tempdetail?objectNo=${objectNo.toString()}`) } } @@ -87,7 +85,7 @@ export default function Stuff() { }, { field: 'objectNo', - width: '180px', + minWidth: 230, headerName: getMessage('stuff.gridHeader.objectNo'), cellRenderer: function (params) { if (params.data.objectNo) { @@ -122,7 +120,7 @@ export default function Stuff() { headerName: getMessage('stuff.gridHeader.saleStoreId'), cellStyle: { textAlign: 'left' }, }, - { field: 'saleStoreName', headerName: getMessage('stuff.gridHeader.saleStoreName'), cellStyle: { textAlign: 'left' } }, + { field: 'saleStoreName', minWidth: 300, headerName: getMessage('stuff.gridHeader.saleStoreName'), cellStyle: { textAlign: 'left' } }, { field: 'address', headerName: getMessage('stuff.gridHeader.address'), cellStyle: { textAlign: 'left' } }, { field: 'dispCompanyName', headerName: getMessage('stuff.gridHeader.dispCompanyName'), cellStyle: { textAlign: 'left' } }, { field: 'receiveUser', headerName: getMessage('stuff.gridHeader.receiveUser'), cellStyle: { textAlign: 'left' } }, @@ -159,14 +157,11 @@ export default function Stuff() { if (event.column.colId === 'objectNo') { return } else { - console.log(' 상세이동::::::::', event.data) //T 면 임시 R은 진짜 if (event.data.objectNo) { if (event.data.objectNo.substring(0, 1) === 'R') { - console.log('진짜:::::::::') router.push(`${pathname}/detail?objectNo=${event.data.objectNo.toString()}`) } else { - console.log('임시:::::::::::::::::') router.push(`${pathname}/tempdetail?objectNo=${event.data.objectNo.toString()}`) } } diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index 96d2bb11..474e0413 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -1,19 +1,19 @@ 'use client' -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { Button } from '@nextui-org/react' import Select from 'react-dropdown-select' import Link from 'next/link' import { useAxios } from '@/hooks/useAxios' import { globalLocaleStore } from '@/store/localeAtom' -import { isEmptyArray } from '@/util/common-utils' +import { isEmptyArray, isObjectNotEmpty } from '@/util/common-utils' import { useMessage } from '@/hooks/useMessage' import { useForm } from 'react-hook-form' import { useRecoilValue } from 'recoil' import { sessionStore } from '@/store/commonAtom' import FindAddressPop from './popup/FindAddressPop' -import DesignRequestPop from './popup/DesignRequestPop' +import PlanRequestPop from './popup/PlanRequestPop' export default function StuffDetail() { const sessionState = useRecoilValue(sessionStore) @@ -21,7 +21,7 @@ export default function StuffDetail() { const searchParams = useSearchParams() const { getMessage } = useMessage() const globalLocaleState = useRecoilValue(globalLocaleStore) - + const ref = useRef() const { get, post, del } = useAxios(globalLocaleState) //form const formInitValue = { @@ -31,10 +31,12 @@ export default function StuffDetail() { objectName: '', //물건명 objectNameOmit: '', //경칭선택 objectNameKana: '', //물건명 후리가나 - saleStoreId: '', //판매점ID - saleStoreName: '', //판매점명 - otherSaleStoreId: '', - otherSaleStoreName: '', + saleStoreLevel: '', //1차점스토어레벨 + saleStoreId: '', //1차점판매점ID + saleStoreName: '', //1차점판매점명 + otherSaleStoreId: '', //1차점 외 판매점ID + otherSaleStoreName: '', //1차점 외 판매점명 + otherSaleStoreLevel: '', //1차점 외 스토어레벨 zipNo: '', //우편번호 prefId: '', //도도부현 prefName: '', @@ -67,18 +69,19 @@ export default function StuffDetail() { const [windSpeedList, setWindSpeedList] = useState([]) //기준풍속 리스트 const [isFormValid, setIsFormValid] = useState(false) //임시저장, 진짜저장 버튼 컨트롤 - const [showButtonValid, setShowButtonValid] = useState(false) //주소검색 활성화 컨트롤 + const [showAddressButtonValid, setShowAddressButtonValid] = useState(false) //주소검색팝업 활성화 컨트롤 + const [showDesignRequestButtonValid, setShowDesignRequestButtonValid] = useState(false) //설계의뢰팝업 활성화 컨트롤 const objectNo = searchParams.get('objectNo') //url에서 물건번호 꺼내서 바로 set - // const [] //1차판매점 자동완성 선택값 - // const [] //2차판매점 자동완성 선택값 - const [editMode, setEditMode] = useState('NEW') const [detailData, setDetailData] = useState({}) + const [tempDetailData, setTempDetailData] = useState({}) + useEffect(() => { + console.log('objectNo::', objectNo) if (objectNo) { - //console.log('수정화면') + console.log('수정화면') setEditMode('EDIT') if (objectNo.substring(0, 1) === 'R') { @@ -86,38 +89,15 @@ export default function StuffDetail() { setIsFormValid(true) } get({ url: `/api/object/${objectNo}/detail` }).then((res) => { - // console.log('물건번호로 상세 API 호출') + console.log('물건번호로 상세 API 호출') if (res != null) { setDetailData(res) - - // 신규 상세 공통APi - // 도도부현API - get({ url: '/api/object/prefecture/list' }).then((res) => { - if (!isEmptyArray(res)) { - // console.log('도도부현API 결과:::', res) - setPrefCodeList(res) - } - }) - // 임시 1차점 판매점코드 saleStoreId=201TES01 - // T01 - //1차점 : X167 - // get({ url: `/api/object/saleStore/X167/list` }).then((res) => { - get({ url: `/api/object/saleStore/${sessionState?.storeId}/list` }).then((res) => { - if (!isEmptyArray(res)) { - // console.log('판매점 결과:::::', res) - setSaleStoreList(res) - //1차 판매점 자동완성 값 셋팅 - form.setValue('saleStoreId', res[0].saleStoreId) - //1차 판매점 번호 셋팅 - form.setValue('saleStoreName', res[0].saleStoreId) - setOtherSaleStoreList([]) - } - }) } }) } else { // 신규 상세 공통APi // 도도부현API + console.log('신규화면') get({ url: '/api/object/prefecture/list' }).then((res) => { if (!isEmptyArray(res)) { // console.log('신규화면 도도부현API 결과:::', res) @@ -135,28 +115,57 @@ export default function StuffDetail() { //1차점 셀렉트박스 setSaleStoreList(firstList) - //1차 판매점 자동완성 값 셋팅 - //form.setValue('saleStoreId', firstList[0].saleStoreId) - //1차 판매점 번호 셋팅 - form.setValue('saleStoreName', firstList[0]?.saleStoreId) - //1차점 아닌 판매점 셀렉트박스 setOriginOtherSaleStoreList(otherList) setOtherSaleStoreList(otherList) - // form.setValue('otherSaleStoreId', otherList[0].saleStoreId) - form.setValue('otherSaleStoreName', otherList[0]?.saleStoreId) } }) } }, [objectNo]) + useEffect(() => { + if (isObjectNotEmpty(detailData)) { + console.log('상세데이타:::::::', detailData) + // 도도부현API + get({ url: '/api/object/prefecture/list' }).then((res) => { + if (!isEmptyArray(res)) { + // console.log('도도부현API 결과:::', res) + setPrefCodeList(res) + } + }) + + //상세정보로 1차점 목록등 셋팅?? + // 임시 1차점 판매점코드 saleStoreId=201TES01 + // T01 + //1차점 : X167 + // get({ url: `/api/object/saleStore/X167/list` }).then((res) => { + // get({ url: `/api/object/saleStore/${sessionState?.storeId}/list` }).then((res) => { + // if (!isEmptyArray(res)) { + // // console.log('판매점 결과:::::', res) + // setSaleStoreList(res) + // //1차 판매점 자동완성 값 셋팅 + // form.setValue('saleStoreId', res[0].saleStoreId) + // //1차 판매점 번호 셋팅 + // form.setValue('saleStoreName', res[0].saleStoreId) + // setOtherSaleStoreList([]) + // } + // }) + } + }, [detailData]) + + useEffect(() => { + if (isObjectNotEmpty(tempDetailData)) { + console.log('임시저장하고 새로고침했을때:::::::::', tempDetailData) + } + }, [tempDetailData]) + //1차점 변경 이벤트 const onSelectionChange = (key) => { if (!isEmptyArray(key)) { setOtherSaleStoreList(otherSaleStoreList) form.setValue('saleStoreId', key[0].saleStoreId) - form.setValue('saleStoreName', key[0].saleStoreId) - + form.setValue('saleStoreName', key[0].saleStoreName) + form.setValue('saleStoreLevel', key[0].saleStoreLevel) //선택한 1차점 정보로 2차점 list 추리기 //長府工産株式会社 大阪支社 let newOtherSaleStoreList = originOtherSaleStoreList.filter((row) => row.firstAgentId === key[0].saleStoreId) @@ -165,7 +174,13 @@ export default function StuffDetail() { //X누름 form.setValue('saleStoreId', '') form.setValue('saleStoreName', '') + form.setValue('saleStoreLevel', '') + form.setValue('otherSaleStoreId', '') + form.setValue('otherSaleStoreName', '') + form.setValue('otherSaleStoreLevel', '') setOtherSaleStoreList(originOtherSaleStoreList) + //1차점 지웠을때 2차점 자동완성 초기화 + handleClear() } } @@ -173,60 +188,32 @@ export default function StuffDetail() { const onSelectionChange2 = (key) => { if (!isEmptyArray(key)) { form.setValue('otherSaleStoreId', key[0].saleStoreId) - form.setValue('otherSaleStoreId', key[0].saleStoreId) + form.setValue('otherSaleStoreName', key[0].saleStoreName) + form.setValue('otherSaleStoreLevel', key[0].saleStoreLevel) } else { form.setValue('otherSaleStoreId', '') - form.setValue('otherSaleStoreId', '') + form.setValue('otherSaleStoreName', '') + form.setValue('otherSaleStoreLevel', '') } } - // 우편번호 숫자만 체크 - const _zipNo = watch('zipNo') - useEffect(() => { - if (_zipNo !== '' && _zipNo.length === 7 && !_zipNo.match(/\D/g)) { - //setButtonValid(true) + + //1차점 지웠을때 2차점 자동완성 초기화 + const handleClear = () => { + if (ref.current.state.dropDown) { + ref.current.methods.dropDown() } else { - //setButtonValid(false) + ref.current.state.values = [] } - }, [_zipNo]) + } //팝업에서 넘어온 우편정보 const setZipInfo = (info) => { - console.log('팝업에서 넘어온 데이타::::::::', info) - - // const params = { - // zipcode: _zipNo, - // } - - // get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => { - // //7830060 - // //9302226 - // //0790177 3개짜리 - // if (res.status === 200) { - // if (res.results != null) { - // console.log('주소검색::', res.results) - // // console.log('prefcode::', res.results[0].prefcode) - // // console.log('address::', res.results[0].address2 + res.results[0].address3) - // setPrefValue(res.results[0].prefcode) - // form.setValue('prefId', res.results[0].prefcode) - // form.setValue('prefName', res.results[0].address1) - // form.setValue('address', res.results[0].address2 + res.results[0].address3) - // } else { - // alert('등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.') - // form.setValue('prefId', '') - // form.setValue('prefName', '') - // form.setValue('address', '') - // form.setValue('zipNo', '') - // setPrefValue('') - // setAreaIdList([]) - // form.setValue('areaId', '') - // // form.setValue('areaName', '') - // setWindSpeedList([]) - // form.setValue('windSpeed', '') - // } - // } else { - // alert(res.message) - // } - // }) + // console.log('팝업에서 넘어온 데이타::::::::', info) + setPrefValue(info.prefId) + form.setValue('prefId', info.prefId) + form.setValue('prefName', info.address1) + form.setValue('address', info.address2 + info.address3) + form.setValue('zipNo', info.zipNo) } //임시저장 저장 버튼 컨트롤 @@ -251,7 +238,7 @@ export default function StuffDetail() { const _objectName = watch('objectName') const _objectNameOmit = watch('objectNameOmit') const _saleStoreId = watch('saleStoreId') - const _otherSaleStoreId = watch('otherSaleStoreId') + const _saleStoreLevel = watch('saleStoreLevel') const _prefId = watch('prefId') const _address = watch('address') const _areaId = watch('areaId') //new @@ -260,68 +247,56 @@ export default function StuffDetail() { const _installHeight = watch('installHeight') useEffect(() => { - console.log('mode:::::', editMode) if (editMode === 'NEW') { const formData = form.getValues() - console.log('폼::::::::::::', formData) + // console.log('임시저장폼::::::::::::', formData) let errors = {} - if (!_dispCompanyName || _dispCompanyName.trim().length === 0) { + if (!formData.dispCompanyName || formData.dispCompanyName.trim().length === 0) { errors.dispCompanyName = true } - if (!_objectName || _objectName.trim().length === 0) { + if (!formData.objectName || formData.objectName.trim().length === 0) { errors.objectName = true } - if (!_objectNameOmit) { + if (!formData.objectNameOmit) { errors.objectNameOmit = true } - if (!_saleStoreId) { + if (!formData.saleStoreId) { errors.saleStoreId = true } - // if (!_otherSaleStoreId) { - // errors.otherSaleStoreId = true - // } - - if (!_zipNo || _zipNo.length != 7) { - errors.zipCode = true - } - - if (!_prefId) { + if (!formData.prefId) { errors.prefId = true } - if (!_address.trim().length === 0) { - errors.address = true - } - - if (!_areaId) { + if (!formData.areaId) { errors.areaId = true } - if (!_windSpeed) { + if (!formData.windSpeed) { errors.windSpeed = true } - if (!_verticalSnowCover) { + if (!formData.verticalSnowCover) { errors.verticalSnowCover = true } - if (!_installHeight) { + if (!formData.installHeight) { errors.installHeight = true } - console.log('errors::', errors) + // console.log('임시저장용::', errors) setIsFormValid(Object.keys(errors).length === 0) } else { - // console.log('상세일때 폼체크') + console.log('상세일때 폼체크') } }, [ _dispCompanyName, _objectName, _objectNameOmit, _saleStoreId, + _saleStoreLevel, // _otherSaleStoreId, - _zipNo, + // _otherSaleStoreLevel, _prefId, _address, _areaId, @@ -330,24 +305,23 @@ export default function StuffDetail() { _installHeight, ]) - // 주소검색 API - const onSearchPostNumber = () => { - console.log('주소검색클릭!!') + // 주소검색 팝업오픈 + const onSearchPostNumberPopOpen = () => { + setShowAddressButtonValid(true) + } - setShowButtonValid(true) + //설계의뢰 팝업 오픈 + const onSearchDesignRequestPopOpen = () => { + setShowDesignRequestButtonValid(true) } useEffect(() => { if (prefValue !== '') { - console.log('우편번호검색해서 값이왔어:::::::::::', prefValue) // 발전량시뮬레이션 지역 목록 - // /api/object/prefecture/도도부현코드/list get({ url: `/api/object/prefecture/${prefValue}/list` }).then((res) => { if (!isEmptyArray(res)) { - console.log('발전량 시뮬레이션::::::::', res) - //res에 areaId추가됨 - // form.setValue('areaId', res[0].areaId) - form.setValue('areaId', res[0].prefId) + // console.log('발전량 시뮬레이션::::::::', res) + form.setValue('areaId', res[0].areaId) form.setValue('areaName', res[0].prefName) setAreaIdList(res) } @@ -362,13 +336,11 @@ export default function StuffDetail() { useEffect(() => { if (!isEmptyArray(areaIdList)) { - //도도부현넘기는지 발전량시뮬레이션지역 넘기는지 ->도도부현넘기기 - console.log('prefName::', form.watch('prefName')) let _prefName = form.watch('prefName') - //http://localhost:8080/api/object/windSpeed/兵庫県/list + // console.log('기준풍속 가져오는 API', _prefName) get({ url: `/api/object/windSpeed/${_prefName}/list` }).then((res) => { + // console.log('res::', res) if (!isEmptyArray(res)) { - // console.log('기준풍속결과:::::::::', res) setWindSpeedList(res) } }) @@ -409,10 +381,11 @@ export default function StuffDetail() { // 임시저장 const onTempSave = async () => { const formData = form.getValues() - + // console.log('formData::', formData) const params = { - saleStoreId: formData.saleStoreId, - saleStoreName: formData.saleStoreName, + saleStoreId: formData.otherSaleStoreId ? formData.otherSaleStoreId : formData.saleStoreId, + saleStoreName: formData.otherSaleStoreName ? formData.otherSaleStoreName : formData.saleStoreName, + saleStoreLevel: formData.otherSaleStoreLevel ? formData.otherSaleStoreLevel : formData.saleStoreLevel, objectStatusId: formData.objectStatusId, objectName: formData.objectName, objectNameOmit: formData.objectNameOmit, @@ -434,10 +407,19 @@ export default function StuffDetail() { workNo: null, workName: null, } + //1차점 or 2차점 안고르고 임시저장하면 + if (params.saleStoreId == '') { + params.saleStoreId = sessionState.storeId + params.saleStoreLevel = sessionState.storeLevel + } console.log('임시저장params::', params) // return await post({ url: '/api/object/save-object', data: params }).then((res) => { - console.log('res::::::', res) + if (res) { + console.log('임시저장res::::::', res) + setTempDetailData(res) + alert('임시저장 되었습니다. 물건번호를 획득하려면 필수 항목을 모두 입력해 주십시오.') + } }) } @@ -462,7 +444,8 @@ export default function StuffDetail() {
- * 필수 입력항목 + * + {getMessage('stuff.detail.required')}
@@ -471,9 +454,20 @@ export default function StuffDetail() { + + + + - + @@ -565,6 +559,8 @@ export default function StuffDetail() { clearable={true} onChange={onSelectionChange2} disabled={form.watch('saleStoreId') !== '' ? false : true} + onClearAll={handleClear} + ref={ref} >
@@ -581,23 +577,24 @@ export default function StuffDetail() {
- + - + @@ -772,6 +767,21 @@ export default function StuffDetail() { )) || ( <> + +
+
+ * 필수 입력항목 +
+
+
{getMessage('stuff.detail.designNo')} +
+
+ +
+
- 담당자 * + {getMessage('stuff.detail.dispCompanyName')} *
@@ -489,18 +483,18 @@ export default function StuffDetail() {
- +
- +
물건명 후리가나{getMessage('stuff.detail.objectNameKana')}
@@ -521,7 +515,7 @@ export default function StuffDetail() {
- 1차 판매점명 / ID + {getMessage('stuff.detail.saleStoreId')} *
@@ -549,7 +543,7 @@ export default function StuffDetail() {
-
2차 판매점명 / ID
+
{getMessage('stuff.detail.otherSaleStoreId')}
- 우편번호 * + {getMessage('stuff.detail.zipNo')} *
- +
- -
*주소검색 버튼을 클릭한 후, 도도부현 정보를 선택해주십시오.
+
{getMessage('stuff.detail.btn.addressPop.guide')}
- 도도부현 / 주소 * + {getMessage('stuff.detail.prefId')} + *
@@ -620,17 +617,15 @@ export default function StuffDetail() {
- 발전량시뮬레이션지역 * + {getMessage('stuff.detail.areaId')} *
- 기준풍속 * + {getMessage('stuff.detail.windSpeed')} *
@@ -660,14 +655,14 @@ export default function StuffDetail() { })}
- m/s이하 - + {getMessage('stuff.detail.windSpeedSpan')} +
- 수직적설량 * + {getMessage('stuff.detail.verticalSnowCover')} *
@@ -681,14 +676,14 @@ export default function StuffDetail() { cm
- +
- 면조도구분 * + {getMessage('stuff.detail.surfaceType')} *
@@ -702,7 +697,7 @@ export default function StuffDetail() {
- +
@@ -710,7 +705,7 @@ export default function StuffDetail() {
- 설치높이 * + {getMessage('stuff.detail.installHeight')} *
@@ -727,25 +722,25 @@ export default function StuffDetail() {
계약조건{getMessage('stuff.detail.conType')}
- +
- +
메모{getMessage('stuff.detail.remarks')}
- +
+ + + + +
+
+
+ {objectNo.substring(0, 1) === 'R' ? ( <> @@ -788,20 +798,26 @@ export default function StuffDetail() { ) : ( <> + {!isFormValid ? ( + + ) : ( + + )} - )} )} - {showButtonValid && } - + {showAddressButtonValid && } + {showDesignRequestButtonValid && } ) } diff --git a/src/components/management/StuffQGrid.jsx b/src/components/management/StuffQGrid.jsx index f9a65052..a2a47c5d 100644 --- a/src/components/management/StuffQGrid.jsx +++ b/src/components/management/StuffQGrid.jsx @@ -38,7 +38,7 @@ export default function StuffQGrid(props) { flex: 1, sortable: false, suppressMovable: true, - resizable: false, + resizable: true, suppressSizeToFit: false, } }, []) @@ -69,18 +69,12 @@ export default function StuffQGrid(props) { props.getCellDoubleClicked(event) }, []) - //컨텐츠에 따라 컬럼넓이 자동조절 const autoSizeStrategy = useMemo(() => { return { type: 'fitCellContents', } }, []) - const onGridReady = useCallback((event) => { - // 헤더 사이즈 조정 컬럼에 width값으로 계산 - event.api.sizeColumnsToFit() - }, []) - // Fetch data & update rowData state useEffect(() => { gridData ? setRowData(gridData) : '' @@ -97,7 +91,6 @@ export default function StuffQGrid(props) {
물건 목록이 없습니다.'} - // getRowStyle={getRowStyle} getRowClass={getRowClass} + autoSizeAllColumns={true} />
) diff --git a/src/components/management/StuffSearchCondition.jsx b/src/components/management/StuffSearchCondition.jsx index 19981fc5..f88cf3cc 100644 --- a/src/components/management/StuffSearchCondition.jsx +++ b/src/components/management/StuffSearchCondition.jsx @@ -110,7 +110,6 @@ export default function StuffSearchCondition() { //초기화 눌렀을 때 자동완성도.. const handleClear = () => { - // console.log('ref::', ref.current.state.values) if (ref.current.state.dropDown) { ref.current.methods.dropDown() } else { diff --git a/src/components/management/popup/DesignRequestPop.jsx b/src/components/management/popup/DesignRequestPop.jsx deleted file mode 100644 index ae7fd858..00000000 --- a/src/components/management/popup/DesignRequestPop.jsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -export default function DesignRequestPop() { - return
-} diff --git a/src/components/management/popup/FindAddressPop.jsx b/src/components/management/popup/FindAddressPop.jsx index bf276218..157cccb4 100644 --- a/src/components/management/popup/FindAddressPop.jsx +++ b/src/components/management/popup/FindAddressPop.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react' +import React, { useState } from 'react' import { useForm } from 'react-hook-form' import { queryStringFormatter } from '@/util/common-utils' import { useAxios } from '@/hooks/useAxios' @@ -6,7 +6,7 @@ import { globalLocaleStore } from '@/store/localeAtom' import { useRecoilValue } from 'recoil' import FindAddressPopQGrid from './FindAddressPopQGrid' import { useMessage } from '@/hooks/useMessage' - +import { isNotEmptyArray } from '@/util/common-utils' export default function FindAddressPop(props) { const globalLocaleState = useRecoilValue(globalLocaleStore) @@ -14,17 +14,37 @@ export default function FindAddressPop(props) { const { getMessage } = useMessage() - const [prefId, setPrefId] = useState('') - const [address1, setAddress1] = useState('') - const [address2, setAddress2] = useState('') - const [address3, setAddress3] = useState('') - const gridRef = useRef() + const [prefId, setPrefId] = useState(null) + const [zipNo, setZipNo] = useState(null) + const [address1, setAddress1] = useState(null) + const [address2, setAddress2] = useState(null) + const [address3, setAddress3] = useState(null) const [gridProps, setGridProps] = useState({ gridData: [], isPageable: false, - gridColumns: [], + gridColumns: [ + { + field: 'address1', + headerName: getMessage('stuff.addressPopup.gridHeader.address1'), + minWidth: 150, + checkboxSelection: true, + showDisabledCheckboxes: true, + }, + { + field: 'address2', + headerName: getMessage('stuff.addressPopup.gridHeader.address2'), + minWidth: 150, + }, + { + field: 'address3', + headerName: getMessage('stuff.addressPopup.gridHeader.address3'), + minWidth: 150, + }, + ], }) + + //검색필드 const formInitValue = { zipNo: '', } @@ -33,38 +53,46 @@ export default function FindAddressPop(props) { }) const form = { register, setValue, getValues, handleSubmit, resetField, control, watch } + //우편번호 검색 const searchPostNum = () => { // //7830060 // //9302226 // //0790177 3개짜리 - console.log(watch('zipNo')) const params = { zipcode: watch('zipNo'), } get({ url: `https://zipcloud.ibsnet.co.jp/api/search?${queryStringFormatter(params)}` }).then((res) => { - console.log('우편조회 API결과:::::', res) if (res.status === 200) { + if (isNotEmptyArray(res.results)) { + setGridProps({ ...gridProps, gridData: res.results }) + } else { + setGridProps({ ...gridProps, gridData: [] }) + } } else { - alert('등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.') + alert(getMessage('stuff.addressPopup.error.message1')) + setGridProps({ ...gridProps, gridData: [] }) } }) } // 주소적용 클릭 - const zipInfo = () => { - console.log('주소적용 클릭:::::::::') - // setAddress3('3333') - // setAddress3('4444') - // setAddress3('5555') - props.zipInfo({ - zipNo: '3434343', - address1: '3333', - address2: '4444', - address3: '5555', - }) + const applyAddress = () => { + // console.log('주소적용 클릭:::::::::', prefId, address1, address2, address3, zipNo) + if (prefId == null) { + alert('stuff.addressPopup.error.message2') + } else { + props.zipInfo({ + zipNo: zipNo, + address1: address1, + address2: address2, + address3: address3, + prefId: prefId, + }) - props.setShowButtonValid(false) + //팝업닫기 + props.setShowAddressButtonValid(false) + } } //숫자만 @@ -72,13 +100,31 @@ export default function FindAddressPop(props) { let input = e.target input.value = input.value.replace(/[^0-9]/g, '') } + + //그리드에서 선택한 우편정보 + const getSelectedRowdata = (data) => { + if (isNotEmptyArray(data)) { + setAddress1(data[0].address1) + setAddress2(data[0].address2) + setAddress3(data[0].address3) + setPrefId(data[0].prefcode) + setZipNo(data[0].zipcode) + } else { + setAddress1(null) + setAddress2(null) + setAddress3(null) + setPrefId(null) + setZipNo(null) + } + } + return (

{getMessage('stuff.addressPopup.title')}

-
@@ -96,14 +142,14 @@ export default function FindAddressPop(props) {
- +
- -
diff --git a/src/components/management/popup/FindAddressPopQGrid.jsx b/src/components/management/popup/FindAddressPopQGrid.jsx index f5b7c2fa..83544a5a 100644 --- a/src/components/management/popup/FindAddressPopQGrid.jsx +++ b/src/components/management/popup/FindAddressPopQGrid.jsx @@ -1,6 +1,66 @@ import React from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { AgGridReact } from 'ag-grid-react' + +import 'ag-grid-community/styles/ag-grid.css' +import 'ag-grid-community/styles/ag-theme-quartz.css' export default function FindAddressPopGrid(props) { - const { gridData, gridColumns, isPageable = true, gridRef } = props - return
+ const { gridData, gridColumns, isPageable = true } = props + + const [rowData, setRowData] = useState(null) + + const [gridApi, setGridApi] = useState(null) + + const [colDefs, setColDefs] = useState(gridColumns) + + const defaultColDef = useMemo(() => { + return { + flex: 1, + minWidth: 100, + sortable: false, + suppressMovable: false, + resizable: false, + suppressSizeToFit: false, + } + }, []) + + const rowBuffer = 100 + + useEffect(() => { + gridData ? setRowData(gridData) : '' + }, [gridData]) + + const rowSelection = useMemo(() => { + return { mode: 'singleRow', enableClickSelection: true } + }, []) + + const onGridReady = useCallback( + (params) => { + setGridApi(params.api) + gridData ? setRowData(gridData) : '' + }, + [gridData], + ) + + //부모로 선택한 우편정보 보내기 + const onSelectionChanged = () => { + const selectedData = gridApi.getSelectedRows() + props.getSelectedRowdata(selectedData) + } + + return ( +
+ +
+ ) } diff --git a/src/components/management/popup/PlanRequestPop.jsx b/src/components/management/popup/PlanRequestPop.jsx new file mode 100644 index 00000000..9eee76a3 --- /dev/null +++ b/src/components/management/popup/PlanRequestPop.jsx @@ -0,0 +1,169 @@ +import React, { useState } from 'react' +import { useForm } from 'react-hook-form' +import { queryStringFormatter } from '@/util/common-utils' +import { useAxios } from '@/hooks/useAxios' +import { globalLocaleStore } from '@/store/localeAtom' +import { useRecoilValue } from 'recoil' +import FindAddressPopQGrid from './FindAddressPopQGrid' +import { useMessage } from '@/hooks/useMessage' +import { isNotEmptyArray } from '@/util/common-utils' +import SingleDatePicker from '@/components/common/datepicker/SingleDatePicker' +import dayjs from 'dayjs' +export default function PlanRequestPop(props) { + const globalLocaleState = useRecoilValue(globalLocaleStore) + + const { get } = useAxios(globalLocaleState) + + const { getMessage } = useMessage() + // 검색조건 달력 셋팅 + const [startDate, setStartDate] = useState(dayjs(new Date()).add(-3, 'month').format('YYYY-MM-DD')) + const [endDate, setEndDate] = useState(dayjs(new Date()).format('YYYY-MM-DD')) + + const rangeDatePickerProps1 = { + startDate, //시작일 + setStartDate, + } + + const rangeDatePickerProps2 = { + startDate: endDate, //종료일 + setStartDate: setEndDate, + } + + const [gridProps, setGridProps] = useState({ + gridData: [], + isPageable: false, + gridColumns: [ + { + field: 'address1', + headerName: getMessage('stuff.addressPopup.gridHeader.address1'), + minWidth: 150, + checkboxSelection: true, + showDisabledCheckboxes: true, + }, + { + field: 'address2', + headerName: getMessage('stuff.addressPopup.gridHeader.address2'), + minWidth: 150, + }, + { + field: 'address3', + headerName: getMessage('stuff.addressPopup.gridHeader.address3'), + minWidth: 150, + }, + ], + }) + + //검색필드 + const formInitValue = { + //zipNo: '', + } + + const { register, setValue, getValues, handleSubmit, resetField, control, watch } = useForm({ + defaultValues: formInitValue, + }) + + const form = { register, setValue, getValues, handleSubmit, resetField, control, watch } + + return ( +
+
+
+
+

{getMessage('stuff.planReqPopup.title')}

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
설계의뢰번호 / 設計依頼番号 +
+ +
+
안건명 / 案件名 +
+ +
+
도도부현 / 都道府県 +
+ +
+
판매대리점명 / 販売代理店名 +
+ +
+
의뢰자명 / 依頼者名 +
+ +
+
상태 / 状態 +
+ +
+
기간검색 / 期間検索 +
+
+
+ + +
+
+ + +
+
+
+ +
+ ~ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+ ) +} diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index 1d395d27..33b393b8 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -1,6 +1,210 @@ +'use client' +import { useMessage } from '@/hooks/useMessage' +import { useRecoilState, useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' +import { INPUT_TYPE, BATCH_TYPE } from '@/common/common' +import { useEvent } from '@/hooks/useEvent' +import { polygonToTurfPolygon, rectToPolygon, pointsToTurfPolygon } from '@/util/canvas-util' +import { useSwal } from '@/hooks/useSwal' +import * as turf from '@turf/turf' + export function useObjectBatch() { const { getMessage } = useMessage() const canvas = useRecoilValue(canvasState) + const { addCanvasMouseEventListener, initEvent } = useEvent() + const { swalFire } = useSwal() - const openObjectBatch = () => {} + const applyOpeningAndShadow = (objectPlacement, buttonAct, surfaceShapePolygons, setShowObjectSettingModal) => { + const selectedType = objectPlacement.typeRef.current.find((radio) => radio.checked).value + const isCrossChecked = buttonAct === 1 ? objectPlacement.isCrossRef.current.checked : false + const objTempName = buttonAct === 1 ? BATCH_TYPE.OPENING_TEMP : BATCH_TYPE.SHADOW_TEMP + const objName = buttonAct === 1 ? BATCH_TYPE.OPENING : BATCH_TYPE.SHADOW + + let rect, isDown, origX, origY + let selectedSurface + + //프리입력 + if (selectedType === INPUT_TYPE.FREE) { + addCanvasMouseEventListener('mouse:down', (e) => { + isDown = true + const pointer = canvas.getPointer(e.e) + + surfaceShapePolygons.forEach((surface) => { + if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { + selectedSurface = surface + } + }) + + if (!selectedSurface) { + swalFire({ text: '지붕안에 그려야해요', icon: 'error' }) + initEvent() //이벤트 초기화 + return + } + + origX = pointer.x + origY = pointer.y + + rect = new fabric.Rect({ + left: origX, + top: origY, + originX: 'left', + originY: 'top', + width: 0, + height: 0, + angle: 0, + stroke: 'black', + }) + + //개구냐 그림자냐에 따라 변경 + rect.set({ + fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)', + name: objTempName, + }) + + canvas?.add(rect) + }) + + addCanvasMouseEventListener('mouse:move', (e) => { + if (!isDown) return + + if (selectedSurface) { + const pointer = canvas.getPointer(e.e) + const width = pointer.x - origX + const height = pointer.y - origY + + rect.set({ width: Math.abs(width), height: Math.abs(height) }) + + if (width < 0) { + rect.set({ left: Math.abs(pointer.x) }) + } + if (height < 0) { + rect.set({ top: Math.abs(pointer.y) }) + } + + canvas?.renderAll() + } + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + if (rect) { + const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect)) + const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) + + //지붕 밖으로 그렸을때 + if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) { + swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' }) + //일단 지워 + deleteTempObjects(setShowObjectSettingModal) + return + } + + if (!isCrossChecked) { + const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW) + const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj)) + const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon)) + + if (isCross) { + swalFire({ text: '겹치기 불가요...', icon: 'error' }) + deleteTempObjects(setShowObjectSettingModal) + return + } + } + + isDown = false + rect.set({ name: objName }) + rect.setCoords() + initEvent() + } + }) + } else if (selectedType === INPUT_TYPE.DIMENSION) { + const width = objectPlacement.widthRef.current.value / 10 + const height = objectPlacement.heightRef.current.value / 10 + + if (width === '' || height === '' || width <= 0 || height <= 0) { + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) + return + } + + // setShowObjectSettingModal(false) //메뉴보이고 + addCanvasMouseEventListener('mouse:move', (e) => { + isDown = true + if (!isDown) return + + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === objTempName)) //움직일때 일단 지워가면서 움직임 + const pointer = canvas.getPointer(e.e) + + surfaceShapePolygons.forEach((surface) => { + if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { + selectedSurface = surface + } + }) + + rect = new fabric.Rect({ + fill: 'white', + stroke: 'black', + strokeWidth: 1, + width: width, + height: height, + left: pointer.x - width / 2, + top: pointer.y - height / 2, + selectable: true, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + }) + + //개구냐 그림자냐에 따라 변경 + rect.set({ + fill: buttonAct === 1 ? 'white' : 'rgba(0, 0, 0, 0.4)', + name: objTempName, + }) + + canvas?.add(rect) + }) + + addCanvasMouseEventListener('mouse:up', (e) => { + if (rect) { + const rectPolygon = pointsToTurfPolygon(rectToPolygon(rect)) + const selectedSurfacePolygon = polygonToTurfPolygon(selectedSurface) + + //지붕 밖으로 그렸을때 + if (!turf.booleanWithin(rectPolygon, selectedSurfacePolygon)) { + swalFire({ text: '개구를 배치할 수 없습니다.', icon: 'error' }) + //일단 지워 + deleteTempObjects(setShowObjectSettingModal) + return + } + + if (!isCrossChecked) { + const preObjects = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW) + const preObjectsArray = preObjects.map((obj) => rectToPolygon(obj)) + const isCross = preObjectsArray.some((object) => turf.booleanOverlap(pointsToTurfPolygon(object), rectPolygon)) + + if (isCross) { + swalFire({ text: '겹치기 불가요...', icon: 'error' }) + deleteTempObjects(setShowObjectSettingModal) + return + } + } + + isDown = false + rect.set({ name: objName }) + rect.setCoords() + initEvent() + } + }) + } + } + + const deleteTempObjects = (setShowObjectSettingModal) => { + const deleteTarget = canvas?.getObjects().filter((obj) => obj.name === BATCH_TYPE.OPENING_TEMP || obj.name === BATCH_TYPE.SHADOW_TEMP) + canvas?.remove(...deleteTarget) + initEvent() //이벤트 초기화 + } + + return { + applyOpeningAndShadow, + } } diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index c9e898e9..bcffeba4 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -2,7 +2,7 @@ import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' -import { MENU } from '@/common/common' +import { MENU, BATCH_TYPE } from '@/common/common' import { getIntersectionPoint, setSurfaceShapePattern } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' import { QPolygon } from '@/components/fabric/QPolygon' @@ -131,23 +131,23 @@ export function useSurfaceShapeBatch() { if (surfaceId === 1) { if (length1 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (length2 === 0) { if (length3 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } } } else if ([2, 4].includes(surfaceId)) { if (length1 === 0 || length2 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } } else if ([3, 5, 6, 15, 18].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (surfaceId === 3 && length3 > length1) { @@ -173,7 +173,7 @@ export function useSurfaceShapeBatch() { } } else if ([8, 12, 13, 16, 17].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } @@ -201,7 +201,7 @@ export function useSurfaceShapeBatch() { } } else if ([7, 9, 10, 11, 14].includes(surfaceId)) { if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0 || length5 === 0) { - swalFire({ text: getMessage('surface.shape.validate.size'), icon: 'error' }) + swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' }) check = false } if (surfaceId === 9 || surfaceId === 10 || surfaceId === 11) { @@ -564,7 +564,27 @@ export function useSurfaceShapeBatch() { return points } + + const deleteAllSurfacesAndObjects = () => { + swalFire({ + text: '삭제 ㄱㄱ?', + type: 'confirm', + confirmFn: () => { + canvas?.getObjects().forEach((obj) => { + if (obj.name === MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH || obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.SHADOW) { + canvas?.remove(obj) + } + }) + swalFire({ text: '삭제 완료 되었습니다.' }) + }, + denyFn: () => { + swalFire({ text: '취소되었습니다.' }) + }, + }) + } + return { applySurfaceShape, + deleteAllSurfacesAndObjects, } } diff --git a/src/hooks/usePlan.js b/src/hooks/usePlan.js index c7f6620e..427fe511 100644 --- a/src/hooks/usePlan.js +++ b/src/hooks/usePlan.js @@ -54,6 +54,7 @@ export function usePlan() { 'y2', 'attributes', 'stickeyPoint', + 'text', ]) const str = JSON.stringify(objs) @@ -76,32 +77,49 @@ export function usePlan() { */ const checkModifiedCanvasPlan = () => { removeMouseLines() - const canvasStr = addCanvas() - const canvasStatus = dbToCanvasFormat(canvasToDbFormat(canvasStr)) + const canvasStatus = addCanvas() const initPlanData = initCanvasPlans.find((plan) => plan.id === currentCanvasPlan.id) - if (JSON.parse(canvasStr).objects.length === 0 && initPlanData === undefined) { - // 저장 안 된 빈 캔버스 - return false - } else if (initPlanData && canvasStatus === initPlanData.canvasStatus) { - // 저장 되어있고 변경사항 없는 캔버스 - return false + + if (!initPlanData) { + // 새로운 캔버스 + return JSON.parse(canvasStatus).objects.length > 0 } else { - return true + // 저장된 캔버스 + if (canvasStatus === initPlanData.canvasStatus) { + return false + } else { + // 각각 object들의 id 목록을 추출하여 비교 + const canvasObjsIds = getObjectIds(JSON.parse(canvasStatus).objects) + const dbObjsIds = getObjectIds(JSON.parse(initPlanData.canvasStatus).objects) + + return canvasObjsIds.length !== dbObjsIds.length || !canvasObjsIds.every((id, index) => id === dbObjsIds[index]) + } } } + const getObjectIds = (objects) => { + return objects + .filter((obj) => obj.hasOwnProperty('id')) + .map((obj) => obj.id) + .sort() + } /** * DB에 저장된 데이터를 canvas에서 사용할 수 있도록 포맷화 */ const dbToCanvasFormat = (cs) => { - return JSON.stringify(cs.replace(/##/g, '"').replace(/\\/g, '').slice(1, -1)) + // return JSON.stringify(cs.replace(/##/g, '"')) //.replace(/\\/g, ''))//.slice(1, -1)) + // JSON.stringify()를 사용하면 "가 \"로 바뀐다. 따라서, JSON.stringify를 제거 + // canvasToDbFormat()에서 \\의 상황을 없앴으므로 replace(/\\/g, '')를 제거 + return cs.replace(/##/g, '"') } /** * canvas의 데이터를 DB에 저장할 수 있도록 포맷화 */ const canvasToDbFormat = (cs) => { - return JSON.stringify(cs).replace(/"/g, '##') + // return JSON.stringify(cs).replace(/"/g, '##') + // addCanvas()에서 JSON.stringify()를 거쳐서 나오는데, 또 감싸버려서 \가 \\로 된다. 따라서, JSON.stringify를 제거 + return cs.replace(/"/g, '##') } /** @@ -130,9 +148,7 @@ export function usePlan() { setInitCanvasPlans((initCanvasPlans) => initCanvasPlans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)), ) - setPlans((plans) => - plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan)), - ) + setPlans((plans) => plans.map((plan) => (plan.id === currentCanvasPlan.id ? { ...plan, canvasStatus: canvasStatus } : plan))) }) .catch((error) => { swalFire({ text: error.message, icon: 'error' }) diff --git a/src/locales/ja.json b/src/locales/ja.json index eddf5a2a..abdfc11c 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -383,8 +383,40 @@ "stuff.message.periodError": "最大1年間閲覧可能.", "stuff.addressPopup.title": "郵便番号", "stuff.addressPopup.placeholder": "郵便番号の7桁を入力してください。", + "stuff.addressPopup.error.message1": "登録された郵便番号に住所が見つかりません。もう一度入力してください。", + "stuff.addressPopup.error.message2": "住所を選択してください.", + "stuff.addressPopup.gridHeader.address1": "都道府県", + "stuff.addressPopup.gridHeader.address2": "市区町村", + "stuff.addressPopup.gridHeader.address3": "市区町村 以下", "stuff.addressPopup.btn1": "閉じる", "stuff.addressPopup.btn2": "住所適用", + "stuff.planReqPopup.title": "設計依頼のインポート", + "stuff.detail.required": "必須入力項目", + "stuff.detail.designNo": "設計依頼No.", + "stuff.detail.dispCompanyName": "担当者", + "stuff.detail.objectStatusId": "物品区分/物件名", + "stuff.detail.objectStatus0": "新築", + "stuff.detail.objectStatus1": "基軸", + "stuff.detail.objectNameKana": "商品名 ふりがな", + "stuff.detail.saleStoreId": "一次販売店名/ID", + "stuff.detail.otherSaleStoreId": "二次販売店名/ID", + "stuff.detail.zipNo": "郵便番号 ", + "stuff.detail.btn.addressPop": "住所検索", + "stuff.detail.btn.addressPop.guide": "※ 郵便番号7桁を入力した後、アドレス検索ボタンをクリックしてください", + "stuff.detail.prefId": "都道府県 / 住所 ", + "stuff.detail.areaId": "発電量シミュレーション地域 ", + "stuff.detail.windSpeed": "基準風速", + "stuff.detail.windSpeedSpan": "m/s以下", + "stuff.detail.btn.windSpeedPop": "風速選択", + "stuff.detail.verticalSnowCover": "垂直説説", + "stuff.detail.coldRegionFlg": "寒冷地対策施行", + "stuff.detail.surfaceType": "面調図区分", + "stuff.detail.saltAreaFlg": "塩害地域用アイテムの使用", + "stuff.detail.installHeight": "設置高さ", + "stuff.detail.conType": "契約条件", + "stuff.detail.conType0": "余剰", + "stuff.detail.conType1": "全量", + "stuff.detail.remarks": "メモ", "length": "長さ", "height": "高さ", "output": "出力", @@ -438,7 +470,7 @@ "main.popup.login.validate1": "入力したパスワードが異なります。", "main.popup.login.validate2": "半角10文字以内で入力してください。", "main.popup.login.success": "パスワードが変更されました。", - "surface.shape.validate.size": "寸法を入力してください.", + "common.canvas.validate.size": "寸法を入力してください.", "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.", diff --git a/src/locales/ko.json b/src/locales/ko.json index ef41a4a8..3e37918b 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -387,8 +387,40 @@ "stuff.message.periodError": "최대1년 조회 가능합니다.", "stuff.addressPopup.title": "우편번호", "stuff.addressPopup.placeholder": "우편번호의 7자리를 입력하세요.", + "stuff.addressPopup.error.message1": "등록된 우편번호에서 주소를 찾을 수 없습니다. 다시 입력해주세요.", + "stuff.addressPopup.error.message2": "주소를 선택해주세요.", + "stuff.addressPopup.gridHeader.address1": "도도부현", + "stuff.addressPopup.gridHeader.address2": "시구정촌", + "stuff.addressPopup.gridHeader.address3": "시구정촌 이하", "stuff.addressPopup.btn1": "닫기", "stuff.addressPopup.btn2": "주소적용", + "stuff.planReqPopup.title": "설계의뢰 불러오기", + "stuff.detail.required": "필수 입력항목", + "stuff.detail.designNo": "설계의뢰No.", + "stuff.detail.dispCompanyName": "담당자", + "stuff.detail.objectStatusId": "물건구분/물건명", + "stuff.detail.objectStatus0": "신축", + "stuff.detail.objectStatus1": "기축", + "stuff.detail.objectNameKana": "물건명 후리가나", + "stuff.detail.saleStoreId": "1차 판매점명 / ID", + "stuff.detail.otherSaleStoreId": "2차 판매점명 / ID", + "stuff.detail.zipNo": "우편번호", + "stuff.detail.btn.addressPop": "주소검색", + "stuff.detail.btn.addressPop.guide": "※ 주소검색 버튼을 클릭한 후, 도도부현 정보를 선택해주십시오.", + "stuff.detail.prefId": "도도부현 / 주소", + "stuff.detail.areaId": "발전량시뮬레이션지역", + "stuff.detail.windSpeed": "기준풍속", + "stuff.detail.windSpeedSpan": "m/s이하", + "stuff.detail.btn.windSpeedPop": "풍속선택", + "stuff.detail.verticalSnowCover": "수직적설량", + "stuff.detail.coldRegionFlg": "한랭지대책시행", + "stuff.detail.surfaceType": "면조도구분", + "stuff.detail.saltAreaFlg": "염해지역용아이템사용", + "stuff.detail.installHeight": "설치높이", + "stuff.detail.conType": "계약조건", + "stuff.detail.conType0": "잉여", + "stuff.detail.conType1": "전량", + "stuff.detail.remarks": "메모", "length": "길이", "height": "높이", "output": "출력", @@ -442,7 +474,7 @@ "main.popup.login.validate1": "입력한 패스워드가 다릅니다.", "main.popup.login.validate2": "반각 10자 이내로 입력해주세요.", "main.popup.login.success": "비밀번호가 변경되었습니다.", - "surface.shape.validate.size": "사이즈를 입력해 주세요.", + "common.canvas.validate.size": "사이즈를 입력해 주세요.", "surface.shape.validate.size.1to2": "①길이는 ②보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to3": "①길이는 ③보다 큰 값을 넣어주세요.", "surface.shape.validate.size.1to23": "①길이는 ②+③보다 큰 값을 넣어주세요.",